From da3d65c18fb90279ac39e497d358fd0903a86708 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 21 Mar 2018 22:18:13 -0400 Subject: [PATCH 1/6] Devolves time to cycle conversion to machines. Thereby avoids a whole bunch of complicated machinations that would otherwise have been required of the multimachine. --- .../Implementation/MultiCRTMachine.cpp | 29 +------------ .../Implementation/MultiCRTMachine.hpp | 12 ++---- ClockReceiver/TimeTypes.hpp | 18 ++++++++ Concurrency/BestEffortUpdater.cpp | 15 ++----- Concurrency/BestEffortUpdater.hpp | 8 +--- Machines/CRTMachine.hpp | 41 +++++++------------ .../Clock Signal.xcodeproj/project.pbxproj | 2 + .../Documents/MachineDocument.swift | 35 ++-------------- .../Mac/Clock Signal/Machine/CSMachine.h | 7 +--- .../Mac/Clock Signal/Machine/CSMachine.mm | 37 +---------------- .../Updater/CSBestEffortUpdater.h | 4 +- .../Updater/CSBestEffortUpdater.mm | 9 +--- 12 files changed, 56 insertions(+), 161 deletions(-) create mode 100644 ClockReceiver/TimeTypes.hpp diff --git a/Analyser/Dynamic/MultiMachine/Implementation/MultiCRTMachine.cpp b/Analyser/Dynamic/MultiMachine/Implementation/MultiCRTMachine.cpp index b20a2c94e..9c974a106 100644 --- a/Analyser/Dynamic/MultiMachine/Implementation/MultiCRTMachine.cpp +++ b/Analyser/Dynamic/MultiMachine/Implementation/MultiCRTMachine.cpp @@ -75,41 +75,16 @@ Outputs::Speaker::Speaker *MultiCRTMachine::get_speaker() { return speaker_; } -void MultiCRTMachine::run_for(const Cycles cycles) { +void MultiCRTMachine::run_for(Time::Seconds duration) { perform_parallel([=](::CRTMachine::Machine *machine) { - if(machine->get_confidence() >= 0.01f) machine->run_for(cycles); + if(machine->get_confidence() >= 0.01f) machine->run_for(duration); }); if(delegate_) delegate_->multi_crt_did_run_machines(); } -double MultiCRTMachine::get_clock_rate() { - // TODO: something smarter than this? Not all clock rates will necessarily be the same. - std::lock_guard machines_lock(machines_mutex_); - CRTMachine::Machine *crt_machine = machines_.front()->crt_machine(); - return crt_machine ? crt_machine->get_clock_rate() : 0.0; -} - -bool MultiCRTMachine::get_clock_is_unlimited() { - std::lock_guard machines_lock(machines_mutex_); - CRTMachine::Machine *crt_machine = machines_.front()->crt_machine(); - return crt_machine ? crt_machine->get_clock_is_unlimited() : false; -} - void MultiCRTMachine::did_change_machine_order() { if(speaker_) { speaker_->set_new_front_machine(machines_.front().get()); } } - -void MultiCRTMachine::set_delegate(::CRTMachine::Machine::Delegate *delegate) { - // TODO: -} - -void MultiCRTMachine::machine_did_change_clock_rate(Machine *machine) { - // TODO: consider passing along. -} - -void MultiCRTMachine::machine_did_change_clock_is_unlimited(Machine *machine) { - // TODO: consider passing along. -} diff --git a/Analyser/Dynamic/MultiMachine/Implementation/MultiCRTMachine.hpp b/Analyser/Dynamic/MultiMachine/Implementation/MultiCRTMachine.hpp index b57583884..4588ea7e2 100644 --- a/Analyser/Dynamic/MultiMachine/Implementation/MultiCRTMachine.hpp +++ b/Analyser/Dynamic/MultiMachine/Implementation/MultiCRTMachine.hpp @@ -29,7 +29,7 @@ namespace Dynamic { acquiring a supplied mutex. The owner should also call did_change_machine_order() if the order of machines changes. */ -class MultiCRTMachine: public CRTMachine::Machine, public CRTMachine::Machine::Delegate { +class MultiCRTMachine: public CRTMachine::Machine { public: MultiCRTMachine(const std::vector> &machines, std::mutex &machines_mutex); @@ -57,16 +57,10 @@ class MultiCRTMachine: public CRTMachine::Machine, public CRTMachine::Machine::D void close_output() override; Outputs::CRT::CRT *get_crt() override; Outputs::Speaker::Speaker *get_speaker() override; - void run_for(const Cycles cycles) override; - double get_clock_rate() override; - bool get_clock_is_unlimited() override; - void set_delegate(::CRTMachine::Machine::Delegate *delegate) override; + void run_for(Time::Seconds duration) override; private: - // CRTMachine::Machine::Delegate - void machine_did_change_clock_rate(Machine *machine) override; - void machine_did_change_clock_is_unlimited(Machine *machine) override; - + void run_for(const Cycles cycles) override {} const std::vector> &machines_; std::mutex &machines_mutex_; std::vector queues_; diff --git a/ClockReceiver/TimeTypes.hpp b/ClockReceiver/TimeTypes.hpp new file mode 100644 index 000000000..63c771b73 --- /dev/null +++ b/ClockReceiver/TimeTypes.hpp @@ -0,0 +1,18 @@ +// +// TimeTypes.hpp +// Clock Signal +// +// Created by Thomas Harte on 21/03/2018. +// Copyright © 2018 Thomas Harte. All rights reserved. +// + +#ifndef TimeTypes_h +#define TimeTypes_h + +namespace Time { + +typedef double Seconds; + +} + +#endif /* TimeTypes_h */ diff --git a/Concurrency/BestEffortUpdater.cpp b/Concurrency/BestEffortUpdater.cpp index 96156b742..9278f2d71 100644 --- a/Concurrency/BestEffortUpdater.cpp +++ b/Concurrency/BestEffortUpdater.cpp @@ -36,13 +36,11 @@ void BestEffortUpdater::update() { // If the duration is valid, convert it to integer cycles, maintaining a rolling error and call the delegate // if there is one. Proceed only if the number of cycles is positive, and cap it to the per-second maximum — // it's possible this is an adjustable clock so be ready to swallow unexpected adjustments. - const int64_t duration = std::chrono::duration_cast(elapsed).count(); - if(duration > 0) { - double cycles = ((static_cast(duration) * clock_rate_) / 1e9) + error_; - error_ = fmod(cycles, 1.0); - + const int64_t integer_duration = std::chrono::duration_cast(elapsed).count(); + if(integer_duration > 0) { if(delegate_) { - delegate_->update(this, static_cast(std::min(cycles, clock_rate_)), has_skipped_); + const double duration = static_cast(integer_duration) / 1e9; + delegate_->update(this, duration, has_skipped_); } has_skipped_ = false; } @@ -70,8 +68,3 @@ void BestEffortUpdater::set_delegate(Delegate *const delegate) { }); } -void BestEffortUpdater::set_clock_rate(const double clock_rate) { - async_task_queue_.enqueue([this, clock_rate]() { - this->clock_rate_ = clock_rate; - }); -} diff --git a/Concurrency/BestEffortUpdater.hpp b/Concurrency/BestEffortUpdater.hpp index c75877dac..f15d6e474 100644 --- a/Concurrency/BestEffortUpdater.hpp +++ b/Concurrency/BestEffortUpdater.hpp @@ -13,6 +13,7 @@ #include #include "AsyncTaskQueue.hpp" +#include "TimeTypes.hpp" namespace Concurrency { @@ -30,15 +31,12 @@ class BestEffortUpdater { /// A delegate receives timing cues. struct Delegate { - virtual void update(BestEffortUpdater *updater, int cycles, bool did_skip_previous_update) = 0; + virtual void update(BestEffortUpdater *updater, Time::Seconds duration, bool did_skip_previous_update) = 0; }; /// Sets the current delegate. void set_delegate(Delegate *); - /// Sets the clock rate of the delegate. - void set_clock_rate(double clock_rate); - /*! If the delegate is not currently in the process of an `update` call, calls it now to catch up to the current time. The call is asynchronous; this method will return immediately. @@ -54,11 +52,9 @@ class BestEffortUpdater { std::chrono::time_point previous_time_point_; bool has_previous_time_point_ = false; - double error_ = 0.0; bool has_skipped_ = false; Delegate *delegate_ = nullptr; - double clock_rate_ = 1.0; }; } diff --git a/Machines/CRTMachine.hpp b/Machines/CRTMachine.hpp index 67c4286bb..ca200f9c1 100644 --- a/Machines/CRTMachine.hpp +++ b/Machines/CRTMachine.hpp @@ -12,8 +12,11 @@ #include "../Outputs/CRT/CRT.hpp" #include "../Outputs/Speaker/Speaker.hpp" #include "../ClockReceiver/ClockReceiver.hpp" +#include "../ClockReceiver/TimeTypes.hpp" #include "ROMMachine.hpp" +#include + namespace CRTMachine { /*! @@ -41,45 +44,31 @@ class Machine: public ROMMachine::Machine { /// @returns The speaker that receives this machine's output, or @c nullptr if this machine is mute. virtual Outputs::Speaker::Speaker *get_speaker() = 0; - /// Runs the machine for @c cycles. - virtual void run_for(const Cycles cycles) = 0; - /// @returns The confidence that this machine is running content it understands. virtual float get_confidence() { return 0.5f; } virtual void print_type() {} - // TODO: sever the clock-rate stuff. - virtual double get_clock_rate() { + /// Runs the machine for @c duration seconds. + virtual void run_for(Time::Seconds duration) { + const double cycles = (duration * clock_rate_) + clock_conversion_error_; + clock_conversion_error_ = std::fmod(cycles, 1.0); + run_for(Cycles(static_cast(cycles))); + } + + double get_clock_rate() { return clock_rate_; } - virtual bool get_clock_is_unlimited() { - return clock_is_unlimited_; - } - class Delegate { - public: - virtual void machine_did_change_clock_rate(Machine *machine) = 0; - virtual void machine_did_change_clock_is_unlimited(Machine *machine) = 0; - }; - virtual void set_delegate(Delegate *delegate) { delegate_ = delegate; } protected: + /// Runs the machine for @c cycles. + virtual void run_for(const Cycles cycles) = 0; void set_clock_rate(double clock_rate) { - if(clock_rate_ != clock_rate) { - clock_rate_ = clock_rate; - if(delegate_) delegate_->machine_did_change_clock_rate(this); - } - } - void set_clock_is_unlimited(bool clock_is_unlimited) { - if(clock_is_unlimited != clock_is_unlimited_) { - clock_is_unlimited_ = clock_is_unlimited; - if(delegate_) delegate_->machine_did_change_clock_is_unlimited(this); - } + clock_rate_ = clock_rate; } private: - Delegate *delegate_ = nullptr; double clock_rate_ = 1.0; - bool clock_is_unlimited_ = false; + double clock_conversion_error_ = 0.0; }; } diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 95ecf7270..3deb8c746 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -780,6 +780,7 @@ 4B448E801F1C45A00009ABD6 /* TZX.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TZX.hpp; sourceTree = ""; }; 4B448E821F1C4C480009ABD6 /* PulseQueuedTape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PulseQueuedTape.cpp; sourceTree = ""; }; 4B448E831F1C4C480009ABD6 /* PulseQueuedTape.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = PulseQueuedTape.hpp; sourceTree = ""; }; + 4B449C942063389900A095C8 /* TimeTypes.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = TimeTypes.hpp; sourceTree = ""; }; 4B44EBF41DC987AE00A7820C /* AllSuiteA.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; name = AllSuiteA.bin; path = AllSuiteA/AllSuiteA.bin; sourceTree = ""; }; 4B44EBF61DC9883B00A7820C /* 6502_functional_test.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; name = 6502_functional_test.bin; path = "Klaus Dormann/6502_functional_test.bin"; sourceTree = ""; }; 4B44EBF81DC9898E00A7820C /* BCDTEST_beeb */ = {isa = PBXFileReference; lastKnownFileType = file; name = BCDTEST_beeb; path = BCDTest/BCDTEST_beeb; sourceTree = ""; }; @@ -2972,6 +2973,7 @@ 4BF6606A1F281573002CB053 /* ClockReceiver.hpp */, 4BB06B211F316A3F00600C7A /* ForceInline.hpp */, 4BB146C61F49D7D700253439 /* Sleeper.hpp */, + 4B449C942063389900A095C8 /* TimeTypes.hpp */, ); name = ClockReceiver; path = ../../ClockReceiver; diff --git a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift index f94963372..0b1235eee 100644 --- a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift @@ -12,7 +12,6 @@ import AudioToolbox class MachineDocument: NSDocument, NSWindowDelegate, - CSMachineDelegate, CSOpenGLViewDelegate, CSOpenGLViewResponderDelegate, CSBestEffortUpdaterDelegate, @@ -56,7 +55,6 @@ class MachineDocument: self.machine.setView(self.openGLView, aspectRatio: Float(displayAspectRatio.width / displayAspectRatio.height)) }) - self.machine.delegate = self self.bestEffortUpdater = CSBestEffortUpdater() // callbacks from the OpenGL may come on a different thread, immediately following the .delegate set; @@ -74,16 +72,6 @@ class MachineDocument: self.bestEffortUpdater!.delegate = self } - func machineDidChangeClockRate(_ machine: CSMachine!) { - setupClockRate() - } - - func machineDidChangeClockIsUnlimited(_ machine: CSMachine!) { - bestEffortLock.lock() - self.bestEffortUpdater?.runAsUnlimited = machine.clockIsUnlimited - bestEffortLock.unlock() - } - fileprivate func setupClockRate() { // establish and provide the audio queue, taking advice as to an appropriate sampling rate let maximumSamplingRate = CSAudioQueue.preferredSamplingRate() @@ -94,10 +82,6 @@ class MachineDocument: self.machine.audioQueue = self.audioQueue self.machine.setAudioSamplingRate(selectedSamplingRate, bufferSize:audioQueue.preferredBufferSize) } - - bestEffortLock.lock() - self.bestEffortUpdater?.clockRate = self.machine.clockRate - bestEffortLock.unlock() } override func close() { @@ -152,21 +136,10 @@ class MachineDocument: } // MARK: CSBestEffortUpdaterDelegate - final func bestEffortUpdater(_ bestEffortUpdater: CSBestEffortUpdater!, runForCycles cycles: UInt, didSkipPreviousUpdate: Bool) { - runForNumberOfCycles(Int32(cycles)) - } - - func runForNumberOfCycles(_ numberOfCycles: Int32) { - bestEffortLock.lock() - if let bestEffortUpdater = bestEffortUpdater { - bestEffortLock.unlock() - let cyclesToRunFor = min(numberOfCycles, Int32(bestEffortUpdater.clockRate / 10)) - if actionLock.try() { - self.machine.runForNumber(ofCycles: cyclesToRunFor) - actionLock.unlock() - } - } else { - bestEffortLock.unlock() + final func bestEffortUpdater(_ bestEffortUpdater: CSBestEffortUpdater!, runForInterval duration: TimeInterval, didSkipPreviousUpdate: Bool) { + if actionLock.try() { + self.machine.run(forInterval: duration) + actionLock.unlock() } } diff --git a/OSBindings/Mac/Clock Signal/Machine/CSMachine.h b/OSBindings/Mac/Clock Signal/Machine/CSMachine.h index a9be3056b..e5c7fd4db 100644 --- a/OSBindings/Mac/Clock Signal/Machine/CSMachine.h +++ b/OSBindings/Mac/Clock Signal/Machine/CSMachine.h @@ -14,10 +14,6 @@ #import "CSStaticAnalyser.h" @class CSMachine; -@protocol CSMachineDelegate -- (void)machineDidChangeClockRate:(CSMachine *)machine; -- (void)machineDidChangeClockIsUnlimited:(CSMachine *)machine; -@end // Deliberately low; to ensure CSMachine has been declared as an @class already. #import "CSAtari2600.h" @@ -33,7 +29,7 @@ */ - (instancetype)initWithAnalyser:(CSStaticAnalyser *)result NS_DESIGNATED_INITIALIZER; -- (void)runForNumberOfCycles:(int)numberOfCycles; +- (void)runForInterval:(NSTimeInterval)interval; - (float)idealSamplingRateFromRange:(NSRange)range; - (void)setAudioSamplingRate:(float)samplingRate bufferSize:(NSUInteger)bufferSize; @@ -46,7 +42,6 @@ @property (nonatomic, strong) CSAudioQueue *audioQueue; @property (nonatomic, readonly) CSOpenGLView *view; -@property (nonatomic, weak) id delegate; @property (nonatomic, readonly) double clockRate; @property (nonatomic, readonly) BOOL clockIsUnlimited; diff --git a/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm b/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm index 5eb669aa0..1e6ddcad1 100644 --- a/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm +++ b/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm @@ -25,8 +25,6 @@ @interface CSMachine() - (void)speaker:(Outputs::Speaker::Speaker *)speaker didCompleteSamples:(const int16_t *)samples length:(int)length; -- (void)machineDidChangeClockRate; -- (void)machineDidChangeClockIsUnlimited; @end struct LockProtectedDelegate { @@ -44,22 +42,8 @@ struct SpeakerDelegate: public Outputs::Speaker::Speaker::Delegate, public LockP } }; -struct MachineDelegate: CRTMachine::Machine::Delegate, public LockProtectedDelegate { - void machine_did_change_clock_rate(CRTMachine::Machine *sender) { - [machineAccessLock lock]; - [machine machineDidChangeClockRate]; - [machineAccessLock unlock]; - } - void machine_did_change_clock_is_unlimited(CRTMachine::Machine *sender) { - [machineAccessLock lock]; - [machine machineDidChangeClockIsUnlimited]; - [machineAccessLock unlock]; - } -}; - @implementation CSMachine { SpeakerDelegate _speakerDelegate; - MachineDelegate _machineDelegate; NSLock *_delegateMachineAccessLock; CSStaticAnalyser *_analyser; @@ -77,12 +61,8 @@ struct MachineDelegate: CRTMachine::Machine::Delegate, public LockProtectedDeleg _delegateMachineAccessLock = [[NSLock alloc] init]; - _machineDelegate.machine = self; _speakerDelegate.machine = self; - _machineDelegate.machineAccessLock = _delegateMachineAccessLock; _speakerDelegate.machineAccessLock = _delegateMachineAccessLock; - - _machine->crt_machine()->set_delegate(&_machineDelegate); } return self; } @@ -91,14 +71,6 @@ struct MachineDelegate: CRTMachine::Machine::Delegate, public LockProtectedDeleg [self.audioQueue enqueueAudioBuffer:samples numberOfSamples:(unsigned int)length]; } -- (void)machineDidChangeClockRate { - [self.delegate machineDidChangeClockRate:self]; -} - -- (void)machineDidChangeClockIsUnlimited { - [self.delegate machineDidChangeClockIsUnlimited:self]; -} - - (void)dealloc { // The two delegate's references to this machine are nilled out here because close_output may result // in a data flush, which might cause an audio callback, which could cause the audio queue to decide @@ -107,7 +79,6 @@ struct MachineDelegate: CRTMachine::Machine::Delegate, public LockProtectedDeleg // They are nilled inside an explicit lock because that allows the delegates to protect their entire // call into the machine, not just the pointer access. [_delegateMachineAccessLock lock]; - _machineDelegate.machine = nil; _speakerDelegate.machine = nil; [_delegateMachineAccessLock unlock]; @@ -146,9 +117,9 @@ struct MachineDelegate: CRTMachine::Machine::Delegate, public LockProtectedDeleg } } -- (void)runForNumberOfCycles:(int)numberOfCycles { +- (void)runForInterval:(NSTimeInterval)interval { @synchronized(self) { - _machine->crt_machine()->run_for(Cycles(numberOfCycles)); + _machine->crt_machine()->run_for(interval); } } @@ -175,10 +146,6 @@ struct MachineDelegate: CRTMachine::Machine::Delegate, public LockProtectedDeleg return _machine->crt_machine()->get_clock_rate(); } -- (BOOL)clockIsUnlimited { - return _machine->crt_machine()->get_clock_is_unlimited() ? YES : NO; -} - - (void)paste:(NSString *)paste { KeyboardMachine::Machine *keyboardMachine = _machine->keyboard_machine(); if(keyboardMachine) diff --git a/OSBindings/Mac/Clock Signal/Updater/CSBestEffortUpdater.h b/OSBindings/Mac/Clock Signal/Updater/CSBestEffortUpdater.h index 7130f3636..6cbe940f0 100644 --- a/OSBindings/Mac/Clock Signal/Updater/CSBestEffortUpdater.h +++ b/OSBindings/Mac/Clock Signal/Updater/CSBestEffortUpdater.h @@ -13,15 +13,13 @@ @protocol CSBestEffortUpdaterDelegate -- (void)bestEffortUpdater:(CSBestEffortUpdater *)bestEffortUpdater runForCycles:(NSUInteger)cycles didSkipPreviousUpdate:(BOOL)didSkipPreviousUpdate; +- (void)bestEffortUpdater:(CSBestEffortUpdater *)bestEffortUpdater runForInterval:(NSTimeInterval)interval didSkipPreviousUpdate:(BOOL)didSkipPreviousUpdate; @end @interface CSBestEffortUpdater : NSObject -@property (nonatomic, assign) double clockRate; -@property (nonatomic, assign) BOOL runAsUnlimited; @property (nonatomic, weak) id delegate; - (void)update; diff --git a/OSBindings/Mac/Clock Signal/Updater/CSBestEffortUpdater.mm b/OSBindings/Mac/Clock Signal/Updater/CSBestEffortUpdater.mm index 40684a021..678b22308 100644 --- a/OSBindings/Mac/Clock Signal/Updater/CSBestEffortUpdater.mm +++ b/OSBindings/Mac/Clock Signal/Updater/CSBestEffortUpdater.mm @@ -14,12 +14,12 @@ struct UpdaterDelegate: public Concurrency::BestEffortUpdater::Delegate { __weak id delegate; NSLock *delegateLock; - void update(Concurrency::BestEffortUpdater *updater, int cycles, bool did_skip_previous_update) { + void update(Concurrency::BestEffortUpdater *updater, Time::Seconds cycles, bool did_skip_previous_update) { [delegateLock lock]; __weak id delegateCopy = delegate; [delegateLock unlock]; - [delegateCopy bestEffortUpdater:nil runForCycles:(NSUInteger)cycles didSkipPreviousUpdate:did_skip_previous_update]; + [delegateCopy bestEffortUpdater:nil runForInterval:(NSTimeInterval)cycles didSkipPreviousUpdate:did_skip_previous_update]; } }; @@ -51,11 +51,6 @@ struct UpdaterDelegate: public Concurrency::BestEffortUpdater::Delegate { _updater.flush(); } -- (void)setClockRate:(double)clockRate { - _clockRate = clockRate; - _updater.set_clock_rate(clockRate); -} - - (void)setDelegate:(id)delegate { [_delegateLock lock]; _updaterDelegate.delegate = delegate; From 682c3d8079df54db023b804ba7f0ee12920b44ec Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 22 Mar 2018 09:23:01 -0400 Subject: [PATCH 2/6] Adds new hook for watching audio output rate changes. --- .../MultiMachine/Implementation/MultiSpeaker.cpp | 10 ++++++++++ .../MultiMachine/Implementation/MultiSpeaker.hpp | 9 +++++---- OSBindings/Mac/Clock Signal/Machine/CSMachine.h | 3 --- OSBindings/Mac/Clock Signal/Machine/CSMachine.mm | 16 +++++++++++----- .../Speaker/Implementation/LowpassSpeaker.hpp | 15 +++++++++++++++ Outputs/Speaker/Speaker.hpp | 1 + 6 files changed, 42 insertions(+), 12 deletions(-) diff --git a/Analyser/Dynamic/MultiMachine/Implementation/MultiSpeaker.cpp b/Analyser/Dynamic/MultiMachine/Implementation/MultiSpeaker.cpp index 47f92e305..0e5d246c0 100644 --- a/Analyser/Dynamic/MultiMachine/Implementation/MultiSpeaker.cpp +++ b/Analyser/Dynamic/MultiMachine/Implementation/MultiSpeaker.cpp @@ -54,7 +54,17 @@ void MultiSpeaker::speaker_did_complete_samples(Speaker *speaker, const std::vec } } +void MultiSpeaker::speaker_did_change_input_clock(Speaker *speaker) { + std::lock_guard lock_guard(front_speaker_mutex_); + if(delegate_ && speaker == front_speaker_) { + delegate_->speaker_did_change_input_clock(this); + } +} + void MultiSpeaker::set_new_front_machine(::Machine::DynamicMachine *machine) { std::lock_guard lock_guard(front_speaker_mutex_); front_speaker_ = machine->crt_machine()->get_speaker(); + if(delegate_) { + delegate_->speaker_did_change_input_clock(this); + } } diff --git a/Analyser/Dynamic/MultiMachine/Implementation/MultiSpeaker.hpp b/Analyser/Dynamic/MultiMachine/Implementation/MultiSpeaker.hpp index cba1acca0..1b73a9f31 100644 --- a/Analyser/Dynamic/MultiMachine/Implementation/MultiSpeaker.hpp +++ b/Analyser/Dynamic/MultiMachine/Implementation/MultiSpeaker.hpp @@ -38,12 +38,13 @@ class MultiSpeaker: public Outputs::Speaker::Speaker, Outputs::Speaker::Speaker: void set_new_front_machine(::Machine::DynamicMachine *machine); // Below is the standard Outputs::Speaker::Speaker interface; see there for documentation. - float get_ideal_clock_rate_in_range(float minimum, float maximum); - void set_output_rate(float cycles_per_second, int buffer_size); - void set_delegate(Outputs::Speaker::Speaker::Delegate *delegate); + float get_ideal_clock_rate_in_range(float minimum, float maximum) override; + void set_output_rate(float cycles_per_second, int buffer_size) override; + void set_delegate(Outputs::Speaker::Speaker::Delegate *delegate) override; private: - void speaker_did_complete_samples(Speaker *speaker, const std::vector &buffer); + void speaker_did_complete_samples(Speaker *speaker, const std::vector &buffer) override; + void speaker_did_change_input_clock(Speaker *speaker) override; MultiSpeaker(const std::vector &speakers); std::vector speakers_; diff --git a/OSBindings/Mac/Clock Signal/Machine/CSMachine.h b/OSBindings/Mac/Clock Signal/Machine/CSMachine.h index e5c7fd4db..72f2edf62 100644 --- a/OSBindings/Mac/Clock Signal/Machine/CSMachine.h +++ b/OSBindings/Mac/Clock Signal/Machine/CSMachine.h @@ -43,9 +43,6 @@ @property (nonatomic, strong) CSAudioQueue *audioQueue; @property (nonatomic, readonly) CSOpenGLView *view; -@property (nonatomic, readonly) double clockRate; -@property (nonatomic, readonly) BOOL clockIsUnlimited; - @property (nonatomic, readonly) NSString *userDefaultsPrefix; - (void)paste:(NSString *)string; diff --git a/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm b/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm index 1e6ddcad1..0b5be154e 100644 --- a/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm +++ b/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm @@ -25,6 +25,7 @@ @interface CSMachine() - (void)speaker:(Outputs::Speaker::Speaker *)speaker didCompleteSamples:(const int16_t *)samples length:(int)length; +- (void)speakerDidChangeInputClock:(Outputs::Speaker::Speaker *)speaker; @end struct LockProtectedDelegate { @@ -35,11 +36,16 @@ struct LockProtectedDelegate { }; struct SpeakerDelegate: public Outputs::Speaker::Speaker::Delegate, public LockProtectedDelegate { - void speaker_did_complete_samples(Outputs::Speaker::Speaker *speaker, const std::vector &buffer) { + void speaker_did_complete_samples(Outputs::Speaker::Speaker *speaker, const std::vector &buffer) override { [machineAccessLock lock]; [machine speaker:speaker didCompleteSamples:buffer.data() length:(int)buffer.size()]; [machineAccessLock unlock]; } + void speaker_did_change_input_clock(Outputs::Speaker::Speaker *speaker) override { + [machineAccessLock lock]; + [machine speakerDidChangeInputClock:speaker]; + [machineAccessLock unlock]; + } }; @implementation CSMachine { @@ -71,6 +77,10 @@ struct SpeakerDelegate: public Outputs::Speaker::Speaker::Delegate, public LockP [self.audioQueue enqueueAudioBuffer:samples numberOfSamples:(unsigned int)length]; } +- (void)speakerDidChangeInputClock:(Outputs::Speaker::Speaker *)speaker { + // TODO: consider changing output audio queue rate. +} + - (void)dealloc { // The two delegate's references to this machine are nilled out here because close_output may result // in a data flush, which might cause an audio callback, which could cause the audio queue to decide @@ -142,10 +152,6 @@ struct SpeakerDelegate: public Outputs::Speaker::Speaker::Delegate, public LockP _machine->crt_machine()->get_crt()->draw_frame((unsigned int)pixelSize.width, (unsigned int)pixelSize.height, onlyIfDirty ? true : false); } -- (double)clockRate { - return _machine->crt_machine()->get_clock_rate(); -} - - (void)paste:(NSString *)paste { KeyboardMachine::Machine *keyboardMachine = _machine->keyboard_machine(); if(keyboardMachine) diff --git a/Outputs/Speaker/Implementation/LowpassSpeaker.hpp b/Outputs/Speaker/Implementation/LowpassSpeaker.hpp index b0b361c38..8f71a8bc6 100644 --- a/Outputs/Speaker/Implementation/LowpassSpeaker.hpp +++ b/Outputs/Speaker/Implementation/LowpassSpeaker.hpp @@ -15,6 +15,7 @@ #include "../../../ClockReceiver/ClockReceiver.hpp" #include "../../../Concurrency/AsyncTaskQueue.hpp" +#include #include namespace Outputs { @@ -34,6 +35,8 @@ template class LowpassSpeaker: public Speaker { // Implemented as per Speaker. float get_ideal_clock_rate_in_range(float minimum, float maximum) { + std::lock_guard lock_guard(filter_parameters_mutex_); + // return twice the cut off, if applicable if( filter_parameters_.high_frequency_cutoff > 0.0f && filter_parameters_.input_cycles_per_second >= filter_parameters_.high_frequency_cutoff * 3.0f && @@ -55,6 +58,7 @@ template class LowpassSpeaker: public Speaker { // Implemented as per Speaker. void set_output_rate(float cycles_per_second, int buffer_size) { + std::lock_guard lock_guard(filter_parameters_mutex_); filter_parameters_.output_cycles_per_second = cycles_per_second; filter_parameters_.parameters_are_dirty = true; output_buffer_.resize(static_cast(buffer_size)); @@ -64,8 +68,10 @@ template class LowpassSpeaker: public Speaker { Sets the clock rate of the input audio. */ void set_input_rate(float cycles_per_second) { + std::lock_guard lock_guard(filter_parameters_mutex_); filter_parameters_.input_cycles_per_second = cycles_per_second; filter_parameters_.parameters_are_dirty = true; + filter_parameters_.input_rate_changed = true; } /*! @@ -75,6 +81,7 @@ template class LowpassSpeaker: public Speaker { path to be explicit about its effect, and get that simulation for free. */ void set_high_frequency_cutoff(float high_frequency) { + std::lock_guard lock_guard(filter_parameters_mutex_); filter_parameters_.high_frequency_cutoff = high_frequency; filter_parameters_.parameters_are_dirty = true; } @@ -88,7 +95,13 @@ template class LowpassSpeaker: public Speaker { std::size_t cycles_remaining = static_cast(cycles.as_int()); if(!cycles_remaining) return; + + std::lock_guard lock_guard(filter_parameters_mutex_); if(filter_parameters_.parameters_are_dirty) update_filter_coefficients(); + if(filter_parameters_.input_rate_changed) { + delegate_->speaker_did_change_input_clock(this); + filter_parameters_.input_rate_changed = false; + } // If input and output rates exactly match, and no additional cut-off has been specified, // just accumulate results and pass on. @@ -175,12 +188,14 @@ template class LowpassSpeaker: public Speaker { std::unique_ptr stepper_; std::unique_ptr filter_; + std::recursive_mutex filter_parameters_mutex_; struct FilterParameters { float input_cycles_per_second = 0.0f; float output_cycles_per_second = 0.0f; float high_frequency_cutoff = -1.0; bool parameters_are_dirty = true; + bool input_rate_changed = false; } filter_parameters_; void update_filter_coefficients() { diff --git a/Outputs/Speaker/Speaker.hpp b/Outputs/Speaker/Speaker.hpp index 209667076..e3c95a5b6 100644 --- a/Outputs/Speaker/Speaker.hpp +++ b/Outputs/Speaker/Speaker.hpp @@ -28,6 +28,7 @@ class Speaker { struct Delegate { virtual void speaker_did_complete_samples(Speaker *speaker, const std::vector &buffer) = 0; + virtual void speaker_did_change_input_clock(Speaker *speaker) {} }; virtual void set_delegate(Delegate *delegate) { delegate_ = delegate; From 58e5b6e3f1ee2955bec16d400560fa5106cffad7 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 22 Mar 2018 09:23:27 -0400 Subject: [PATCH 3/6] Updates SDL kiosk mode to the death of CRTMachineDelegate. --- OSBindings/SDL/main.cpp | 22 +++------------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/OSBindings/SDL/main.cpp b/OSBindings/SDL/main.cpp index cd0bad61d..bdd334c4e 100644 --- a/OSBindings/SDL/main.cpp +++ b/OSBindings/SDL/main.cpp @@ -23,20 +23,9 @@ namespace { -struct CRTMachineDelegate: public CRTMachine::Machine::Delegate { - void machine_did_change_clock_rate(CRTMachine::Machine *machine) { - best_effort_updater->set_clock_rate(machine->get_clock_rate()); - } - - void machine_did_change_clock_is_unlimited(CRTMachine::Machine *machine) { - } - - Concurrency::BestEffortUpdater *best_effort_updater; -}; - struct BestEffortUpdaterDelegate: public Concurrency::BestEffortUpdater::Delegate { - void update(Concurrency::BestEffortUpdater *updater, int cycles, bool did_skip_previous_update) { - machine->crt_machine()->run_for(Cycles(cycles)); + void update(Concurrency::BestEffortUpdater *updater, Time::Seconds duration, bool did_skip_previous_update) override { + machine->crt_machine()->run_for(duration); } Machine::DynamicMachine *machine; @@ -46,7 +35,7 @@ struct BestEffortUpdaterDelegate: public Concurrency::BestEffortUpdater::Delegat struct SpeakerDelegate: public Outputs::Speaker::Speaker::Delegate { static const int buffer_size = 1024; - void speaker_did_complete_samples(Outputs::Speaker::Speaker *speaker, const std::vector &buffer) { + void speaker_did_complete_samples(Outputs::Speaker::Speaker *speaker, const std::vector &buffer) override { std::lock_guard lock_guard(audio_buffer_mutex_); if(audio_buffer_.size() > buffer_size) { audio_buffer_.erase(audio_buffer_.begin(), audio_buffer_.end() - buffer_size); @@ -258,7 +247,6 @@ int main(int argc, char *argv[]) { Concurrency::BestEffortUpdater updater; BestEffortUpdaterDelegate best_effort_updater_delegate; - CRTMachineDelegate crt_delegate; SpeakerDelegate speaker_delegate; // For vanilla SDL purposes, assume system ROMs can be found in one of: @@ -321,12 +309,8 @@ int main(int argc, char *argv[]) { return -1; } - updater.set_clock_rate(machine->crt_machine()->get_clock_rate()); - crt_delegate.best_effort_updater = &updater; best_effort_updater_delegate.machine = machine.get(); speaker_delegate.updater = &updater; - - machine->crt_machine()->set_delegate(&crt_delegate); updater.set_delegate(&best_effort_updater_delegate); // Attempt to set up video and audio. From 6419b0e619b0aa84915ceaeeaff29f8f5b2996a3 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 22 Mar 2018 09:48:19 -0400 Subject: [PATCH 4/6] Reintroduces `CSMachineDelegate`, allowing the Mac port to switch output audio rate dynamically. --- OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift | 6 ++++++ OSBindings/Mac/Clock Signal/Machine/CSMachine.h | 4 ++++ OSBindings/Mac/Clock Signal/Machine/CSMachine.mm | 2 +- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift index 0b1235eee..85c5fbb7f 100644 --- a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift @@ -12,6 +12,7 @@ import AudioToolbox class MachineDocument: NSDocument, NSWindowDelegate, + CSMachineDelegate, CSOpenGLViewDelegate, CSOpenGLViewResponderDelegate, CSBestEffortUpdaterDelegate, @@ -55,6 +56,7 @@ class MachineDocument: self.machine.setView(self.openGLView, aspectRatio: Float(displayAspectRatio.width / displayAspectRatio.height)) }) + self.machine.delegate = self self.bestEffortUpdater = CSBestEffortUpdater() // callbacks from the OpenGL may come on a different thread, immediately following the .delegate set; @@ -72,6 +74,10 @@ class MachineDocument: self.bestEffortUpdater!.delegate = self } + func machineSpeakerDidChangeInputClock(_ machine: CSMachine!) { + setupClockRate() + } + fileprivate func setupClockRate() { // establish and provide the audio queue, taking advice as to an appropriate sampling rate let maximumSamplingRate = CSAudioQueue.preferredSamplingRate() diff --git a/OSBindings/Mac/Clock Signal/Machine/CSMachine.h b/OSBindings/Mac/Clock Signal/Machine/CSMachine.h index 72f2edf62..4f1ea43c9 100644 --- a/OSBindings/Mac/Clock Signal/Machine/CSMachine.h +++ b/OSBindings/Mac/Clock Signal/Machine/CSMachine.h @@ -14,6 +14,9 @@ #import "CSStaticAnalyser.h" @class CSMachine; +@protocol CSMachineDelegate +- (void)machineSpeakerDidChangeInputClock:(CSMachine *)machine; +@end // Deliberately low; to ensure CSMachine has been declared as an @class already. #import "CSAtari2600.h" @@ -42,6 +45,7 @@ @property (nonatomic, strong) CSAudioQueue *audioQueue; @property (nonatomic, readonly) CSOpenGLView *view; +@property (nonatomic, weak) id delegate; @property (nonatomic, readonly) NSString *userDefaultsPrefix; diff --git a/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm b/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm index 0b5be154e..5009aabd4 100644 --- a/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm +++ b/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm @@ -78,7 +78,7 @@ struct SpeakerDelegate: public Outputs::Speaker::Speaker::Delegate, public LockP } - (void)speakerDidChangeInputClock:(Outputs::Speaker::Speaker *)speaker { - // TODO: consider changing output audio queue rate. + [self.delegate machineSpeakerDidChangeInputClock:self]; } - (void)dealloc { From a3fa9440d1d2503e45bab3624fdf6226e2a1e94f Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 22 Mar 2018 09:49:36 -0400 Subject: [PATCH 5/6] Renames method better to communicate purpose. --- OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift index 85c5fbb7f..f2e72b86a 100644 --- a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift @@ -64,7 +64,7 @@ class MachineDocument: self.openGLView.delegate = self self.openGLView.responderDelegate = self - setupClockRate() + setupAudioQueueClockRate() self.optionsPanel?.establishStoredOptions() // bring OpenGL view-holding window on top of the options panel @@ -75,10 +75,10 @@ class MachineDocument: } func machineSpeakerDidChangeInputClock(_ machine: CSMachine!) { - setupClockRate() + setupAudioQueueClockRate() } - fileprivate func setupClockRate() { + fileprivate func setupAudioQueueClockRate() { // establish and provide the audio queue, taking advice as to an appropriate sampling rate let maximumSamplingRate = CSAudioQueue.preferredSamplingRate() let selectedSamplingRate = self.machine.idealSamplingRate(from: NSRange(location: 0, length: NSInteger(maximumSamplingRate))) From 27127024618ac1fbe196927a08e3ce0ee52f2704 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 22 Mar 2018 10:01:18 -0400 Subject: [PATCH 6/6] Makes `get_clock_rate` protected. It's now an implementation detail. --- Machines/CRTMachine.hpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Machines/CRTMachine.hpp b/Machines/CRTMachine.hpp index ca200f9c1..4229fd0b1 100644 --- a/Machines/CRTMachine.hpp +++ b/Machines/CRTMachine.hpp @@ -55,16 +55,15 @@ class Machine: public ROMMachine::Machine { run_for(Cycles(static_cast(cycles))); } - double get_clock_rate() { - return clock_rate_; - } - protected: /// Runs the machine for @c cycles. virtual void run_for(const Cycles cycles) = 0; void set_clock_rate(double clock_rate) { clock_rate_ = clock_rate; } + double get_clock_rate() { + return clock_rate_; + } private: double clock_rate_ = 1.0;