1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-11-26 08:49:37 +00:00

Adds new hook for watching audio output rate changes.

This commit is contained in:
Thomas Harte 2018-03-22 09:23:01 -04:00
parent da3d65c18f
commit 682c3d8079
6 changed files with 42 additions and 12 deletions

View File

@ -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) { void MultiSpeaker::set_new_front_machine(::Machine::DynamicMachine *machine) {
std::lock_guard<std::mutex> lock_guard(front_speaker_mutex_); std::lock_guard<std::mutex> lock_guard(front_speaker_mutex_);
front_speaker_ = machine->crt_machine()->get_speaker(); front_speaker_ = machine->crt_machine()->get_speaker();
if(delegate_) {
delegate_->speaker_did_change_input_clock(this);
}
} }

View File

@ -38,12 +38,13 @@ class MultiSpeaker: public Outputs::Speaker::Speaker, Outputs::Speaker::Speaker:
void set_new_front_machine(::Machine::DynamicMachine *machine); void set_new_front_machine(::Machine::DynamicMachine *machine);
// 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); float get_ideal_clock_rate_in_range(float minimum, float maximum) override;
void set_output_rate(float cycles_per_second, int buffer_size); void set_output_rate(float cycles_per_second, int buffer_size) override;
void set_delegate(Outputs::Speaker::Speaker::Delegate *delegate); void set_delegate(Outputs::Speaker::Speaker::Delegate *delegate) override;
private: 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); MultiSpeaker(const std::vector<Outputs::Speaker::Speaker *> &speakers);
std::vector<Outputs::Speaker::Speaker *> speakers_; std::vector<Outputs::Speaker::Speaker *> speakers_;

View File

@ -43,9 +43,6 @@
@property (nonatomic, strong) CSAudioQueue *audioQueue; @property (nonatomic, strong) CSAudioQueue *audioQueue;
@property (nonatomic, readonly) CSOpenGLView *view; @property (nonatomic, readonly) CSOpenGLView *view;
@property (nonatomic, readonly) double clockRate;
@property (nonatomic, readonly) BOOL clockIsUnlimited;
@property (nonatomic, readonly) NSString *userDefaultsPrefix; @property (nonatomic, readonly) NSString *userDefaultsPrefix;
- (void)paste:(NSString *)string; - (void)paste:(NSString *)string;

View File

@ -25,6 +25,7 @@
@interface CSMachine() <CSFastLoading> @interface CSMachine() <CSFastLoading>
- (void)speaker:(Outputs::Speaker::Speaker *)speaker didCompleteSamples:(const int16_t *)samples length:(int)length; - (void)speaker:(Outputs::Speaker::Speaker *)speaker didCompleteSamples:(const int16_t *)samples length:(int)length;
- (void)speakerDidChangeInputClock:(Outputs::Speaker::Speaker *)speaker;
@end @end
struct LockProtectedDelegate { struct LockProtectedDelegate {
@ -35,11 +36,16 @@ struct LockProtectedDelegate {
}; };
struct SpeakerDelegate: public Outputs::Speaker::Speaker::Delegate, public 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]; [machineAccessLock lock];
[machine speaker:speaker didCompleteSamples:buffer.data() length:(int)buffer.size()]; [machine speaker:speaker didCompleteSamples:buffer.data() length:(int)buffer.size()];
[machineAccessLock unlock]; [machineAccessLock unlock];
} }
void speaker_did_change_input_clock(Outputs::Speaker::Speaker *speaker) override {
[machineAccessLock lock];
[machine speakerDidChangeInputClock:speaker];
[machineAccessLock unlock];
}
}; };
@implementation CSMachine { @implementation CSMachine {
@ -71,6 +77,10 @@ struct SpeakerDelegate: public Outputs::Speaker::Speaker::Delegate, public LockP
[self.audioQueue enqueueAudioBuffer:samples numberOfSamples:(unsigned int)length]; [self.audioQueue enqueueAudioBuffer:samples numberOfSamples:(unsigned int)length];
} }
- (void)speakerDidChangeInputClock:(Outputs::Speaker::Speaker *)speaker {
// TODO: consider changing output audio queue rate.
}
- (void)dealloc { - (void)dealloc {
// The two delegate's references to this machine are nilled out here because close_output may result // The two delegate's references to this machine are nilled out here because close_output may result
// in a data flush, which might cause an audio callback, which could cause the audio queue to decide // in a data flush, which might cause an audio callback, which could cause the audio queue to decide
@ -142,10 +152,6 @@ struct SpeakerDelegate: public Outputs::Speaker::Speaker::Delegate, public LockP
_machine->crt_machine()->get_crt()->draw_frame((unsigned int)pixelSize.width, (unsigned int)pixelSize.height, onlyIfDirty ? true : false); _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();
}
- (void)paste:(NSString *)paste { - (void)paste:(NSString *)paste {
KeyboardMachine::Machine *keyboardMachine = _machine->keyboard_machine(); KeyboardMachine::Machine *keyboardMachine = _machine->keyboard_machine();
if(keyboardMachine) if(keyboardMachine)

View File

@ -15,6 +15,7 @@
#include "../../../ClockReceiver/ClockReceiver.hpp" #include "../../../ClockReceiver/ClockReceiver.hpp"
#include "../../../Concurrency/AsyncTaskQueue.hpp" #include "../../../Concurrency/AsyncTaskQueue.hpp"
#include <mutex>
#include <cstring> #include <cstring>
namespace Outputs { namespace Outputs {
@ -34,6 +35,8 @@ 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) {
std::lock_guard<std::recursive_mutex> lock_guard(filter_parameters_mutex_);
// return twice the cut off, if applicable // return twice the cut off, if applicable
if( filter_parameters_.high_frequency_cutoff > 0.0f && if( filter_parameters_.high_frequency_cutoff > 0.0f &&
filter_parameters_.input_cycles_per_second >= filter_parameters_.high_frequency_cutoff * 3.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. // Implemented as per Speaker.
void set_output_rate(float cycles_per_second, int buffer_size) { 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_.output_cycles_per_second = cycles_per_second;
filter_parameters_.parameters_are_dirty = true; filter_parameters_.parameters_are_dirty = true;
output_buffer_.resize(static_cast<std::size_t>(buffer_size)); 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. Sets the clock rate of the input audio.
*/ */
void set_input_rate(float cycles_per_second) { 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_.input_cycles_per_second = cycles_per_second;
filter_parameters_.parameters_are_dirty = true; 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. path to be explicit about its effect, and get that simulation for free.
*/ */
void set_high_frequency_cutoff(float high_frequency) { 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_.high_frequency_cutoff = high_frequency;
filter_parameters_.parameters_are_dirty = true; 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()); std::size_t cycles_remaining = static_cast<size_t>(cycles.as_int());
if(!cycles_remaining) return; 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_.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, // If input and output rates exactly match, and no additional cut-off has been specified,
// just accumulate results and pass on. // 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::Stepper> stepper_;
std::unique_ptr<SignalProcessing::FIRFilter> filter_; std::unique_ptr<SignalProcessing::FIRFilter> filter_;
std::recursive_mutex filter_parameters_mutex_;
struct FilterParameters { struct FilterParameters {
float input_cycles_per_second = 0.0f; float input_cycles_per_second = 0.0f;
float output_cycles_per_second = 0.0f; float output_cycles_per_second = 0.0f;
float high_frequency_cutoff = -1.0; float high_frequency_cutoff = -1.0;
bool parameters_are_dirty = true; bool parameters_are_dirty = true;
bool input_rate_changed = false;
} filter_parameters_; } filter_parameters_;
void update_filter_coefficients() { void update_filter_coefficients() {

View File

@ -28,6 +28,7 @@ class Speaker {
struct Delegate { struct Delegate {
virtual void speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer) = 0; 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) { virtual void set_delegate(Delegate *delegate) {
delegate_ = delegate; delegate_ = delegate;