From e66a3523b6564c1fcee1820d0a575b3dce162100 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 15 Feb 2020 18:55:19 -0500 Subject: [PATCH] Makes some attempt at stereo support, with the Amstrad CPC being the test case. --- Machines/AmstradCPC/AmstradCPC.cpp | 3 +- .../Speaker/Implementation/LowpassSpeaker.hpp | 47 +++++++++++-------- SignalProcessing/FIRFilter.hpp | 6 +-- 3 files changed, 33 insertions(+), 23 deletions(-) diff --git a/Machines/AmstradCPC/AmstradCPC.cpp b/Machines/AmstradCPC/AmstradCPC.cpp index 0c0b383f4..5d8dd8d4d 100644 --- a/Machines/AmstradCPC/AmstradCPC.cpp +++ b/Machines/AmstradCPC/AmstradCPC.cpp @@ -126,6 +126,7 @@ class AYDeferrer { /// Constructs a new AY instance and sets its clock rate. AYDeferrer() : ay_(GI::AY38910::Personality::AY38910, audio_queue_), speaker_(ay_) { speaker_.set_input_rate(1000000); + ay_.set_output_mixing(true, 0.0, 0.5, 1.0, 1.0, 0.5, 0.0); } ~AYDeferrer() { @@ -160,7 +161,7 @@ class AYDeferrer { private: Concurrency::DeferringAsyncTaskQueue audio_queue_; GI::AY38910::AY38910 ay_; - Outputs::Speaker::LowpassSpeaker speaker_; + Outputs::Speaker::LowpassSpeaker speaker_; HalfCycles cycles_since_update_; }; diff --git a/Outputs/Speaker/Implementation/LowpassSpeaker.hpp b/Outputs/Speaker/Implementation/LowpassSpeaker.hpp index bb1162e02..9c1056e5b 100644 --- a/Outputs/Speaker/Implementation/LowpassSpeaker.hpp +++ b/Outputs/Speaker/Implementation/LowpassSpeaker.hpp @@ -66,7 +66,7 @@ template class LowpassSpeaker: public Sp filter_parameters_.output_cycles_per_second = cycles_per_second; filter_parameters_.parameters_are_dirty = true; - output_buffer_.resize(std::size_t(buffer_size)); + output_buffer_.resize(std::size_t(buffer_size) * (is_stereo ? 2 : 1); } bool get_is_stereo() final { @@ -144,12 +144,11 @@ template class LowpassSpeaker: public Sp switch(conversion_) { case Conversion::Copy: while(cycles_remaining) { - const auto cycles_to_read = std::min(output_buffer_.size() - output_buffer_pointer_, cycles_remaining); + const auto cycles_to_read = std::min((output_buffer_.size() - output_buffer_pointer_) / (is_stereo ? 2 : 1), cycles_remaining); + sample_source_.get_samples(cycles_to_read, &output_buffer_[output_buffer_pointer_ ]); + output_buffer_pointer_ += cycles_to_read * (is_stereo ? 2 : 1); - sample_source_.get_samples(cycles_to_read, &output_buffer_[output_buffer_pointer_]); - output_buffer_pointer_ += cycles_to_read; - - // announce to delegate if full + // Announce to delegate if full. if(output_buffer_pointer_ == output_buffer_.size()) { output_buffer_pointer_ = 0; did_complete_samples(this, output_buffer_); @@ -161,14 +160,16 @@ template class LowpassSpeaker: public Sp case Conversion::ResampleSmaller: while(cycles_remaining) { - const auto cycles_to_read = std::min(cycles_remaining, input_buffer_.size() - input_buffer_depth_); + const auto cycles_to_read = std::min((input_buffer_.size() - input_buffer_depth_) / (is_stereo ? 2 : 1), cycles_remaining); + sample_source_.get_samples(cycles_to_read, &input_buffer_[input_buffer_depth_]); - cycles_remaining -= cycles_to_read; - input_buffer_depth_ += cycles_to_read; + input_buffer_depth_ += cycles_to_read * (is_stereo ? 2 : 1); if(input_buffer_depth_ == input_buffer_.size()) { resample_input_buffer(); } + + cycles_remaining -= cycles_to_read; } break; @@ -243,24 +244,31 @@ template class LowpassSpeaker: public Sp // that means nothing to do. default: break; - case Conversion::ResampleSmaller: + case Conversion::ResampleSmaller: { // Reize the input buffer only if absolutely necessary; if sizing downward // such that a sample would otherwise be lost then output it now. Keep anything // currently in the input buffer that hasn't yet been processed. - if(input_buffer_.size() != size_t(number_of_taps)) { - if(input_buffer_depth_ >= size_t(number_of_taps)) { + const size_t required_buffer_size = size_t(number_of_taps) * (is_stereo ? 2 : 1); + if(input_buffer_.size() != required_buffer_size) { + if(input_buffer_depth_ >= required_buffer_size) { resample_input_buffer(); - input_buffer_depth_ %= size_t(number_of_taps); + input_buffer_depth_ %= required_buffer_size; } - input_buffer_.resize(size_t(number_of_taps)); + input_buffer_.resize(required_buffer_size); } - break; + } break; } } inline void resample_input_buffer() { - output_buffer_[output_buffer_pointer_] = filter_->apply(input_buffer_.data()); - output_buffer_pointer_++; + if constexpr (is_stereo) { + output_buffer_[output_buffer_pointer_ + 0] = filter_->apply(input_buffer_.data(), 2); + output_buffer_[output_buffer_pointer_ + 1] = filter_->apply(input_buffer_.data() + 1, 2); + output_buffer_pointer_+= 2; + } else { + output_buffer_[output_buffer_pointer_] = filter_->apply(input_buffer_.data()); + output_buffer_pointer_++; + } // Announce to delegate if full. if(output_buffer_pointer_ == output_buffer_.size()) { @@ -279,8 +287,9 @@ template class LowpassSpeaker: public Sp sizeof(int16_t) * (input_buffer_.size() - steps)); input_buffer_depth_ -= steps; } else { - if(steps > input_buffer_.size()) - sample_source_.skip_samples(steps - input_buffer_.size()); + if(steps > input_buffer_.size()) { + sample_source_.skip_samples((steps - input_buffer_.size()) / (is_stereo ? 2 : 1)); + } input_buffer_depth_ = 0; } } diff --git a/SignalProcessing/FIRFilter.hpp b/SignalProcessing/FIRFilter.hpp index 8ee2d09bd..8fa69f164 100644 --- a/SignalProcessing/FIRFilter.hpp +++ b/SignalProcessing/FIRFilter.hpp @@ -49,15 +49,15 @@ class FIRFilter { @param src The source buffer to apply the filter to. @returns The result of applying the filter. */ - inline short apply(const short *src) const { + inline short apply(const short *src, size_t stride = 1) const { #ifdef __APPLE__ short result; - vDSP_dotpr_s1_15(filter_coefficients_.data(), 1, src, 1, &result, filter_coefficients_.size()); + vDSP_dotpr_s1_15(filter_coefficients_.data(), 1, src, stride, &result, filter_coefficients_.size()); return result; #else int outputValue = 0; for(std::size_t c = 0; c < filter_coefficients_.size(); ++c) { - outputValue += filter_coefficients_[c] * src[c]; + outputValue += filter_coefficients_[c] * src[c * stride]; } return static_cast(outputValue >> FixedShift); #endif