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:
parent
7e4c13c43e
commit
b514756272
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -67,7 +67,7 @@
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
buildConfiguration = "Release"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
enableASanStackUseAfterReturn = "YES"
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user