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:
parent
da3d65c18f
commit
682c3d8079
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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_;
|
||||||
|
@ -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;
|
||||||
|
@ -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)
|
||||||
|
@ -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() {
|
||||||
|
@ -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;
|
||||||
|
Loading…
Reference in New Issue
Block a user