1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-07-17 13:29:02 +00:00

Merge pull request #367 from TomHarte/DynamicVolume

Introduces formal setting of the output volume to `SampleSource`.
This commit is contained in:
Thomas Harte 2018-03-09 14:10:55 -05:00 committed by GitHub
commit 719f5d79c2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 96 additions and 24 deletions

View File

@ -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 shift
#undef increment #undef increment
#undef update #undef update

View File

@ -25,8 +25,10 @@ class AudioGenerator: public ::Outputs::Speaker::SampleSource {
void set_volume(uint8_t volume); void set_volume(uint8_t volume);
void set_control(int channel, uint8_t value); void set_control(int channel, uint8_t value);
// For ::SampleSource.
void get_samples(std::size_t number_of_samples, int16_t *target); void get_samples(std::size_t number_of_samples, int16_t *target);
void skip_samples(std::size_t number_of_samples); void skip_samples(std::size_t number_of_samples);
void set_sample_volume_range(std::int16_t range);
private: private:
Concurrency::DeferringAsyncTaskQueue &audio_queue_; Concurrency::DeferringAsyncTaskQueue &audio_queue_;

View File

@ -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 // set up volume lookup table
float max_volume = 8192; const float max_volume = static_cast<float>(range) / 3.0f; // As there are three channels.
float root_two = sqrtf(2.0f); const float root_two = sqrtf(2.0f);
for(int v = 0; v < 16; v++) { for(int v = 0; v < 16; v++) {
volumes_[v] = static_cast<int>(max_volume / powf(root_two, static_cast<float>(v ^ 0xf))); volumes_[v] = static_cast<int>(max_volume / powf(root_two, static_cast<float>(v ^ 0xf)));
} }
volumes_[0] = 0; volumes_[0] = 0;
evaluate_output_volume();
} }
void AY38910::get_samples(std::size_t number_of_samples, int16_t *target) { void AY38910::get_samples(std::size_t number_of_samples, int16_t *target) {

View File

@ -86,6 +86,7 @@ class AY38910: public ::Outputs::Speaker::SampleSource {
// to satisfy ::Outputs::Speaker (included via ::Outputs::Filter; not for public consumption // to satisfy ::Outputs::Speaker (included via ::Outputs::Filter; not for public consumption
void get_samples(std::size_t number_of_samples, int16_t *target); void get_samples(std::size_t number_of_samples, int16_t *target);
bool is_zero_level(); bool is_zero_level();
void set_sample_volume_range(std::int16_t range);
private: private:
Concurrency::DeferringAsyncTaskQueue &task_queue_; Concurrency::DeferringAsyncTaskQueue &task_queue_;

View File

@ -27,7 +27,7 @@ void SCC::get_samples(std::size_t number_of_samples, std::int16_t *target) {
std::size_t c = 0; std::size_t c = 0;
while((master_divider_&7) && c < number_of_samples) { while((master_divider_&7) && c < number_of_samples) {
target[c] = output_volume_; target[c] = transient_output_level_;
master_divider_++; master_divider_++;
c++; c++;
} }
@ -44,7 +44,7 @@ void SCC::get_samples(std::size_t number_of_samples, std::int16_t *target) {
evaluate_output_volume(); evaluate_output_volume();
for(int ic = 0; ic < 8 && c < number_of_samples; ++ic) { for(int ic = 0; ic < 8 && c < number_of_samples; ++ic) {
target[c] = output_volume_; target[c] = transient_output_level_;
c++; c++;
master_divider_++; master_divider_++;
} }
@ -86,18 +86,24 @@ void SCC::write(uint16_t address, uint8_t value) {
} }
void SCC::evaluate_output_volume() { void SCC::evaluate_output_volume() {
output_volume_ = transient_output_level_ =
static_cast<int16_t>( static_cast<int16_t>(
( ((
(channel_enable_ & 0x01) ? static_cast<int8_t>(waves_[0].samples[channels_[0].offset]) * channels_[0].amplitude : 0 + (channel_enable_ & 0x01) ? static_cast<int8_t>(waves_[0].samples[channels_[0].offset]) * channels_[0].amplitude : 0 +
(channel_enable_ & 0x02) ? static_cast<int8_t>(waves_[1].samples[channels_[1].offset]) * channels_[1].amplitude : 0 + (channel_enable_ & 0x02) ? static_cast<int8_t>(waves_[1].samples[channels_[1].offset]) * channels_[1].amplitude : 0 +
(channel_enable_ & 0x04) ? static_cast<int8_t>(waves_[2].samples[channels_[2].offset]) * channels_[2].amplitude : 0 + (channel_enable_ & 0x04) ? static_cast<int8_t>(waves_[2].samples[channels_[2].offset]) * channels_[2].amplitude : 0 +
(channel_enable_ & 0x08) ? static_cast<int8_t>(waves_[3].samples[channels_[3].offset]) * channels_[3].amplitude : 0 + (channel_enable_ & 0x08) ? static_cast<int8_t>(waves_[3].samples[channels_[3].offset]) * channels_[3].amplitude : 0 +
(channel_enable_ & 0x10) ? static_cast<int8_t>(waves_[3].samples[channels_[4].offset]) * channels_[4].amplitude : 0 (channel_enable_ & 0x10) ? static_cast<int8_t>(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) { uint8_t SCC::read(uint16_t address) {
address &= 0xff; address &= 0xff;
if(address < 0x80) { if(address < 0x80) {
@ -105,3 +111,5 @@ uint8_t SCC::read(uint16_t address) {
} }
return 0xff; return 0xff;
} }

View File

@ -31,6 +31,7 @@ class SCC: public ::Outputs::Speaker::SampleSource {
/// As per ::SampleSource; provides audio output. /// As per ::SampleSource; provides audio output.
void get_samples(std::size_t number_of_samples, std::int16_t *target); 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. /// Writes to the SCC.
void write(uint16_t address, uint8_t value); 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. // State from here on down is accessed ony from the audio thread.
int master_divider_ = 0; int master_divider_ = 0;
int16_t output_volume_ = 0; std::int16_t master_volume_ = 0;
int16_t transient_output_level_ = 0;
struct Channel { struct Channel {
int period = 0; int period = 0;

View File

@ -14,15 +14,7 @@
using namespace TI; using namespace TI;
SN76489::SN76489(Personality personality, Concurrency::DeferringAsyncTaskQueue &task_queue, int additional_divider) : task_queue_(task_queue) { SN76489::SN76489(Personality personality, Concurrency::DeferringAsyncTaskQueue &task_queue, int additional_divider) : task_queue_(task_queue) {
// Build a volume table. set_sample_volume_range(0);
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();
switch(personality) { switch(personality) {
case Personality::SN76494: case Personality::SN76494:
@ -44,6 +36,18 @@ SN76489::SN76489(Personality personality, Concurrency::DeferringAsyncTaskQueue &
master_divider_period_ /= additional_divider; 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<float>(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) { void SN76489::set_register(uint8_t value) {
task_queue_.defer([value, this] () { task_queue_.defer([value, this] () {
if(value & 0x80) { if(value & 0x80) {

View File

@ -31,6 +31,7 @@ class SN76489: public Outputs::Speaker::SampleSource {
// As per SampleSource. // As per SampleSource.
void get_samples(std::size_t number_of_samples, std::int16_t *target); void get_samples(std::size_t number_of_samples, std::int16_t *target);
bool is_zero_level(); bool is_zero_level();
void set_sample_volume_range(std::int16_t range);
private: private:
int master_divider_ = 0; int master_divider_ = 0;

View File

@ -119,7 +119,11 @@ void Atari2600::TIASound::get_samples(std::size_t number_of_samples, int16_t *ta
break; 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;
}

View File

@ -26,7 +26,9 @@ class TIASound: public Outputs::Speaker::SampleSource {
void set_divider(int channel, uint8_t divider); void set_divider(int channel, uint8_t divider);
void set_control(int channel, uint8_t control); void set_control(int channel, uint8_t control);
// To satisfy ::SampleSource.
void get_samples(std::size_t number_of_samples, int16_t *target); void get_samples(std::size_t number_of_samples, int16_t *target);
void set_sample_volume_range(std::int16_t range);
private: private:
Concurrency::DeferringAsyncTaskQueue &audio_queue_; Concurrency::DeferringAsyncTaskQueue &audio_queue_;
@ -41,6 +43,7 @@ class TIASound: public Outputs::Speaker::SampleSource {
int output_state_[2]; int output_state_[2];
int divider_counter_[2]; int divider_counter_[2];
int16_t per_channel_volume_ = 0;
}; };
} }

View File

@ -15,10 +15,14 @@ using namespace Electron;
SoundGenerator::SoundGenerator(Concurrency::DeferringAsyncTaskQueue &audio_queue) : SoundGenerator::SoundGenerator(Concurrency::DeferringAsyncTaskQueue &audio_queue) :
audio_queue_(audio_queue) {} audio_queue_(audio_queue) {}
void SoundGenerator::set_sample_volume_range(std::int16_t range) {
volume_ = static_cast<unsigned int>(range / 2);
}
void SoundGenerator::get_samples(std::size_t number_of_samples, int16_t *target) { void SoundGenerator::get_samples(std::size_t number_of_samples, int16_t *target) {
if(is_enabled_) { if(is_enabled_) {
while(number_of_samples--) { while(number_of_samples--) {
*target = static_cast<int16_t>((counter_ / (divider_+1)) * 8192); *target = static_cast<int16_t>((counter_ / (divider_+1)) * volume_);
target++; target++;
counter_ = (counter_ + 1) % ((divider_+1) * 2); counter_ = (counter_ + 1) % ((divider_+1) * 2);
} }

View File

@ -22,16 +22,19 @@ class SoundGenerator: public ::Outputs::Speaker::SampleSource {
void set_is_enabled(bool is_enabled); 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 get_samples(std::size_t number_of_samples, int16_t *target);
void skip_samples(std::size_t number_of_samples); void skip_samples(std::size_t number_of_samples);
void set_sample_volume_range(std::int16_t range);
static const unsigned int clock_rate_divider = 8;
private: private:
Concurrency::DeferringAsyncTaskQueue &audio_queue_; Concurrency::DeferringAsyncTaskQueue &audio_queue_;
unsigned int counter_ = 0; unsigned int counter_ = 0;
unsigned int divider_ = 0; unsigned int divider_ = 0;
bool is_enabled_ = false; bool is_enabled_ = false;
unsigned int volume_ = 0;
}; };
} }

View File

@ -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 skip_samples(const std::size_t number_of_samples) {}
void set_output(bool enabled) { void set_output(bool enabled) {
if(is_enabled_ == enabled) return; if(is_enabled_ == enabled) return;
is_enabled_ = enabled; is_enabled_ = enabled;
audio_queue_.defer([=] { audio_queue_.defer([=] {
level_ = enabled ? 4096 : 0; level_ = enabled ? volume_ : 0;
}); });
} }
@ -78,7 +82,7 @@ class AudioToggle: public Outputs::Speaker::SampleSource {
private: private:
bool is_enabled_ = false; bool is_enabled_ = false;
int16_t level_ = 0; int16_t level_ = 0, volume_ = 0;
Concurrency::DeferringAsyncTaskQueue &audio_queue_; Concurrency::DeferringAsyncTaskQueue &audio_queue_;
}; };

View File

@ -25,6 +25,12 @@ template <typename... T> class CompoundSource: public Outputs::Speaker::SampleSo
void get_samples(std::size_t number_of_samples, std::int16_t *target) { void get_samples(std::size_t number_of_samples, std::int16_t *target) {
std::memset(target, 0, sizeof(std::int16_t) * number_of_samples); 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 <typename T, typename... R> class CompoundSource<T, R...>:
next_source_.skip_samples(number_of_samples); 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: private:
bool is_sleeping_ = false; bool is_sleeping_ = false;
T &source_; T &source_;

View File

@ -28,7 +28,9 @@ namespace Speaker {
*/ */
template <typename T> class LowpassSpeaker: public Speaker { template <typename T> class LowpassSpeaker: public Speaker {
public: 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. // Implemented as per Speaker.
float get_ideal_clock_rate_in_range(float minimum, float maximum) { float get_ideal_clock_rate_in_range(float minimum, float maximum) {

View File

@ -46,6 +46,13 @@ class SampleSource {
bool is_zero_level() { bool is_zero_level() {
return false; 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) {
}
}; };
} }