From b5147562720803d2d354e7cb451731d34c3e9160 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 26 Jan 2020 13:25:23 -0500 Subject: [PATCH] Adds the option to run machines at a multiple of their real speeds. Exposed to SDL users only, for now. --- .../Implementation/MultiSpeaker.cpp | 4 ++-- .../Implementation/MultiSpeaker.hpp | 2 +- Machines/CRTMachine.hpp | 23 ++++++++++++++++++- .../xcschemes/Clock Signal.xcscheme | 2 +- OSBindings/SDL/main.cpp | 21 +++++++++++++++-- .../Speaker/Implementation/LowpassSpeaker.hpp | 4 ++-- Outputs/Speaker/Speaker.hpp | 23 ++++++++++++++++++- 7 files changed, 69 insertions(+), 10 deletions(-) diff --git a/Analyser/Dynamic/MultiMachine/Implementation/MultiSpeaker.cpp b/Analyser/Dynamic/MultiMachine/Implementation/MultiSpeaker.cpp index c5a8fa724..a6f412ca4 100644 --- a/Analyser/Dynamic/MultiMachine/Implementation/MultiSpeaker.cpp +++ b/Analyser/Dynamic/MultiMachine/Implementation/MultiSpeaker.cpp @@ -37,9 +37,9 @@ float MultiSpeaker::get_ideal_clock_rate_in_range(float minimum, float maximum) return ideal / static_cast(speakers_.size()); } -void MultiSpeaker::set_output_rate(float cycles_per_second, int buffer_size) { +void MultiSpeaker::set_computed_output_rate(float cycles_per_second, int buffer_size) { for(const auto &speaker: speakers_) { - speaker->set_output_rate(cycles_per_second, buffer_size); + speaker->set_computed_output_rate(cycles_per_second, buffer_size); } } diff --git a/Analyser/Dynamic/MultiMachine/Implementation/MultiSpeaker.hpp b/Analyser/Dynamic/MultiMachine/Implementation/MultiSpeaker.hpp index a6f882fd0..890526118 100644 --- a/Analyser/Dynamic/MultiMachine/Implementation/MultiSpeaker.hpp +++ b/Analyser/Dynamic/MultiMachine/Implementation/MultiSpeaker.hpp @@ -39,7 +39,7 @@ class MultiSpeaker: public Outputs::Speaker::Speaker, Outputs::Speaker::Speaker: // Below is the standard Outputs::Speaker::Speaker interface; see there for documentation. 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_computed_output_rate(float cycles_per_second, int buffer_size) override; void set_delegate(Outputs::Speaker::Speaker::Delegate *delegate) override; private: diff --git a/Machines/CRTMachine.hpp b/Machines/CRTMachine.hpp index 62c1ff408..1e57575c1 100644 --- a/Machines/CRTMachine.hpp +++ b/Machines/CRTMachine.hpp @@ -54,11 +54,31 @@ class Machine { /// Runs the machine for @c duration seconds. virtual void run_for(Time::Seconds duration) { - const double cycles = (duration * clock_rate_) + clock_conversion_error_; + const double cycles = (duration * clock_rate_ * speed_multiplier_) + clock_conversion_error_; clock_conversion_error_ = std::fmod(cycles, 1.0); run_for(Cycles(static_cast(cycles))); } + /*! + Sets a speed multiplier to apply to this machine; e.g. a multiplier of 1.5 will cause the + emulated machine to run 50% faster than a real machine. This speed-up is an emulation + fiction: it will apply across the system, including to the CRT. + */ + virtual void set_speed_multiplier(double multiplier) { + speed_multiplier_ = multiplier; + auto speaker = get_speaker(); + if(speaker) { + speaker->set_input_rate_multiplier(float(multiplier)); + } + } + + /*! + @returns The current speed multiplier. + */ + virtual double get_speed_multiplier() { + return speed_multiplier_; + } + /*! Runs for the machine for at least @c duration seconds, and then until @c condition is true. @@ -169,6 +189,7 @@ class Machine { private: double clock_rate_ = 1.0; double clock_conversion_error_ = 0.0; + double speed_multiplier_ = 1.0; }; } diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme index 47f9c7286..1465a4f62 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme +++ b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme @@ -67,7 +67,7 @@ list_selection()->value; + const std::string user_path = arguments.selections["rompath"]->list_selection()->value; if(user_path.back() != '/') { paths.push_back(user_path + "/"); } else { @@ -472,6 +472,23 @@ int main(int argc, char *argv[]) { return EXIT_FAILURE; } + // Apply the speed multiplier, if one was requested. + if(arguments.selections.find("speed") != arguments.selections.end()) { + const char *speed_string = arguments.selections["speed"]->list_selection()->value.c_str(); + char *end; + double speed = strtod(speed_string, &end); + + if(end-speed_string != strlen(speed_string)) { + std::cerr << "Unable to parse speed: " << speed_string << std::endl; + } else if(speed <= 0.0) { + std::cerr << "Cannot run at speed " << speed_string << "; speeds must be positive." << std::endl; + } else { + machine->crt_machine()->set_speed_multiplier(speed); + // TODO: what if not a 'CRT' machine? Likely rests on resolving this project's machine naming policy. + } + } + + // Wire up the best-effort updater, its delegate, and the speaker delegate. best_effort_updater_delegate.machine = machine.get(); best_effort_updater_delegate.machine_mutex = &machine_mutex; speaker_delegate.updater = &updater; diff --git a/Outputs/Speaker/Implementation/LowpassSpeaker.hpp b/Outputs/Speaker/Implementation/LowpassSpeaker.hpp index ced763a56..686e00d25 100644 --- a/Outputs/Speaker/Implementation/LowpassSpeaker.hpp +++ b/Outputs/Speaker/Implementation/LowpassSpeaker.hpp @@ -35,7 +35,7 @@ template class LowpassSpeaker: public Speaker { } // Implemented as per Speaker. - float get_ideal_clock_rate_in_range(float minimum, float maximum) { + float get_ideal_clock_rate_in_range(float minimum, float maximum) final { std::lock_guard lock_guard(filter_parameters_mutex_); // return twice the cut off, if applicable @@ -58,7 +58,7 @@ template class LowpassSpeaker: public Speaker { } // Implemented as per Speaker. - void set_output_rate(float cycles_per_second, int buffer_size) { + void set_computed_output_rate(float cycles_per_second, int buffer_size) final { std::lock_guard lock_guard(filter_parameters_mutex_); filter_parameters_.output_cycles_per_second = cycles_per_second; filter_parameters_.parameters_are_dirty = true; diff --git a/Outputs/Speaker/Speaker.hpp b/Outputs/Speaker/Speaker.hpp index 2f575880b..941621729 100644 --- a/Outputs/Speaker/Speaker.hpp +++ b/Outputs/Speaker/Speaker.hpp @@ -24,7 +24,15 @@ class Speaker { virtual ~Speaker() {} virtual float get_ideal_clock_rate_in_range(float minimum, float maximum) = 0; - virtual void set_output_rate(float cycles_per_second, int buffer_size) = 0; + void set_output_rate(float cycles_per_second, int buffer_size) { + output_cycles_per_second_ = cycles_per_second; + output_buffer_size_ = buffer_size; + compute_output_rate(); + } + void set_input_rate_multiplier(float multiplier) { + input_rate_multiplier_ = multiplier; + compute_output_rate(); + } int completed_sample_sets() const { return completed_sample_sets_; } @@ -36,13 +44,26 @@ class Speaker { delegate_ = delegate; } + virtual void set_computed_output_rate(float cycles_per_second, int buffer_size) = 0; + protected: void did_complete_samples(Speaker *speaker, const std::vector &buffer) { ++completed_sample_sets_; delegate_->speaker_did_complete_samples(this, buffer); } Delegate *delegate_ = nullptr; + + private: + void compute_output_rate() { + // The input rate multiplier is actually used as an output rate divider, + // to confirm to the public interface of a generic speaker being output-centric. + set_computed_output_rate(output_cycles_per_second_ / input_rate_multiplier_, output_buffer_size_); + } + int completed_sample_sets_ = 0; + float input_rate_multiplier_ = 1.0f; + float output_cycles_per_second_ = 1.0f; + int output_buffer_size_ = 1; }; }