mirror of
https://github.com/TomHarte/CLK.git
synced 2025-01-27 06:35:04 +00:00
Merge pull request #371 from TomHarte/NanosecondMachines
Devolves time -> clock rate mapping to machines.
This commit is contained in:
commit
29921bfa8d
@ -75,41 +75,16 @@ Outputs::Speaker::Speaker *MultiCRTMachine::get_speaker() {
|
||||
return speaker_;
|
||||
}
|
||||
|
||||
void MultiCRTMachine::run_for(const Cycles cycles) {
|
||||
void MultiCRTMachine::run_for(Time::Seconds duration) {
|
||||
perform_parallel([=](::CRTMachine::Machine *machine) {
|
||||
if(machine->get_confidence() >= 0.01f) machine->run_for(cycles);
|
||||
if(machine->get_confidence() >= 0.01f) machine->run_for(duration);
|
||||
});
|
||||
|
||||
if(delegate_) delegate_->multi_crt_did_run_machines();
|
||||
}
|
||||
|
||||
double MultiCRTMachine::get_clock_rate() {
|
||||
// TODO: something smarter than this? Not all clock rates will necessarily be the same.
|
||||
std::lock_guard<std::mutex> machines_lock(machines_mutex_);
|
||||
CRTMachine::Machine *crt_machine = machines_.front()->crt_machine();
|
||||
return crt_machine ? crt_machine->get_clock_rate() : 0.0;
|
||||
}
|
||||
|
||||
bool MultiCRTMachine::get_clock_is_unlimited() {
|
||||
std::lock_guard<std::mutex> machines_lock(machines_mutex_);
|
||||
CRTMachine::Machine *crt_machine = machines_.front()->crt_machine();
|
||||
return crt_machine ? crt_machine->get_clock_is_unlimited() : false;
|
||||
}
|
||||
|
||||
void MultiCRTMachine::did_change_machine_order() {
|
||||
if(speaker_) {
|
||||
speaker_->set_new_front_machine(machines_.front().get());
|
||||
}
|
||||
}
|
||||
|
||||
void MultiCRTMachine::set_delegate(::CRTMachine::Machine::Delegate *delegate) {
|
||||
// TODO:
|
||||
}
|
||||
|
||||
void MultiCRTMachine::machine_did_change_clock_rate(Machine *machine) {
|
||||
// TODO: consider passing along.
|
||||
}
|
||||
|
||||
void MultiCRTMachine::machine_did_change_clock_is_unlimited(Machine *machine) {
|
||||
// TODO: consider passing along.
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ namespace Dynamic {
|
||||
acquiring a supplied mutex. The owner should also call did_change_machine_order()
|
||||
if the order of machines changes.
|
||||
*/
|
||||
class MultiCRTMachine: public CRTMachine::Machine, public CRTMachine::Machine::Delegate {
|
||||
class MultiCRTMachine: public CRTMachine::Machine {
|
||||
public:
|
||||
MultiCRTMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::mutex &machines_mutex);
|
||||
|
||||
@ -57,16 +57,10 @@ class MultiCRTMachine: public CRTMachine::Machine, public CRTMachine::Machine::D
|
||||
void close_output() override;
|
||||
Outputs::CRT::CRT *get_crt() override;
|
||||
Outputs::Speaker::Speaker *get_speaker() override;
|
||||
void run_for(const Cycles cycles) override;
|
||||
double get_clock_rate() override;
|
||||
bool get_clock_is_unlimited() override;
|
||||
void set_delegate(::CRTMachine::Machine::Delegate *delegate) override;
|
||||
void run_for(Time::Seconds duration) override;
|
||||
|
||||
private:
|
||||
// CRTMachine::Machine::Delegate
|
||||
void machine_did_change_clock_rate(Machine *machine) override;
|
||||
void machine_did_change_clock_is_unlimited(Machine *machine) override;
|
||||
|
||||
void run_for(const Cycles cycles) override {}
|
||||
const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines_;
|
||||
std::mutex &machines_mutex_;
|
||||
std::vector<Concurrency::AsyncTaskQueue> queues_;
|
||||
|
@ -54,7 +54,17 @@ void MultiSpeaker::speaker_did_complete_samples(Speaker *speaker, const std::vec
|
||||
}
|
||||
}
|
||||
|
||||
void MultiSpeaker::speaker_did_change_input_clock(Speaker *speaker) {
|
||||
std::lock_guard<std::mutex> lock_guard(front_speaker_mutex_);
|
||||
if(delegate_ && speaker == front_speaker_) {
|
||||
delegate_->speaker_did_change_input_clock(this);
|
||||
}
|
||||
}
|
||||
|
||||
void MultiSpeaker::set_new_front_machine(::Machine::DynamicMachine *machine) {
|
||||
std::lock_guard<std::mutex> lock_guard(front_speaker_mutex_);
|
||||
front_speaker_ = machine->crt_machine()->get_speaker();
|
||||
if(delegate_) {
|
||||
delegate_->speaker_did_change_input_clock(this);
|
||||
}
|
||||
}
|
||||
|
@ -38,12 +38,13 @@ class MultiSpeaker: public Outputs::Speaker::Speaker, Outputs::Speaker::Speaker:
|
||||
void set_new_front_machine(::Machine::DynamicMachine *machine);
|
||||
|
||||
// Below is the standard Outputs::Speaker::Speaker interface; see there for documentation.
|
||||
float get_ideal_clock_rate_in_range(float minimum, float maximum);
|
||||
void set_output_rate(float cycles_per_second, int buffer_size);
|
||||
void set_delegate(Outputs::Speaker::Speaker::Delegate *delegate);
|
||||
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_delegate(Outputs::Speaker::Speaker::Delegate *delegate) override;
|
||||
|
||||
private:
|
||||
void speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer);
|
||||
void speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer) override;
|
||||
void speaker_did_change_input_clock(Speaker *speaker) override;
|
||||
MultiSpeaker(const std::vector<Outputs::Speaker::Speaker *> &speakers);
|
||||
|
||||
std::vector<Outputs::Speaker::Speaker *> speakers_;
|
||||
|
18
ClockReceiver/TimeTypes.hpp
Normal file
18
ClockReceiver/TimeTypes.hpp
Normal file
@ -0,0 +1,18 @@
|
||||
//
|
||||
// TimeTypes.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 21/03/2018.
|
||||
// Copyright © 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef TimeTypes_h
|
||||
#define TimeTypes_h
|
||||
|
||||
namespace Time {
|
||||
|
||||
typedef double Seconds;
|
||||
|
||||
}
|
||||
|
||||
#endif /* TimeTypes_h */
|
@ -36,13 +36,11 @@ void BestEffortUpdater::update() {
|
||||
// If the duration is valid, convert it to integer cycles, maintaining a rolling error and call the delegate
|
||||
// if there is one. Proceed only if the number of cycles is positive, and cap it to the per-second maximum —
|
||||
// it's possible this is an adjustable clock so be ready to swallow unexpected adjustments.
|
||||
const int64_t duration = std::chrono::duration_cast<std::chrono::nanoseconds>(elapsed).count();
|
||||
if(duration > 0) {
|
||||
double cycles = ((static_cast<double>(duration) * clock_rate_) / 1e9) + error_;
|
||||
error_ = fmod(cycles, 1.0);
|
||||
|
||||
const int64_t integer_duration = std::chrono::duration_cast<std::chrono::nanoseconds>(elapsed).count();
|
||||
if(integer_duration > 0) {
|
||||
if(delegate_) {
|
||||
delegate_->update(this, static_cast<int>(std::min(cycles, clock_rate_)), has_skipped_);
|
||||
const double duration = static_cast<double>(integer_duration) / 1e9;
|
||||
delegate_->update(this, duration, has_skipped_);
|
||||
}
|
||||
has_skipped_ = false;
|
||||
}
|
||||
@ -70,8 +68,3 @@ void BestEffortUpdater::set_delegate(Delegate *const delegate) {
|
||||
});
|
||||
}
|
||||
|
||||
void BestEffortUpdater::set_clock_rate(const double clock_rate) {
|
||||
async_task_queue_.enqueue([this, clock_rate]() {
|
||||
this->clock_rate_ = clock_rate;
|
||||
});
|
||||
}
|
||||
|
@ -13,6 +13,7 @@
|
||||
#include <chrono>
|
||||
|
||||
#include "AsyncTaskQueue.hpp"
|
||||
#include "TimeTypes.hpp"
|
||||
|
||||
namespace Concurrency {
|
||||
|
||||
@ -30,15 +31,12 @@ class BestEffortUpdater {
|
||||
|
||||
/// A delegate receives timing cues.
|
||||
struct Delegate {
|
||||
virtual void update(BestEffortUpdater *updater, int cycles, bool did_skip_previous_update) = 0;
|
||||
virtual void update(BestEffortUpdater *updater, Time::Seconds duration, bool did_skip_previous_update) = 0;
|
||||
};
|
||||
|
||||
/// Sets the current delegate.
|
||||
void set_delegate(Delegate *);
|
||||
|
||||
/// Sets the clock rate of the delegate.
|
||||
void set_clock_rate(double clock_rate);
|
||||
|
||||
/*!
|
||||
If the delegate is not currently in the process of an `update` call, calls it now to catch up to the current time.
|
||||
The call is asynchronous; this method will return immediately.
|
||||
@ -54,11 +52,9 @@ class BestEffortUpdater {
|
||||
|
||||
std::chrono::time_point<std::chrono::high_resolution_clock> previous_time_point_;
|
||||
bool has_previous_time_point_ = false;
|
||||
double error_ = 0.0;
|
||||
bool has_skipped_ = false;
|
||||
|
||||
Delegate *delegate_ = nullptr;
|
||||
double clock_rate_ = 1.0;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -12,8 +12,11 @@
|
||||
#include "../Outputs/CRT/CRT.hpp"
|
||||
#include "../Outputs/Speaker/Speaker.hpp"
|
||||
#include "../ClockReceiver/ClockReceiver.hpp"
|
||||
#include "../ClockReceiver/TimeTypes.hpp"
|
||||
#include "ROMMachine.hpp"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
namespace CRTMachine {
|
||||
|
||||
/*!
|
||||
@ -41,45 +44,30 @@ class Machine: public ROMMachine::Machine {
|
||||
/// @returns The speaker that receives this machine's output, or @c nullptr if this machine is mute.
|
||||
virtual Outputs::Speaker::Speaker *get_speaker() = 0;
|
||||
|
||||
/// Runs the machine for @c cycles.
|
||||
virtual void run_for(const Cycles cycles) = 0;
|
||||
|
||||
/// @returns The confidence that this machine is running content it understands.
|
||||
virtual float get_confidence() { return 0.5f; }
|
||||
virtual void print_type() {}
|
||||
|
||||
// TODO: sever the clock-rate stuff.
|
||||
virtual double get_clock_rate() {
|
||||
return clock_rate_;
|
||||
/// Runs the machine for @c duration seconds.
|
||||
virtual void run_for(Time::Seconds duration) {
|
||||
const double cycles = (duration * clock_rate_) + clock_conversion_error_;
|
||||
clock_conversion_error_ = std::fmod(cycles, 1.0);
|
||||
run_for(Cycles(static_cast<int>(cycles)));
|
||||
}
|
||||
virtual bool get_clock_is_unlimited() {
|
||||
return clock_is_unlimited_;
|
||||
}
|
||||
class Delegate {
|
||||
public:
|
||||
virtual void machine_did_change_clock_rate(Machine *machine) = 0;
|
||||
virtual void machine_did_change_clock_is_unlimited(Machine *machine) = 0;
|
||||
};
|
||||
virtual void set_delegate(Delegate *delegate) { delegate_ = delegate; }
|
||||
|
||||
protected:
|
||||
/// Runs the machine for @c cycles.
|
||||
virtual void run_for(const Cycles cycles) = 0;
|
||||
void set_clock_rate(double clock_rate) {
|
||||
if(clock_rate_ != clock_rate) {
|
||||
clock_rate_ = clock_rate;
|
||||
if(delegate_) delegate_->machine_did_change_clock_rate(this);
|
||||
}
|
||||
clock_rate_ = clock_rate;
|
||||
}
|
||||
void set_clock_is_unlimited(bool clock_is_unlimited) {
|
||||
if(clock_is_unlimited != clock_is_unlimited_) {
|
||||
clock_is_unlimited_ = clock_is_unlimited;
|
||||
if(delegate_) delegate_->machine_did_change_clock_is_unlimited(this);
|
||||
}
|
||||
double get_clock_rate() {
|
||||
return clock_rate_;
|
||||
}
|
||||
|
||||
private:
|
||||
Delegate *delegate_ = nullptr;
|
||||
double clock_rate_ = 1.0;
|
||||
bool clock_is_unlimited_ = false;
|
||||
double clock_conversion_error_ = 0.0;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -780,6 +780,7 @@
|
||||
4B448E801F1C45A00009ABD6 /* TZX.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TZX.hpp; sourceTree = "<group>"; };
|
||||
4B448E821F1C4C480009ABD6 /* PulseQueuedTape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PulseQueuedTape.cpp; sourceTree = "<group>"; };
|
||||
4B448E831F1C4C480009ABD6 /* PulseQueuedTape.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = PulseQueuedTape.hpp; sourceTree = "<group>"; };
|
||||
4B449C942063389900A095C8 /* TimeTypes.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = TimeTypes.hpp; sourceTree = "<group>"; };
|
||||
4B44EBF41DC987AE00A7820C /* AllSuiteA.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; name = AllSuiteA.bin; path = AllSuiteA/AllSuiteA.bin; sourceTree = "<group>"; };
|
||||
4B44EBF61DC9883B00A7820C /* 6502_functional_test.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; name = 6502_functional_test.bin; path = "Klaus Dormann/6502_functional_test.bin"; sourceTree = "<group>"; };
|
||||
4B44EBF81DC9898E00A7820C /* BCDTEST_beeb */ = {isa = PBXFileReference; lastKnownFileType = file; name = BCDTEST_beeb; path = BCDTest/BCDTEST_beeb; sourceTree = "<group>"; };
|
||||
@ -2972,6 +2973,7 @@
|
||||
4BF6606A1F281573002CB053 /* ClockReceiver.hpp */,
|
||||
4BB06B211F316A3F00600C7A /* ForceInline.hpp */,
|
||||
4BB146C61F49D7D700253439 /* Sleeper.hpp */,
|
||||
4B449C942063389900A095C8 /* TimeTypes.hpp */,
|
||||
);
|
||||
name = ClockReceiver;
|
||||
path = ../../ClockReceiver;
|
||||
|
@ -64,7 +64,7 @@ class MachineDocument:
|
||||
self.openGLView.delegate = self
|
||||
self.openGLView.responderDelegate = self
|
||||
|
||||
setupClockRate()
|
||||
setupAudioQueueClockRate()
|
||||
self.optionsPanel?.establishStoredOptions()
|
||||
|
||||
// bring OpenGL view-holding window on top of the options panel
|
||||
@ -74,17 +74,11 @@ class MachineDocument:
|
||||
self.bestEffortUpdater!.delegate = self
|
||||
}
|
||||
|
||||
func machineDidChangeClockRate(_ machine: CSMachine!) {
|
||||
setupClockRate()
|
||||
func machineSpeakerDidChangeInputClock(_ machine: CSMachine!) {
|
||||
setupAudioQueueClockRate()
|
||||
}
|
||||
|
||||
func machineDidChangeClockIsUnlimited(_ machine: CSMachine!) {
|
||||
bestEffortLock.lock()
|
||||
self.bestEffortUpdater?.runAsUnlimited = machine.clockIsUnlimited
|
||||
bestEffortLock.unlock()
|
||||
}
|
||||
|
||||
fileprivate func setupClockRate() {
|
||||
fileprivate func setupAudioQueueClockRate() {
|
||||
// establish and provide the audio queue, taking advice as to an appropriate sampling rate
|
||||
let maximumSamplingRate = CSAudioQueue.preferredSamplingRate()
|
||||
let selectedSamplingRate = self.machine.idealSamplingRate(from: NSRange(location: 0, length: NSInteger(maximumSamplingRate)))
|
||||
@ -94,10 +88,6 @@ class MachineDocument:
|
||||
self.machine.audioQueue = self.audioQueue
|
||||
self.machine.setAudioSamplingRate(selectedSamplingRate, bufferSize:audioQueue.preferredBufferSize)
|
||||
}
|
||||
|
||||
bestEffortLock.lock()
|
||||
self.bestEffortUpdater?.clockRate = self.machine.clockRate
|
||||
bestEffortLock.unlock()
|
||||
}
|
||||
|
||||
override func close() {
|
||||
@ -152,21 +142,10 @@ class MachineDocument:
|
||||
}
|
||||
|
||||
// MARK: CSBestEffortUpdaterDelegate
|
||||
final func bestEffortUpdater(_ bestEffortUpdater: CSBestEffortUpdater!, runForCycles cycles: UInt, didSkipPreviousUpdate: Bool) {
|
||||
runForNumberOfCycles(Int32(cycles))
|
||||
}
|
||||
|
||||
func runForNumberOfCycles(_ numberOfCycles: Int32) {
|
||||
bestEffortLock.lock()
|
||||
if let bestEffortUpdater = bestEffortUpdater {
|
||||
bestEffortLock.unlock()
|
||||
let cyclesToRunFor = min(numberOfCycles, Int32(bestEffortUpdater.clockRate / 10))
|
||||
if actionLock.try() {
|
||||
self.machine.runForNumber(ofCycles: cyclesToRunFor)
|
||||
actionLock.unlock()
|
||||
}
|
||||
} else {
|
||||
bestEffortLock.unlock()
|
||||
final func bestEffortUpdater(_ bestEffortUpdater: CSBestEffortUpdater!, runForInterval duration: TimeInterval, didSkipPreviousUpdate: Bool) {
|
||||
if actionLock.try() {
|
||||
self.machine.run(forInterval: duration)
|
||||
actionLock.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,8 +15,7 @@
|
||||
|
||||
@class CSMachine;
|
||||
@protocol CSMachineDelegate
|
||||
- (void)machineDidChangeClockRate:(CSMachine *)machine;
|
||||
- (void)machineDidChangeClockIsUnlimited:(CSMachine *)machine;
|
||||
- (void)machineSpeakerDidChangeInputClock:(CSMachine *)machine;
|
||||
@end
|
||||
|
||||
// Deliberately low; to ensure CSMachine has been declared as an @class already.
|
||||
@ -33,7 +32,7 @@
|
||||
*/
|
||||
- (instancetype)initWithAnalyser:(CSStaticAnalyser *)result NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
- (void)runForNumberOfCycles:(int)numberOfCycles;
|
||||
- (void)runForInterval:(NSTimeInterval)interval;
|
||||
|
||||
- (float)idealSamplingRateFromRange:(NSRange)range;
|
||||
- (void)setAudioSamplingRate:(float)samplingRate bufferSize:(NSUInteger)bufferSize;
|
||||
@ -48,9 +47,6 @@
|
||||
@property (nonatomic, readonly) CSOpenGLView *view;
|
||||
@property (nonatomic, weak) id<CSMachineDelegate> delegate;
|
||||
|
||||
@property (nonatomic, readonly) double clockRate;
|
||||
@property (nonatomic, readonly) BOOL clockIsUnlimited;
|
||||
|
||||
@property (nonatomic, readonly) NSString *userDefaultsPrefix;
|
||||
|
||||
- (void)paste:(NSString *)string;
|
||||
|
@ -25,8 +25,7 @@
|
||||
|
||||
@interface CSMachine() <CSFastLoading>
|
||||
- (void)speaker:(Outputs::Speaker::Speaker *)speaker didCompleteSamples:(const int16_t *)samples length:(int)length;
|
||||
- (void)machineDidChangeClockRate;
|
||||
- (void)machineDidChangeClockIsUnlimited;
|
||||
- (void)speakerDidChangeInputClock:(Outputs::Speaker::Speaker *)speaker;
|
||||
@end
|
||||
|
||||
struct LockProtectedDelegate {
|
||||
@ -37,29 +36,20 @@ struct LockProtectedDelegate {
|
||||
};
|
||||
|
||||
struct SpeakerDelegate: public Outputs::Speaker::Speaker::Delegate, public LockProtectedDelegate {
|
||||
void speaker_did_complete_samples(Outputs::Speaker::Speaker *speaker, const std::vector<int16_t> &buffer) {
|
||||
void speaker_did_complete_samples(Outputs::Speaker::Speaker *speaker, const std::vector<int16_t> &buffer) override {
|
||||
[machineAccessLock lock];
|
||||
[machine speaker:speaker didCompleteSamples:buffer.data() length:(int)buffer.size()];
|
||||
[machineAccessLock unlock];
|
||||
}
|
||||
};
|
||||
|
||||
struct MachineDelegate: CRTMachine::Machine::Delegate, public LockProtectedDelegate {
|
||||
void machine_did_change_clock_rate(CRTMachine::Machine *sender) {
|
||||
void speaker_did_change_input_clock(Outputs::Speaker::Speaker *speaker) override {
|
||||
[machineAccessLock lock];
|
||||
[machine machineDidChangeClockRate];
|
||||
[machineAccessLock unlock];
|
||||
}
|
||||
void machine_did_change_clock_is_unlimited(CRTMachine::Machine *sender) {
|
||||
[machineAccessLock lock];
|
||||
[machine machineDidChangeClockIsUnlimited];
|
||||
[machine speakerDidChangeInputClock:speaker];
|
||||
[machineAccessLock unlock];
|
||||
}
|
||||
};
|
||||
|
||||
@implementation CSMachine {
|
||||
SpeakerDelegate _speakerDelegate;
|
||||
MachineDelegate _machineDelegate;
|
||||
NSLock *_delegateMachineAccessLock;
|
||||
|
||||
CSStaticAnalyser *_analyser;
|
||||
@ -77,12 +67,8 @@ struct MachineDelegate: CRTMachine::Machine::Delegate, public LockProtectedDeleg
|
||||
|
||||
_delegateMachineAccessLock = [[NSLock alloc] init];
|
||||
|
||||
_machineDelegate.machine = self;
|
||||
_speakerDelegate.machine = self;
|
||||
_machineDelegate.machineAccessLock = _delegateMachineAccessLock;
|
||||
_speakerDelegate.machineAccessLock = _delegateMachineAccessLock;
|
||||
|
||||
_machine->crt_machine()->set_delegate(&_machineDelegate);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@ -91,12 +77,8 @@ struct MachineDelegate: CRTMachine::Machine::Delegate, public LockProtectedDeleg
|
||||
[self.audioQueue enqueueAudioBuffer:samples numberOfSamples:(unsigned int)length];
|
||||
}
|
||||
|
||||
- (void)machineDidChangeClockRate {
|
||||
[self.delegate machineDidChangeClockRate:self];
|
||||
}
|
||||
|
||||
- (void)machineDidChangeClockIsUnlimited {
|
||||
[self.delegate machineDidChangeClockIsUnlimited:self];
|
||||
- (void)speakerDidChangeInputClock:(Outputs::Speaker::Speaker *)speaker {
|
||||
[self.delegate machineSpeakerDidChangeInputClock:self];
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
@ -107,7 +89,6 @@ struct MachineDelegate: CRTMachine::Machine::Delegate, public LockProtectedDeleg
|
||||
// They are nilled inside an explicit lock because that allows the delegates to protect their entire
|
||||
// call into the machine, not just the pointer access.
|
||||
[_delegateMachineAccessLock lock];
|
||||
_machineDelegate.machine = nil;
|
||||
_speakerDelegate.machine = nil;
|
||||
[_delegateMachineAccessLock unlock];
|
||||
|
||||
@ -146,9 +127,9 @@ struct MachineDelegate: CRTMachine::Machine::Delegate, public LockProtectedDeleg
|
||||
}
|
||||
}
|
||||
|
||||
- (void)runForNumberOfCycles:(int)numberOfCycles {
|
||||
- (void)runForInterval:(NSTimeInterval)interval {
|
||||
@synchronized(self) {
|
||||
_machine->crt_machine()->run_for(Cycles(numberOfCycles));
|
||||
_machine->crt_machine()->run_for(interval);
|
||||
}
|
||||
}
|
||||
|
||||
@ -171,14 +152,6 @@ struct MachineDelegate: CRTMachine::Machine::Delegate, public LockProtectedDeleg
|
||||
_machine->crt_machine()->get_crt()->draw_frame((unsigned int)pixelSize.width, (unsigned int)pixelSize.height, onlyIfDirty ? true : false);
|
||||
}
|
||||
|
||||
- (double)clockRate {
|
||||
return _machine->crt_machine()->get_clock_rate();
|
||||
}
|
||||
|
||||
- (BOOL)clockIsUnlimited {
|
||||
return _machine->crt_machine()->get_clock_is_unlimited() ? YES : NO;
|
||||
}
|
||||
|
||||
- (void)paste:(NSString *)paste {
|
||||
KeyboardMachine::Machine *keyboardMachine = _machine->keyboard_machine();
|
||||
if(keyboardMachine)
|
||||
|
@ -13,15 +13,13 @@
|
||||
|
||||
@protocol CSBestEffortUpdaterDelegate <NSObject>
|
||||
|
||||
- (void)bestEffortUpdater:(CSBestEffortUpdater *)bestEffortUpdater runForCycles:(NSUInteger)cycles didSkipPreviousUpdate:(BOOL)didSkipPreviousUpdate;
|
||||
- (void)bestEffortUpdater:(CSBestEffortUpdater *)bestEffortUpdater runForInterval:(NSTimeInterval)interval didSkipPreviousUpdate:(BOOL)didSkipPreviousUpdate;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface CSBestEffortUpdater : NSObject
|
||||
|
||||
@property (nonatomic, assign) double clockRate;
|
||||
@property (nonatomic, assign) BOOL runAsUnlimited;
|
||||
@property (nonatomic, weak) id<CSBestEffortUpdaterDelegate> delegate;
|
||||
|
||||
- (void)update;
|
||||
|
@ -14,12 +14,12 @@ struct UpdaterDelegate: public Concurrency::BestEffortUpdater::Delegate {
|
||||
__weak id<CSBestEffortUpdaterDelegate> delegate;
|
||||
NSLock *delegateLock;
|
||||
|
||||
void update(Concurrency::BestEffortUpdater *updater, int cycles, bool did_skip_previous_update) {
|
||||
void update(Concurrency::BestEffortUpdater *updater, Time::Seconds cycles, bool did_skip_previous_update) {
|
||||
[delegateLock lock];
|
||||
__weak id<CSBestEffortUpdaterDelegate> delegateCopy = delegate;
|
||||
[delegateLock unlock];
|
||||
|
||||
[delegateCopy bestEffortUpdater:nil runForCycles:(NSUInteger)cycles didSkipPreviousUpdate:did_skip_previous_update];
|
||||
[delegateCopy bestEffortUpdater:nil runForInterval:(NSTimeInterval)cycles didSkipPreviousUpdate:did_skip_previous_update];
|
||||
}
|
||||
};
|
||||
|
||||
@ -51,11 +51,6 @@ struct UpdaterDelegate: public Concurrency::BestEffortUpdater::Delegate {
|
||||
_updater.flush();
|
||||
}
|
||||
|
||||
- (void)setClockRate:(double)clockRate {
|
||||
_clockRate = clockRate;
|
||||
_updater.set_clock_rate(clockRate);
|
||||
}
|
||||
|
||||
- (void)setDelegate:(id<CSBestEffortUpdaterDelegate>)delegate {
|
||||
[_delegateLock lock];
|
||||
_updaterDelegate.delegate = delegate;
|
||||
|
@ -23,20 +23,9 @@
|
||||
|
||||
namespace {
|
||||
|
||||
struct CRTMachineDelegate: public CRTMachine::Machine::Delegate {
|
||||
void machine_did_change_clock_rate(CRTMachine::Machine *machine) {
|
||||
best_effort_updater->set_clock_rate(machine->get_clock_rate());
|
||||
}
|
||||
|
||||
void machine_did_change_clock_is_unlimited(CRTMachine::Machine *machine) {
|
||||
}
|
||||
|
||||
Concurrency::BestEffortUpdater *best_effort_updater;
|
||||
};
|
||||
|
||||
struct BestEffortUpdaterDelegate: public Concurrency::BestEffortUpdater::Delegate {
|
||||
void update(Concurrency::BestEffortUpdater *updater, int cycles, bool did_skip_previous_update) {
|
||||
machine->crt_machine()->run_for(Cycles(cycles));
|
||||
void update(Concurrency::BestEffortUpdater *updater, Time::Seconds duration, bool did_skip_previous_update) override {
|
||||
machine->crt_machine()->run_for(duration);
|
||||
}
|
||||
|
||||
Machine::DynamicMachine *machine;
|
||||
@ -46,7 +35,7 @@ struct BestEffortUpdaterDelegate: public Concurrency::BestEffortUpdater::Delegat
|
||||
struct SpeakerDelegate: public Outputs::Speaker::Speaker::Delegate {
|
||||
static const int buffer_size = 1024;
|
||||
|
||||
void speaker_did_complete_samples(Outputs::Speaker::Speaker *speaker, const std::vector<int16_t> &buffer) {
|
||||
void speaker_did_complete_samples(Outputs::Speaker::Speaker *speaker, const std::vector<int16_t> &buffer) override {
|
||||
std::lock_guard<std::mutex> lock_guard(audio_buffer_mutex_);
|
||||
if(audio_buffer_.size() > buffer_size) {
|
||||
audio_buffer_.erase(audio_buffer_.begin(), audio_buffer_.end() - buffer_size);
|
||||
@ -258,7 +247,6 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
Concurrency::BestEffortUpdater updater;
|
||||
BestEffortUpdaterDelegate best_effort_updater_delegate;
|
||||
CRTMachineDelegate crt_delegate;
|
||||
SpeakerDelegate speaker_delegate;
|
||||
|
||||
// For vanilla SDL purposes, assume system ROMs can be found in one of:
|
||||
@ -321,12 +309,8 @@ int main(int argc, char *argv[]) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
updater.set_clock_rate(machine->crt_machine()->get_clock_rate());
|
||||
crt_delegate.best_effort_updater = &updater;
|
||||
best_effort_updater_delegate.machine = machine.get();
|
||||
speaker_delegate.updater = &updater;
|
||||
|
||||
machine->crt_machine()->set_delegate(&crt_delegate);
|
||||
updater.set_delegate(&best_effort_updater_delegate);
|
||||
|
||||
// Attempt to set up video and audio.
|
||||
|
@ -15,6 +15,7 @@
|
||||
#include "../../../ClockReceiver/ClockReceiver.hpp"
|
||||
#include "../../../Concurrency/AsyncTaskQueue.hpp"
|
||||
|
||||
#include <mutex>
|
||||
#include <cstring>
|
||||
|
||||
namespace Outputs {
|
||||
@ -34,6 +35,8 @@ template <typename T> class LowpassSpeaker: public Speaker {
|
||||
|
||||
// Implemented as per Speaker.
|
||||
float get_ideal_clock_rate_in_range(float minimum, float maximum) {
|
||||
std::lock_guard<std::recursive_mutex> lock_guard(filter_parameters_mutex_);
|
||||
|
||||
// return twice the cut off, if applicable
|
||||
if( filter_parameters_.high_frequency_cutoff > 0.0f &&
|
||||
filter_parameters_.input_cycles_per_second >= filter_parameters_.high_frequency_cutoff * 3.0f &&
|
||||
@ -55,6 +58,7 @@ template <typename T> class LowpassSpeaker: public Speaker {
|
||||
|
||||
// Implemented as per Speaker.
|
||||
void set_output_rate(float cycles_per_second, int buffer_size) {
|
||||
std::lock_guard<std::recursive_mutex> lock_guard(filter_parameters_mutex_);
|
||||
filter_parameters_.output_cycles_per_second = cycles_per_second;
|
||||
filter_parameters_.parameters_are_dirty = true;
|
||||
output_buffer_.resize(static_cast<std::size_t>(buffer_size));
|
||||
@ -64,8 +68,10 @@ template <typename T> class LowpassSpeaker: public Speaker {
|
||||
Sets the clock rate of the input audio.
|
||||
*/
|
||||
void set_input_rate(float cycles_per_second) {
|
||||
std::lock_guard<std::recursive_mutex> lock_guard(filter_parameters_mutex_);
|
||||
filter_parameters_.input_cycles_per_second = cycles_per_second;
|
||||
filter_parameters_.parameters_are_dirty = true;
|
||||
filter_parameters_.input_rate_changed = true;
|
||||
}
|
||||
|
||||
/*!
|
||||
@ -75,6 +81,7 @@ template <typename T> class LowpassSpeaker: public Speaker {
|
||||
path to be explicit about its effect, and get that simulation for free.
|
||||
*/
|
||||
void set_high_frequency_cutoff(float high_frequency) {
|
||||
std::lock_guard<std::recursive_mutex> lock_guard(filter_parameters_mutex_);
|
||||
filter_parameters_.high_frequency_cutoff = high_frequency;
|
||||
filter_parameters_.parameters_are_dirty = true;
|
||||
}
|
||||
@ -88,7 +95,13 @@ template <typename T> class LowpassSpeaker: public Speaker {
|
||||
|
||||
std::size_t cycles_remaining = static_cast<size_t>(cycles.as_int());
|
||||
if(!cycles_remaining) return;
|
||||
|
||||
std::lock_guard<std::recursive_mutex> lock_guard(filter_parameters_mutex_);
|
||||
if(filter_parameters_.parameters_are_dirty) update_filter_coefficients();
|
||||
if(filter_parameters_.input_rate_changed) {
|
||||
delegate_->speaker_did_change_input_clock(this);
|
||||
filter_parameters_.input_rate_changed = false;
|
||||
}
|
||||
|
||||
// If input and output rates exactly match, and no additional cut-off has been specified,
|
||||
// just accumulate results and pass on.
|
||||
@ -175,12 +188,14 @@ template <typename T> class LowpassSpeaker: public Speaker {
|
||||
std::unique_ptr<SignalProcessing::Stepper> stepper_;
|
||||
std::unique_ptr<SignalProcessing::FIRFilter> filter_;
|
||||
|
||||
std::recursive_mutex filter_parameters_mutex_;
|
||||
struct FilterParameters {
|
||||
float input_cycles_per_second = 0.0f;
|
||||
float output_cycles_per_second = 0.0f;
|
||||
float high_frequency_cutoff = -1.0;
|
||||
|
||||
bool parameters_are_dirty = true;
|
||||
bool input_rate_changed = false;
|
||||
} filter_parameters_;
|
||||
|
||||
void update_filter_coefficients() {
|
||||
|
@ -28,6 +28,7 @@ class Speaker {
|
||||
|
||||
struct Delegate {
|
||||
virtual void speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer) = 0;
|
||||
virtual void speaker_did_change_input_clock(Speaker *speaker) {}
|
||||
};
|
||||
virtual void set_delegate(Delegate *delegate) {
|
||||
delegate_ = delegate;
|
||||
|
Loading…
x
Reference in New Issue
Block a user