1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-11-26 08:49:37 +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());
}
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);
}
}

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.
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:

View File

@ -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<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.
@ -169,6 +189,7 @@ class Machine {
private:
double clock_rate_ = 1.0;
double clock_conversion_error_ = 0.0;
double speed_multiplier_ = 1.0;
};
}

View File

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

View File

@ -345,7 +345,7 @@ int main(int argc, char *argv[]) {
ParsedArguments arguments = parse_arguments(argc, argv);
// 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.
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/"
};
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() != '/') {
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;

View File

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

View File

@ -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<int16_t> &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;
};
}