1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-12-25 03:32:01 +00:00

Makes some attempt at stereo support, with the Amstrad CPC being the test case.

This commit is contained in:
Thomas Harte 2020-02-15 18:55:19 -05:00
parent 89d6b85b83
commit e66a3523b6
3 changed files with 33 additions and 23 deletions

View File

@ -126,6 +126,7 @@ class AYDeferrer {
/// Constructs a new AY instance and sets its clock rate. /// Constructs a new AY instance and sets its clock rate.
AYDeferrer() : ay_(GI::AY38910::Personality::AY38910, audio_queue_), speaker_(ay_) { AYDeferrer() : ay_(GI::AY38910::Personality::AY38910, audio_queue_), speaker_(ay_) {
speaker_.set_input_rate(1000000); speaker_.set_input_rate(1000000);
ay_.set_output_mixing(true, 0.0, 0.5, 1.0, 1.0, 0.5, 0.0);
} }
~AYDeferrer() { ~AYDeferrer() {
@ -160,7 +161,7 @@ class AYDeferrer {
private: private:
Concurrency::DeferringAsyncTaskQueue audio_queue_; Concurrency::DeferringAsyncTaskQueue audio_queue_;
GI::AY38910::AY38910 ay_; GI::AY38910::AY38910 ay_;
Outputs::Speaker::LowpassSpeaker<GI::AY38910::AY38910, false> speaker_; Outputs::Speaker::LowpassSpeaker<GI::AY38910::AY38910, true> speaker_;
HalfCycles cycles_since_update_; HalfCycles cycles_since_update_;
}; };

View File

@ -66,7 +66,7 @@ template <typename SampleSource, bool is_stereo> class LowpassSpeaker: public Sp
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(std::size_t(buffer_size)); output_buffer_.resize(std::size_t(buffer_size) * (is_stereo ? 2 : 1);
} }
bool get_is_stereo() final { bool get_is_stereo() final {
@ -144,12 +144,11 @@ template <typename SampleSource, bool is_stereo> class LowpassSpeaker: public Sp
switch(conversion_) { switch(conversion_) {
case Conversion::Copy: case Conversion::Copy:
while(cycles_remaining) { 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_]); // Announce to delegate if full.
output_buffer_pointer_ += cycles_to_read;
// announce to delegate if full
if(output_buffer_pointer_ == output_buffer_.size()) { if(output_buffer_pointer_ == output_buffer_.size()) {
output_buffer_pointer_ = 0; output_buffer_pointer_ = 0;
did_complete_samples(this, output_buffer_); did_complete_samples(this, output_buffer_);
@ -161,14 +160,16 @@ template <typename SampleSource, bool is_stereo> class LowpassSpeaker: public Sp
case Conversion::ResampleSmaller: case Conversion::ResampleSmaller:
while(cycles_remaining) { 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_]); sample_source_.get_samples(cycles_to_read, &input_buffer_[input_buffer_depth_]);
cycles_remaining -= cycles_to_read; input_buffer_depth_ += cycles_to_read * (is_stereo ? 2 : 1);
input_buffer_depth_ += cycles_to_read;
if(input_buffer_depth_ == input_buffer_.size()) { if(input_buffer_depth_ == input_buffer_.size()) {
resample_input_buffer(); resample_input_buffer();
} }
cycles_remaining -= cycles_to_read;
} }
break; break;
@ -243,24 +244,31 @@ template <typename SampleSource, bool is_stereo> class LowpassSpeaker: public Sp
// that means nothing to do. // that means nothing to do.
default: break; default: break;
case Conversion::ResampleSmaller: case Conversion::ResampleSmaller: {
// Reize the input buffer only if absolutely necessary; if sizing downward // 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 // 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. // currently in the input buffer that hasn't yet been processed.
if(input_buffer_.size() != size_t(number_of_taps)) { const size_t required_buffer_size = size_t(number_of_taps) * (is_stereo ? 2 : 1);
if(input_buffer_depth_ >= size_t(number_of_taps)) { if(input_buffer_.size() != required_buffer_size) {
if(input_buffer_depth_ >= required_buffer_size) {
resample_input_buffer(); 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() { inline void resample_input_buffer() {
output_buffer_[output_buffer_pointer_] = filter_->apply(input_buffer_.data()); if constexpr (is_stereo) {
output_buffer_pointer_++; 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. // Announce to delegate if full.
if(output_buffer_pointer_ == output_buffer_.size()) { if(output_buffer_pointer_ == output_buffer_.size()) {
@ -279,8 +287,9 @@ template <typename SampleSource, bool is_stereo> class LowpassSpeaker: public Sp
sizeof(int16_t) * (input_buffer_.size() - steps)); sizeof(int16_t) * (input_buffer_.size() - steps));
input_buffer_depth_ -= steps; input_buffer_depth_ -= steps;
} else { } else {
if(steps > input_buffer_.size()) if(steps > input_buffer_.size()) {
sample_source_.skip_samples(steps - input_buffer_.size()); sample_source_.skip_samples((steps - input_buffer_.size()) / (is_stereo ? 2 : 1));
}
input_buffer_depth_ = 0; input_buffer_depth_ = 0;
} }
} }

View File

@ -49,15 +49,15 @@ class FIRFilter {
@param src The source buffer to apply the filter to. @param src The source buffer to apply the filter to.
@returns The result of applying the filter. @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__ #ifdef __APPLE__
short result; 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; return result;
#else #else
int outputValue = 0; int outputValue = 0;
for(std::size_t c = 0; c < filter_coefficients_.size(); ++c) { 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<short>(outputValue >> FixedShift); return static_cast<short>(outputValue >> FixedShift);
#endif #endif