mirror of
https://github.com/TomHarte/CLK.git
synced 2024-11-29 12:50:28 +00:00
394ee61c78
The Amstrad CPC and ZX80/81 have made the jump so far, subject to caveats. The macOS build is unlikely currently to work properly.
216 lines
6.8 KiB
C++
216 lines
6.8 KiB
C++
//
|
|
// CRTMachine.hpp
|
|
// Clock Signal
|
|
//
|
|
// Created by Thomas Harte on 31/05/2016.
|
|
// Copyright 2016 Thomas Harte. All rights reserved.
|
|
//
|
|
|
|
#ifndef CRTMachine_hpp
|
|
#define CRTMachine_hpp
|
|
|
|
#include "../Outputs/ScanTarget.hpp"
|
|
#include "../Outputs/Speaker/Speaker.hpp"
|
|
#include "../ClockReceiver/ClockReceiver.hpp"
|
|
#include "../ClockReceiver/TimeTypes.hpp"
|
|
#include "ROMMachine.hpp"
|
|
|
|
#include "../Configurable/StandardOptions.hpp"
|
|
|
|
#include <array>
|
|
#include <cmath>
|
|
|
|
// TODO: rename.
|
|
namespace CRTMachine {
|
|
|
|
/*!
|
|
A CRTMachine::Machine is a mostly-abstract base class for machines that connect to a CRT,
|
|
that optionally provide a speaker, and that nominate a clock rate and can announce to a delegate
|
|
should that clock rate change.
|
|
*/
|
|
class Machine {
|
|
public:
|
|
/*!
|
|
Causes the machine to set up its display and, if it has one, speaker.
|
|
|
|
The @c scan_target will receive all video output; the caller guarantees
|
|
that it is non-null.
|
|
*/
|
|
virtual void set_scan_target(Outputs::Display::ScanTarget *scan_target) = 0;
|
|
|
|
/*!
|
|
@returns The current scan status.
|
|
*/
|
|
virtual Outputs::Display::ScanStatus get_scan_status() const {
|
|
return get_scaled_scan_status() / float(clock_rate_);
|
|
}
|
|
|
|
/// @returns The speaker that receives this machine's output, or @c nullptr if this machine is mute.
|
|
virtual Outputs::Speaker::Speaker *get_speaker() = 0;
|
|
|
|
/// @returns The confidence that this machine is running content it understands.
|
|
virtual float get_confidence() { return 0.5f; }
|
|
virtual std::string debug_type() { return ""; }
|
|
|
|
/// Runs the machine for @c duration seconds.
|
|
virtual void run_for(Time::Seconds duration) {
|
|
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.
|
|
|
|
@returns The amount of time run for.
|
|
*/
|
|
Time::Seconds run_until(Time::Seconds minimum_duration, std::function<bool()> condition) {
|
|
Time::Seconds total_runtime = minimum_duration;
|
|
run_for(minimum_duration);
|
|
while(!condition()) {
|
|
// Advance in increments of one 500th of a second until the condition
|
|
// is true; that's 1/10th of a 50Hz frame, but more like 1/8.33 of a
|
|
// 60Hz frame. Though most machines aren't exactly 50Hz or 60Hz, and some
|
|
// are arbitrary other refresh rates. So those observations are merely
|
|
// for scale.
|
|
run_for(0.002);
|
|
total_runtime += 0.002;
|
|
}
|
|
return total_runtime;
|
|
}
|
|
|
|
enum MachineEvent: int {
|
|
/// At least one new packet of audio has been delivered to the spaker's delegate.
|
|
NewSpeakerSamplesGenerated = 1 << 0,
|
|
|
|
/// The next vertical retrace has begun.
|
|
VerticalSync = 1 << 1,
|
|
};
|
|
|
|
/*!
|
|
Runs for at least @c duration seconds, and then every one of the @c events has occurred at least once since this
|
|
call to @c run_until_event.
|
|
|
|
@param events A bitmask comprised of @c MachineEvent flags.
|
|
@returns The amount of time run for.
|
|
*/
|
|
Time::Seconds run_until(Time::Seconds minimum_duration, int events) {
|
|
// Tie up a wait-for-samples, if requested.
|
|
const Outputs::Speaker::Speaker *speaker = nullptr;
|
|
int sample_sets = 0;
|
|
if(events & MachineEvent::NewSpeakerSamplesGenerated) {
|
|
speaker = get_speaker();
|
|
if(!speaker) events &= ~MachineEvent::NewSpeakerSamplesGenerated;
|
|
sample_sets = speaker->completed_sample_sets();
|
|
}
|
|
|
|
int retraces = 0;
|
|
if(events & MachineEvent::VerticalSync) {
|
|
retraces = get_scan_status().hsync_count;
|
|
}
|
|
|
|
// Run until all requested events are satisfied.
|
|
return run_until(minimum_duration, [=]() {
|
|
return
|
|
(!(events & MachineEvent::NewSpeakerSamplesGenerated) || (sample_sets != speaker->completed_sample_sets())) &&
|
|
(!(events & MachineEvent::VerticalSync) || (retraces != get_scan_status().hsync_count));
|
|
});
|
|
}
|
|
|
|
protected:
|
|
/// Runs the machine for @c cycles.
|
|
virtual void run_for(const Cycles cycles) = 0;
|
|
void set_clock_rate(double clock_rate) {
|
|
clock_rate_ = clock_rate;
|
|
}
|
|
double get_clock_rate() {
|
|
return clock_rate_;
|
|
}
|
|
|
|
virtual Outputs::Display::ScanStatus get_scaled_scan_status() const {
|
|
// This deliberately sets up an infinite loop if the user hasn't
|
|
// overridden at least one of this or get_scan_status.
|
|
//
|
|
// Most likely you want to override this, and let the base class
|
|
// throw in a divide-by-clock-rate at the end for you.
|
|
return get_scan_status();
|
|
}
|
|
|
|
/*!
|
|
Maps from Configurable::Display to Outputs::Display::VideoSignal and calls
|
|
@c set_display_type with the result.
|
|
*/
|
|
void set_video_signal_configurable(Configurable::Display type) {
|
|
Outputs::Display::DisplayType display_type;
|
|
switch(type) {
|
|
default:
|
|
case Configurable::Display::RGB:
|
|
display_type = Outputs::Display::DisplayType::RGB;
|
|
break;
|
|
case Configurable::Display::SVideo:
|
|
display_type = Outputs::Display::DisplayType::SVideo;
|
|
break;
|
|
case Configurable::Display::CompositeColour:
|
|
display_type = Outputs::Display::DisplayType::CompositeColour;
|
|
break;
|
|
case Configurable::Display::CompositeMonochrome:
|
|
display_type = Outputs::Display::DisplayType::CompositeMonochrome;
|
|
break;
|
|
}
|
|
set_display_type(display_type);
|
|
}
|
|
|
|
/*!
|
|
Maps back from Outputs::Display::VideoSignal to Configurable::Display,
|
|
calling @c get_display_type for the input.
|
|
*/
|
|
Configurable::Display get_video_signal_configurable() {
|
|
switch(get_display_type()) {
|
|
default:
|
|
case Outputs::Display::DisplayType::RGB: return Configurable::Display::RGB;
|
|
case Outputs::Display::DisplayType::SVideo: return Configurable::Display::SVideo;
|
|
case Outputs::Display::DisplayType::CompositeColour: return Configurable::Display::CompositeColour;
|
|
case Outputs::Display::DisplayType::CompositeMonochrome: return Configurable::Display::CompositeMonochrome;
|
|
}
|
|
}
|
|
|
|
/*!
|
|
Sets the display type.
|
|
*/
|
|
virtual void set_display_type(Outputs::Display::DisplayType display_type) {}
|
|
|
|
/*!
|
|
Gets the display type.
|
|
*/
|
|
virtual Outputs::Display::DisplayType get_display_type() { return Outputs::Display::DisplayType::RGB; }
|
|
|
|
private:
|
|
double clock_rate_ = 1.0;
|
|
double clock_conversion_error_ = 0.0;
|
|
double speed_multiplier_ = 1.0;
|
|
};
|
|
|
|
}
|
|
|
|
#endif /* CRTMachine_hpp */
|