diff --git a/Components/6560/6560.cpp b/Components/6560/6560.cpp index 20d86e063..ffe950b24 100644 --- a/Components/6560/6560.cpp +++ b/Components/6560/6560.cpp @@ -132,6 +132,9 @@ void AudioGenerator::skip_samples(std::size_t number_of_samples) { } } +void AudioGenerator::set_sample_volume_range(std::int16_t range) { +} + #undef shift #undef increment #undef update diff --git a/Components/6560/6560.hpp b/Components/6560/6560.hpp index 0d17b0c45..8cb672d8b 100644 --- a/Components/6560/6560.hpp +++ b/Components/6560/6560.hpp @@ -25,8 +25,10 @@ class AudioGenerator: public ::Outputs::Speaker::SampleSource { void set_volume(uint8_t volume); void set_control(int channel, uint8_t value); + // For ::SampleSource. void get_samples(std::size_t number_of_samples, int16_t *target); void skip_samples(std::size_t number_of_samples); + void set_sample_volume_range(std::int16_t range); private: Concurrency::DeferringAsyncTaskQueue &audio_queue_; diff --git a/Components/AY38910/AY38910.cpp b/Components/AY38910/AY38910.cpp index 62a7612fe..1c02aeea0 100644 --- a/Components/AY38910/AY38910.cpp +++ b/Components/AY38910/AY38910.cpp @@ -56,13 +56,18 @@ AY38910::AY38910(Concurrency::DeferringAsyncTaskQueue &task_queue) : task_queue_ } } + set_sample_volume_range(0); +} + +void AY38910::set_sample_volume_range(std::int16_t range) { // set up volume lookup table - float max_volume = 8192; - float root_two = sqrtf(2.0f); + const float max_volume = static_cast(range) / 3.0f; // As there are three channels. + const float root_two = sqrtf(2.0f); for(int v = 0; v < 16; v++) { volumes_[v] = static_cast(max_volume / powf(root_two, static_cast(v ^ 0xf))); } volumes_[0] = 0; + evaluate_output_volume(); } void AY38910::get_samples(std::size_t number_of_samples, int16_t *target) { diff --git a/Components/AY38910/AY38910.hpp b/Components/AY38910/AY38910.hpp index 1e86607ed..e619df699 100644 --- a/Components/AY38910/AY38910.hpp +++ b/Components/AY38910/AY38910.hpp @@ -86,6 +86,7 @@ class AY38910: public ::Outputs::Speaker::SampleSource { // to satisfy ::Outputs::Speaker (included via ::Outputs::Filter; not for public consumption void get_samples(std::size_t number_of_samples, int16_t *target); bool is_zero_level(); + void set_sample_volume_range(std::int16_t range); private: Concurrency::DeferringAsyncTaskQueue &task_queue_; diff --git a/Components/KonamiSCC/KonamiSCC.cpp b/Components/KonamiSCC/KonamiSCC.cpp index b841ecf90..91d9a2050 100644 --- a/Components/KonamiSCC/KonamiSCC.cpp +++ b/Components/KonamiSCC/KonamiSCC.cpp @@ -27,7 +27,7 @@ void SCC::get_samples(std::size_t number_of_samples, std::int16_t *target) { std::size_t c = 0; while((master_divider_&7) && c < number_of_samples) { - target[c] = output_volume_; + target[c] = transient_output_level_; master_divider_++; c++; } @@ -44,7 +44,7 @@ void SCC::get_samples(std::size_t number_of_samples, std::int16_t *target) { evaluate_output_volume(); for(int ic = 0; ic < 8 && c < number_of_samples; ++ic) { - target[c] = output_volume_; + target[c] = transient_output_level_; c++; master_divider_++; } @@ -86,18 +86,24 @@ void SCC::write(uint16_t address, uint8_t value) { } void SCC::evaluate_output_volume() { - output_volume_ = + transient_output_level_ = static_cast( - ( + (( (channel_enable_ & 0x01) ? static_cast(waves_[0].samples[channels_[0].offset]) * channels_[0].amplitude : 0 + (channel_enable_ & 0x02) ? static_cast(waves_[1].samples[channels_[1].offset]) * channels_[1].amplitude : 0 + (channel_enable_ & 0x04) ? static_cast(waves_[2].samples[channels_[2].offset]) * channels_[2].amplitude : 0 + (channel_enable_ & 0x08) ? static_cast(waves_[3].samples[channels_[3].offset]) * channels_[3].amplitude : 0 + (channel_enable_ & 0x10) ? static_cast(waves_[3].samples[channels_[4].offset]) * channels_[4].amplitude : 0 - ) + ) * master_volume_) / (255*15*5) + // Five channels, each with 8-bit samples and 4-bit volumes implies a natural range of 0 to 255*15*5. ); } +void SCC::set_sample_volume_range(std::int16_t range) { + master_volume_ = range; + evaluate_output_volume(); +} + uint8_t SCC::read(uint16_t address) { address &= 0xff; if(address < 0x80) { @@ -105,3 +111,5 @@ uint8_t SCC::read(uint16_t address) { } return 0xff; } + + diff --git a/Components/KonamiSCC/KonamiSCC.hpp b/Components/KonamiSCC/KonamiSCC.hpp index d7abc7eb0..6c156d1a8 100644 --- a/Components/KonamiSCC/KonamiSCC.hpp +++ b/Components/KonamiSCC/KonamiSCC.hpp @@ -31,6 +31,7 @@ class SCC: public ::Outputs::Speaker::SampleSource { /// As per ::SampleSource; provides audio output. void get_samples(std::size_t number_of_samples, std::int16_t *target); + void set_sample_volume_range(std::int16_t range); /// Writes to the SCC. void write(uint16_t address, uint8_t value); @@ -43,7 +44,8 @@ class SCC: public ::Outputs::Speaker::SampleSource { // State from here on down is accessed ony from the audio thread. int master_divider_ = 0; - int16_t output_volume_ = 0; + std::int16_t master_volume_ = 0; + int16_t transient_output_level_ = 0; struct Channel { int period = 0; diff --git a/Components/SN76489/SN76489.cpp b/Components/SN76489/SN76489.cpp index 1a5d09e4a..395f64574 100644 --- a/Components/SN76489/SN76489.cpp +++ b/Components/SN76489/SN76489.cpp @@ -14,15 +14,7 @@ using namespace TI; SN76489::SN76489(Personality personality, Concurrency::DeferringAsyncTaskQueue &task_queue, int additional_divider) : task_queue_(task_queue) { - // Build a volume table. - double multiplier = pow(10.0, -0.1); - double volume = 8191.0f; - for(int c = 0; c < 16; ++c) { - volumes_[c] = (int)round(volume); - volume *= multiplier; - } - volumes_[15] = 0; - evaluate_output_volume(); + set_sample_volume_range(0); switch(personality) { case Personality::SN76494: @@ -44,6 +36,18 @@ SN76489::SN76489(Personality personality, Concurrency::DeferringAsyncTaskQueue & master_divider_period_ /= additional_divider; } +void SN76489::set_sample_volume_range(std::int16_t range) { + // Build a volume table. + double multiplier = pow(10.0, -0.1); + double volume = static_cast(range) / 4.0f; // As there are four channels. + for(int c = 0; c < 16; ++c) { + volumes_[c] = (int)round(volume); + volume *= multiplier; + } + volumes_[15] = 0; + evaluate_output_volume(); +} + void SN76489::set_register(uint8_t value) { task_queue_.defer([value, this] () { if(value & 0x80) { diff --git a/Components/SN76489/SN76489.hpp b/Components/SN76489/SN76489.hpp index f87112ab0..a3e4884d5 100644 --- a/Components/SN76489/SN76489.hpp +++ b/Components/SN76489/SN76489.hpp @@ -31,6 +31,7 @@ class SN76489: public Outputs::Speaker::SampleSource { // As per SampleSource. void get_samples(std::size_t number_of_samples, std::int16_t *target); bool is_zero_level(); + void set_sample_volume_range(std::int16_t range); private: int master_divider_ = 0; diff --git a/Machines/Atari2600/TIASound.cpp b/Machines/Atari2600/TIASound.cpp index f0cbcf3c5..b22de8baf 100644 --- a/Machines/Atari2600/TIASound.cpp +++ b/Machines/Atari2600/TIASound.cpp @@ -119,7 +119,11 @@ void Atari2600::TIASound::get_samples(std::size_t number_of_samples, int16_t *ta break; } - target[c] += volume_[channel] * 1024 * level; + target[c] += (volume_[channel] * per_channel_volume_ * level) >> 4; } } } + +void Atari2600::TIASound::set_sample_volume_range(std::int16_t range) { + per_channel_volume_ = range / 2; +} diff --git a/Machines/Atari2600/TIASound.hpp b/Machines/Atari2600/TIASound.hpp index 25c782d19..a68b96aa4 100644 --- a/Machines/Atari2600/TIASound.hpp +++ b/Machines/Atari2600/TIASound.hpp @@ -26,7 +26,9 @@ class TIASound: public Outputs::Speaker::SampleSource { void set_divider(int channel, uint8_t divider); void set_control(int channel, uint8_t control); + // To satisfy ::SampleSource. void get_samples(std::size_t number_of_samples, int16_t *target); + void set_sample_volume_range(std::int16_t range); private: Concurrency::DeferringAsyncTaskQueue &audio_queue_; @@ -41,6 +43,7 @@ class TIASound: public Outputs::Speaker::SampleSource { int output_state_[2]; int divider_counter_[2]; + int16_t per_channel_volume_ = 0; }; } diff --git a/Machines/Electron/SoundGenerator.cpp b/Machines/Electron/SoundGenerator.cpp index 9a8a7ada4..18ddb3f12 100644 --- a/Machines/Electron/SoundGenerator.cpp +++ b/Machines/Electron/SoundGenerator.cpp @@ -15,10 +15,14 @@ using namespace Electron; SoundGenerator::SoundGenerator(Concurrency::DeferringAsyncTaskQueue &audio_queue) : audio_queue_(audio_queue) {} +void SoundGenerator::set_sample_volume_range(std::int16_t range) { + volume_ = static_cast(range / 2); +} + void SoundGenerator::get_samples(std::size_t number_of_samples, int16_t *target) { if(is_enabled_) { while(number_of_samples--) { - *target = static_cast((counter_ / (divider_+1)) * 8192); + *target = static_cast((counter_ / (divider_+1)) * volume_); target++; counter_ = (counter_ + 1) % ((divider_+1) * 2); } diff --git a/Machines/Electron/SoundGenerator.hpp b/Machines/Electron/SoundGenerator.hpp index 324d70a43..ffd4dbb9d 100644 --- a/Machines/Electron/SoundGenerator.hpp +++ b/Machines/Electron/SoundGenerator.hpp @@ -22,16 +22,19 @@ class SoundGenerator: public ::Outputs::Speaker::SampleSource { void set_is_enabled(bool is_enabled); + static const unsigned int clock_rate_divider = 8; + + // To satisfy ::SampleSource. void get_samples(std::size_t number_of_samples, int16_t *target); void skip_samples(std::size_t number_of_samples); - - static const unsigned int clock_rate_divider = 8; + void set_sample_volume_range(std::int16_t range); private: Concurrency::DeferringAsyncTaskQueue &audio_queue_; unsigned int counter_ = 0; unsigned int divider_ = 0; bool is_enabled_ = false; + unsigned int volume_ = 0; }; } diff --git a/Machines/MSX/MSX.cpp b/Machines/MSX/MSX.cpp index 8e589a102..a927c4cb5 100644 --- a/Machines/MSX/MSX.cpp +++ b/Machines/MSX/MSX.cpp @@ -62,13 +62,17 @@ class AudioToggle: public Outputs::Speaker::SampleSource { } } + void set_sample_volume_range(std::int16_t range) { + volume_ = range; + } + void skip_samples(const std::size_t number_of_samples) {} void set_output(bool enabled) { if(is_enabled_ == enabled) return; is_enabled_ = enabled; audio_queue_.defer([=] { - level_ = enabled ? 4096 : 0; + level_ = enabled ? volume_ : 0; }); } @@ -78,7 +82,7 @@ class AudioToggle: public Outputs::Speaker::SampleSource { private: bool is_enabled_ = false; - int16_t level_ = 0; + int16_t level_ = 0, volume_ = 0; Concurrency::DeferringAsyncTaskQueue &audio_queue_; }; diff --git a/Outputs/Speaker/Implementation/CompoundSource.hpp b/Outputs/Speaker/Implementation/CompoundSource.hpp index 5f29e095b..b082ad671 100644 --- a/Outputs/Speaker/Implementation/CompoundSource.hpp +++ b/Outputs/Speaker/Implementation/CompoundSource.hpp @@ -25,6 +25,12 @@ template class CompoundSource: public Outputs::Speaker::SampleSo void get_samples(std::size_t number_of_samples, std::int16_t *target) { std::memset(target, 0, sizeof(std::int16_t) * number_of_samples); } + + void set_scaled_volume_range(int16_t range) {} + + int size() { + return 0; + } }; /*! @@ -55,6 +61,19 @@ template class CompoundSource: next_source_.skip_samples(number_of_samples); } + void set_sample_volume_range(int16_t range) { + set_scaled_volume_range(range / size()); + } + + void set_scaled_volume_range(int16_t range) { + source_.set_sample_volume_range(range); + next_source_.set_scaled_volume_range(range); + } + + int size() { + return 1+next_source_.size(); + } + private: bool is_sleeping_ = false; T &source_; diff --git a/Outputs/Speaker/Implementation/LowpassSpeaker.hpp b/Outputs/Speaker/Implementation/LowpassSpeaker.hpp index 3ca42e477..b0b361c38 100644 --- a/Outputs/Speaker/Implementation/LowpassSpeaker.hpp +++ b/Outputs/Speaker/Implementation/LowpassSpeaker.hpp @@ -28,7 +28,9 @@ namespace Speaker { */ template class LowpassSpeaker: public Speaker { public: - LowpassSpeaker(T &sample_source) : sample_source_(sample_source) {} + LowpassSpeaker(T &sample_source) : sample_source_(sample_source) { + sample_source.set_sample_volume_range(32767); + } // Implemented as per Speaker. float get_ideal_clock_rate_in_range(float minimum, float maximum) { diff --git a/Outputs/Speaker/Implementation/SampleSource.hpp b/Outputs/Speaker/Implementation/SampleSource.hpp index 24e45544b..651ad3301 100644 --- a/Outputs/Speaker/Implementation/SampleSource.hpp +++ b/Outputs/Speaker/Implementation/SampleSource.hpp @@ -46,6 +46,13 @@ class SampleSource { bool is_zero_level() { return false; } + + /*! + Sets the proper output range for this sample source; it should write values + between 0 and volume. + */ + void set_sample_volume_range(std::int16_t volume) { + } }; }