1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-11-29 12:50:28 +00:00

Adds the option to run machines at a multiple of their real speeds.

Exposed to SDL users only, for now.
This commit is contained in:
Thomas Harte 2020-01-26 13:25:23 -05:00
parent 7e4c13c43e
commit b514756272
7 changed files with 69 additions and 10 deletions

View File

@ -37,9 +37,9 @@ float MultiSpeaker::get_ideal_clock_rate_in_range(float minimum, float maximum)
return ideal / static_cast<float>(speakers_.size()); return ideal / static_cast<float>(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_) { for(const auto &speaker: speakers_) {
speaker->set_output_rate(cycles_per_second, buffer_size); speaker->set_computed_output_rate(cycles_per_second, buffer_size);
} }
} }

View File

@ -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. // Below is the standard Outputs::Speaker::Speaker interface; see there for documentation.
float get_ideal_clock_rate_in_range(float minimum, float maximum) override; 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; void set_delegate(Outputs::Speaker::Speaker::Delegate *delegate) override;
private: private:

View File

@ -54,11 +54,31 @@ class Machine {
/// Runs the machine for @c duration seconds. /// Runs the machine for @c duration seconds.
virtual void run_for(Time::Seconds duration) { 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); clock_conversion_error_ = std::fmod(cycles, 1.0);
run_for(Cycles(static_cast<int>(cycles))); run_for(Cycles(static_cast<int>(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. Runs for the machine for at least @c duration seconds, and then until @c condition is true.
@ -169,6 +189,7 @@ class Machine {
private: private:
double clock_rate_ = 1.0; double clock_rate_ = 1.0;
double clock_conversion_error_ = 0.0; double clock_conversion_error_ = 0.0;
double speed_multiplier_ = 1.0;
}; };
} }

View File

@ -67,7 +67,7 @@
</Testables> </Testables>
</TestAction> </TestAction>
<LaunchAction <LaunchAction
buildConfiguration = "Debug" buildConfiguration = "Release"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
enableASanStackUseAfterReturn = "YES" enableASanStackUseAfterReturn = "YES"

View File

@ -345,7 +345,7 @@ int main(int argc, char *argv[]) {
ParsedArguments arguments = parse_arguments(argc, argv); ParsedArguments arguments = parse_arguments(argc, argv);
// This may be printed either as // This may be printed either as
const std::string usage_suffix = " [file] [OPTIONS] [--rompath={path to ROMs}]"; const std::string usage_suffix = " [file] [OPTIONS] [--rompath={path to ROMs}] [--speed={speed multiplier, e.g. 1.5}]";
// Print a help message if requested. // Print a help message if requested.
if(arguments.selections.find("help") != arguments.selections.end() || arguments.selections.find("h") != arguments.selections.end()) { if(arguments.selections.find("help") != arguments.selections.end() || arguments.selections.find("h") != arguments.selections.end()) {
@ -410,7 +410,7 @@ int main(int argc, char *argv[]) {
"/usr/share/CLK/" "/usr/share/CLK/"
}; };
if(arguments.selections.find("rompath") != arguments.selections.end()) { if(arguments.selections.find("rompath") != arguments.selections.end()) {
std::string user_path = arguments.selections["rompath"]->list_selection()->value; const std::string user_path = arguments.selections["rompath"]->list_selection()->value;
if(user_path.back() != '/') { if(user_path.back() != '/') {
paths.push_back(user_path + "/"); paths.push_back(user_path + "/");
} else { } else {
@ -472,6 +472,23 @@ int main(int argc, char *argv[]) {
return EXIT_FAILURE; 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 = machine.get();
best_effort_updater_delegate.machine_mutex = &machine_mutex; best_effort_updater_delegate.machine_mutex = &machine_mutex;
speaker_delegate.updater = &updater; speaker_delegate.updater = &updater;

View File

@ -35,7 +35,7 @@ template <typename T> class LowpassSpeaker: public Speaker {
} }
// Implemented as per 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<std::mutex> lock_guard(filter_parameters_mutex_); std::lock_guard<std::mutex> lock_guard(filter_parameters_mutex_);
// return twice the cut off, if applicable // return twice the cut off, if applicable
@ -58,7 +58,7 @@ template <typename T> class LowpassSpeaker: public Speaker {
} }
// Implemented as per 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<std::mutex> lock_guard(filter_parameters_mutex_); std::lock_guard<std::mutex> lock_guard(filter_parameters_mutex_);
filter_parameters_.output_cycles_per_second = cycles_per_second; filter_parameters_.output_cycles_per_second = cycles_per_second;
filter_parameters_.parameters_are_dirty = true; filter_parameters_.parameters_are_dirty = true;

View File

@ -24,7 +24,15 @@ class Speaker {
virtual ~Speaker() {} virtual ~Speaker() {}
virtual float get_ideal_clock_rate_in_range(float minimum, float maximum) = 0; 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_; } int completed_sample_sets() const { return completed_sample_sets_; }
@ -36,13 +44,26 @@ class Speaker {
delegate_ = delegate; delegate_ = delegate;
} }
virtual void set_computed_output_rate(float cycles_per_second, int buffer_size) = 0;
protected: protected:
void did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer) { void did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer) {
++completed_sample_sets_; ++completed_sample_sets_;
delegate_->speaker_did_complete_samples(this, buffer); delegate_->speaker_did_complete_samples(this, buffer);
} }
Delegate *delegate_ = nullptr; 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; int completed_sample_sets_ = 0;
float input_rate_multiplier_ = 1.0f;
float output_cycles_per_second_ = 1.0f;
int output_buffer_size_ = 1;
}; };
} }