From 89d6b85b83bb641d93f279eb8bd12355a44cd743 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 15 Feb 2020 18:09:17 -0500 Subject: [PATCH] Adds optional stereo output for the AY. The real chip provides the three tone channels as separate outputs, so a variety of different mixings can exist. --- Components/AY38910/AY38910.cpp | 73 ++++++++++++++++++++++++++++------ Components/AY38910/AY38910.hpp | 26 +++++++++++- 2 files changed, 85 insertions(+), 14 deletions(-) diff --git a/Components/AY38910/AY38910.cpp b/Components/AY38910/AY38910.cpp index b789cc152..e8c38cda8 100644 --- a/Components/AY38910/AY38910.cpp +++ b/Components/AY38910/AY38910.cpp @@ -84,10 +84,33 @@ void AY38910::set_sample_volume_range(std::int16_t range) { for(int v = 31; v >= 0; --v) { volumes_[v] -= volumes_[0]; } - evaluate_output_volume(); + + if(is_stereo_) { + evaluate_output_volume(); + } else { + evaluate_output_volume(); + } +} + +void AY38910::set_output_mixing(bool is_stereo, float a_left, float b_left, float c_left, float a_right, float b_right, float c_right) { + is_stereo_ = is_stereo; + a_left_ = uint8_t(a_left * 255.0f); + b_left_ = uint8_t(b_left * 255.0f); + c_left_ = uint8_t(c_left * 255.0f); + a_right_ = uint8_t(a_right * 255.0f); + b_right_ = uint8_t(b_right * 255.0f); + c_right_ = uint8_t(c_right * 255.0f); } void AY38910::get_samples(std::size_t number_of_samples, int16_t *target) { + if(is_stereo_) { + get_samples(number_of_samples, target); + } else { + get_samples(number_of_samples, target); + } +} + +template void AY38910::get_samples(std::size_t number_of_samples, int16_t *target) { // Note on structure below: the real AY has a built-in divider of 8 // prior to applying its tone and noise dividers. But the YM fills the // same total periods for noise and tone with double-precision envelopes. @@ -99,7 +122,11 @@ void AY38910::get_samples(std::size_t number_of_samples, int16_t *target) { std::size_t c = 0; while((master_divider_&3) && c < number_of_samples) { - target[c] = output_volume_; + if constexpr (is_stereo) { + reinterpret_cast(target)[c] = output_volume_; + } else { + target[c] = output_volume_; + } master_divider_++; c++; } @@ -138,10 +165,14 @@ void AY38910::get_samples(std::size_t number_of_samples, int16_t *target) { if(envelope_position_ == 64) envelope_position_ = envelope_overflow_masks_[output_registers_[13]]; } - evaluate_output_volume(); + evaluate_output_volume(); for(int ic = 0; ic < 4 && c < number_of_samples; ic++) { - target[c] = output_volume_; + if constexpr (is_stereo) { + reinterpret_cast(target)[c] = output_volume_; + } else { + target[c] = output_volume_; + } c++; master_divider_++; } @@ -150,7 +181,7 @@ void AY38910::get_samples(std::size_t number_of_samples, int16_t *target) { master_divider_ &= 3; } -void AY38910::evaluate_output_volume() { +template void AY38910::evaluate_output_volume() { int envelope_volume = envelope_shapes_[output_registers_[13]][envelope_position_ | envelope_position_mask_]; // The output level for a channel is: @@ -190,12 +221,26 @@ void AY38910::evaluate_output_volume() { }; #undef channel_volume - // Mix additively. - output_volume_ = int16_t( - volumes_[volumes[0]] * channel_levels[0] + - volumes_[volumes[1]] * channel_levels[1] + - volumes_[volumes[2]] * channel_levels[2] - ); + // Mix additively, weighting if in stereo. + if constexpr (is_stereo) { + int16_t *const volumes = reinterpret_cast(&output_volume_); + volumes[0] = int16_t(( + volumes_[volumes[0]] * channel_levels[0] * a_left_ + + volumes_[volumes[1]] * channel_levels[1] * b_left_ + + volumes_[volumes[2]] * channel_levels[2] * c_left_ + ) >> 8); + volumes[1] = int16_t(( + volumes_[volumes[0]] * channel_levels[0] * a_right_ + + volumes_[volumes[1]] * channel_levels[1] * b_right_ + + volumes_[volumes[2]] * channel_levels[2] * c_right_ + ) >> 8); + } else { + output_volume_ = int16_t( + volumes_[volumes[0]] * channel_levels[0] + + volumes_[volumes[1]] * channel_levels[1] + + volumes_[volumes[2]] * channel_levels[2] + ); + } } bool AY38910::is_zero_level() { @@ -252,7 +297,11 @@ void AY38910::set_register_value(uint8_t value) { // Store a copy of the current register within the storage used by the audio generation // thread, and apply any changes to output volume. output_registers_[selected_register] = masked_value; - evaluate_output_volume(); + if(is_stereo_) { + evaluate_output_volume(); + } else { + evaluate_output_volume(); + } }); } diff --git a/Components/AY38910/AY38910.hpp b/Components/AY38910/AY38910.hpp index 2a1d777c4..9e6e4bd93 100644 --- a/Components/AY38910/AY38910.hpp +++ b/Components/AY38910/AY38910.hpp @@ -63,6 +63,8 @@ enum class Personality { Provides emulation of an AY-3-8910 / YM2149, which is a three-channel sound chip with a noise generator and a volume envelope generator, which also provides two bidirectional interface ports. + + This AY has an attached mono or stereo mixer. */ class AY38910: public ::Outputs::Speaker::SampleSource { public: @@ -91,6 +93,18 @@ class AY38910: public ::Outputs::Speaker::SampleSource { */ void set_port_handler(PortHandler *); + /*! + Enables or disables stereo output; if stereo output is enabled then also sets the weight of each of the AY's + channels in each of the output channels. + + If a_left_ = b_left = c_left = a_right = b_right = c_right = 1.0 then you'll get output that's effectively mono. + + a_left = 0.0, a_right = 1.0 will make A full volume on the right output, and silent on the left. + + a_left = 0.5, a_right = 0.5 will make A half volume on both outputs. + */ + void set_output_mixing(bool is_stereo, float a_left, float b_left, float c_left, float a_right, float b_right, float c_right); + // to satisfy ::Outputs::Speaker (included via ::Outputs::Filter. void get_samples(std::size_t number_of_samples, int16_t *target); bool is_zero_level(); @@ -135,12 +149,20 @@ class AY38910: public ::Outputs::Speaker::SampleSource { uint8_t data_input_, data_output_; - int16_t output_volume_; - void evaluate_output_volume(); + uint32_t output_volume_; void update_bus(); PortHandler *port_handler_ = nullptr; void set_port_output(bool port_b); + + template void get_samples(std::size_t number_of_samples, int16_t *target); + template void evaluate_output_volume(); + + // Output mixing control. + bool is_stereo_ = false; + uint8_t a_left_ = 255, a_right_ = 255; + uint8_t b_left_ = 255, b_right_ = 255; + uint8_t c_left_ = 255, c_right_ = 255; }; }