// // 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 #include #include #include #include #include #include #include #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 &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(static_cast(buffer_size)); set_needs_updated_filter_coefficients(); } void set_output_quality(int number_of_taps) { requested_number_of_taps_ = static_cast(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() : _queue(new Concurrency::AsyncTaskQueue) {} /*! Ensures any deferred processing occurs now. */ void flush() { if(!queued_functions_) return; std::shared_ptr>> queued_functions = queued_functions_; queued_functions_.reset(); _queue->enqueue([queued_functions] { for(auto &function : *queued_functions) { function(); } }); } protected: void enqueue(std::function function) { if(!queued_functions_) queued_functions_.reset(new std::list>); queued_functions_->push_back(function); } std::shared_ptr>> queued_functions_; std::vector buffer_in_progress_; float high_frequency_cut_off_ = -1.0; std::size_t buffer_in_progress_pointer_ = 0; std::size_t number_of_taps_; std::size_t requested_number_of_taps_ = 0; bool coefficients_are_dirty_; Delegate *delegate_ = nullptr; float input_cycles_per_second_ = 0.0f; float output_cycles_per_second_ = 0.0f; 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 _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 Filter: public Speaker { public: ~Filter() { _queue->flush(); } void run_for(const Cycles cycles) { enqueue([=]() { unsigned int cycles_remaining = static_cast(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 = static_cast(buffer_in_progress_.size() - static_cast(buffer_in_progress_pointer_)); if(cycles_to_read > cycles_remaining) cycles_to_read = cycles_remaining; static_cast(this)->get_samples(cycles_to_read, &buffer_in_progress_[static_cast(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 = static_cast(std::min(static_cast(cycles_remaining), number_of_taps_ - input_buffer_depth_)); static_cast(this)->get_samples(cycles_to_read, &input_buffer_[static_cast(input_buffer_depth_)]); cycles_remaining -= cycles_to_read; input_buffer_depth_ += cycles_to_read; if(input_buffer_depth_ == number_of_taps_) { buffer_in_progress_[static_cast(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) * (static_cast(number_of_taps_) - static_cast(steps))); input_buffer_depth_ -= steps; } else { if(steps > number_of_taps_) static_cast(this)->skip_samples(static_cast(steps) - static_cast(number_of_taps_)); input_buffer_depth_ = 0; } } } return; } // TODO: input rate is less than output rate }); } private: std::unique_ptr stepper_; std::unique_ptr filter_; std::vector input_buffer_; std::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_ = static_cast(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(static_cast(number_of_taps_), static_cast(input_cycles_per_second_), 0.0, high_pass_frequency, SignalProcessing::FIRFilter::DefaultAttenuation)); input_buffer_.resize(static_cast(number_of_taps_)); input_buffer_depth_ = 0; } }; } #endif /* Speaker_hpp */