1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-11-05 06:05:27 +00:00
CLK/Outputs/Speaker.hpp
2017-08-22 21:55:10 -04:00

254 lines
8.8 KiB
C++

//
// Speaker.hpp
// Clock Signal
//
// Created by Thomas Harte on 12/01/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#ifndef Speaker_hpp
#define Speaker_hpp
#include <cstdint>
#include <cstdio>
#include <ctime>
#include <memory>
#include <list>
#include <vector>
#include "../SignalProcessing/Stepper.hpp"
#include "../SignalProcessing/FIRFilter.hpp"
#include "../Concurrency/AsyncTaskQueue.hpp"
#include "../ClockReceiver/ClockReceiver.hpp"
namespace Outputs {
/*!
Provides the base class for an audio output source, with an input rate (the speed at which the source will
provide data), an output rate (the speed at which the destination will receive data), a delegate to receive
the output and some help for the output in picking an appropriate rate once the input rate is known.
Intended to be a parent class, allowing descendants to pick the strategy by which input samples are mapped to
output samples.
*/
class Speaker {
public:
class Delegate {
public:
virtual void speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer) = 0;
};
float get_ideal_clock_rate_in_range(float minimum, float maximum) {
// return twice the cut off, if applicable
if(high_frequency_cut_off_ > 0.0f && input_cycles_per_second_ >= high_frequency_cut_off_ * 3.0f && input_cycles_per_second_ <= high_frequency_cut_off_ * 3.0f) return high_frequency_cut_off_ * 3.0f;
// return exactly the input rate if possible
if(input_cycles_per_second_ >= minimum && input_cycles_per_second_ <= maximum) return input_cycles_per_second_;
// if the input rate is lower, return the minimum
if(input_cycles_per_second_ < minimum) return minimum;
// otherwise, return the maximum
return maximum;
}
void set_output_rate(float cycles_per_second, int buffer_size) {
output_cycles_per_second_ = cycles_per_second;
buffer_in_progress_.resize((size_t)buffer_size);
set_needs_updated_filter_coefficients();
}
void set_output_quality(int number_of_taps) {
requested_number_of_taps_ = (size_t)number_of_taps;
set_needs_updated_filter_coefficients();
}
void set_delegate(Delegate *delegate) {
delegate_ = delegate;
}
void set_input_rate(float cycles_per_second) {
input_cycles_per_second_ = cycles_per_second;
set_needs_updated_filter_coefficients();
}
/*!
Sets the cut-off frequency for a low-pass filter attached to the output of this speaker; optional.
*/
void set_high_frequency_cut_off(float high_frequency) {
high_frequency_cut_off_ = high_frequency;
set_needs_updated_filter_coefficients();
}
Speaker() : buffer_in_progress_pointer_(0), requested_number_of_taps_(0), high_frequency_cut_off_(-1.0), _queue(new Concurrency::AsyncTaskQueue) {}
/*!
Ensures any deferred processing occurs now.
*/
void flush() {
if(!queued_functions_) return;
std::shared_ptr<std::list<std::function<void(void)>>> queued_functions = queued_functions_;
queued_functions_.reset();
_queue->enqueue([queued_functions] {
for(auto &function : *queued_functions) {
function();
}
});
}
protected:
void enqueue(std::function<void(void)> function) {
if(!queued_functions_) queued_functions_.reset(new std::list<std::function<void(void)>>);
queued_functions_->push_back(function);
}
std::shared_ptr<std::list<std::function<void(void)>>> queued_functions_;
std::vector<int16_t> buffer_in_progress_;
float high_frequency_cut_off_;
size_t buffer_in_progress_pointer_;
size_t number_of_taps_, requested_number_of_taps_;
bool coefficients_are_dirty_;
Delegate *delegate_;
float input_cycles_per_second_, output_cycles_per_second_;
void set_needs_updated_filter_coefficients() {
coefficients_are_dirty_ = true;
}
void get_samples(unsigned int quantity, int16_t *target) {}
void skip_samples(unsigned int quantity) {
int16_t throwaway_samples[quantity];
get_samples(quantity, throwaway_samples);
}
std::unique_ptr<Concurrency::AsyncTaskQueue> _queue;
};
/*!
A concrete descendant of Speaker that uses a FIR filter to map from input data to output data when scaling
and a copy-through buffer when input and output rates are the same.
Audio sources should use @c Filter as both a template and a parent, implementing at least
`get_samples(unsigned int quantity, int16_t *target)` and ideally also `skip_samples(unsigned int quantity)`
to provide source data.
Call `run_for` to request that the next period of input data is collected.
*/
template <class T> class Filter: public Speaker {
public:
~Filter() {
_queue->flush();
}
void run_for(const Cycles cycles) {
enqueue([=]() {
unsigned int cycles_remaining = (unsigned int)cycles.as_int();
if(coefficients_are_dirty_) update_filter_coefficients();
// if input and output rates exactly match, just accumulate results and pass on
if(input_cycles_per_second_ == output_cycles_per_second_ && high_frequency_cut_off_ < 0.0) {
while(cycles_remaining) {
unsigned int cycles_to_read = (unsigned int)(buffer_in_progress_.size() - (size_t)buffer_in_progress_pointer_);
if(cycles_to_read > cycles_remaining) cycles_to_read = cycles_remaining;
static_cast<T *>(this)->get_samples(cycles_to_read, &buffer_in_progress_[(size_t)buffer_in_progress_pointer_]);
buffer_in_progress_pointer_ += cycles_to_read;
// announce to delegate if full
if(buffer_in_progress_pointer_ == buffer_in_progress_.size()) {
buffer_in_progress_pointer_ = 0;
if(delegate_) {
delegate_->speaker_did_complete_samples(this, buffer_in_progress_);
}
}
cycles_remaining -= cycles_to_read;
}
return;
}
// if the output rate is less than the input rate, use the filter
if(input_cycles_per_second_ > output_cycles_per_second_ || (input_cycles_per_second_ == output_cycles_per_second_ && high_frequency_cut_off_ >= 0.0)) {
while(cycles_remaining) {
unsigned int cycles_to_read = (unsigned int)std::min((size_t)cycles_remaining, number_of_taps_ - input_buffer_depth_);
static_cast<T *>(this)->get_samples(cycles_to_read, &input_buffer_[(size_t)input_buffer_depth_]);
cycles_remaining -= cycles_to_read;
input_buffer_depth_ += cycles_to_read;
if(input_buffer_depth_ == number_of_taps_) {
buffer_in_progress_[(size_t)buffer_in_progress_pointer_] = filter_->apply(input_buffer_.data());
buffer_in_progress_pointer_++;
// announce to delegate if full
if(buffer_in_progress_pointer_ == buffer_in_progress_.size()) {
buffer_in_progress_pointer_ = 0;
if(delegate_) {
delegate_->speaker_did_complete_samples(this, buffer_in_progress_);
}
}
// If the next loop around is going to reuse some of the samples just collected, use a memmove to
// preserve them in the correct locations (TODO: use a longer buffer to fix that) and don't skip
// anything. Otherwise skip as required to get to the next sample batch and don't expect to reuse.
uint64_t steps = stepper_->step();
if(steps < number_of_taps_) {
int16_t *input_buffer = input_buffer_.data();
memmove(input_buffer, &input_buffer[steps], sizeof(int16_t) * ((size_t)number_of_taps_ - (size_t)steps));
input_buffer_depth_ -= steps;
} else {
if(steps > number_of_taps_)
static_cast<T *>(this)->skip_samples((unsigned int)steps - (unsigned int)number_of_taps_);
input_buffer_depth_ = 0;
}
}
}
return;
}
// TODO: input rate is less than output rate
});
}
private:
std::unique_ptr<SignalProcessing::Stepper> stepper_;
std::unique_ptr<SignalProcessing::FIRFilter> filter_;
std::vector<int16_t> input_buffer_;
size_t input_buffer_depth_;
void update_filter_coefficients() {
// make a guess at a good number of taps if this hasn't been provided explicitly
if(requested_number_of_taps_) {
number_of_taps_ = requested_number_of_taps_;
} else {
number_of_taps_ = (size_t)ceilf((input_cycles_per_second_ + output_cycles_per_second_) / output_cycles_per_second_);
number_of_taps_ *= 2;
number_of_taps_ |= 1;
}
coefficients_are_dirty_ = false;
buffer_in_progress_pointer_ = 0;
stepper_.reset(new SignalProcessing::Stepper((uint64_t)input_cycles_per_second_, (uint64_t)output_cycles_per_second_));
float high_pass_frequency;
if(high_frequency_cut_off_ > 0.0) {
high_pass_frequency = std::min(output_cycles_per_second_ / 2.0f, high_frequency_cut_off_);
} else {
high_pass_frequency = output_cycles_per_second_ / 2.0f;
}
filter_.reset(new SignalProcessing::FIRFilter((unsigned int)number_of_taps_, (float)input_cycles_per_second_, 0.0, high_pass_frequency, SignalProcessing::FIRFilter::DefaultAttenuation));
input_buffer_.resize((size_t)number_of_taps_);
input_buffer_depth_ = 0;
}
};
}
#endif /* Speaker_hpp */