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:
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());
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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:
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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"
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user