1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-07-05 10:28:58 +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 increment
#undef update

View File

@ -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_;

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
float max_volume = 8192;
float root_two = sqrtf(2.0f);
const float max_volume = static_cast<float>(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<int>(max_volume / powf(root_two, static_cast<float>(v ^ 0xf)));
}
volumes_[0] = 0;
evaluate_output_volume();
}
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
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_;

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;
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<int16_t>(
(
((
(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_ & 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_ & 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) {
address &= 0xff;
if(address < 0x80) {
@ -105,3 +111,5 @@ uint8_t SCC::read(uint16_t address) {
}
return 0xff;
}

View File

@ -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;

View File

@ -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<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) {
task_queue_.defer([value, this] () {
if(value & 0x80) {

View File

@ -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;

View File

@ -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;
}

View File

@ -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;
};
}

View File

@ -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<unsigned int>(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<int16_t>((counter_ / (divider_+1)) * 8192);
*target = static_cast<int16_t>((counter_ / (divider_+1)) * volume_);
target++;
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);
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;
};
}

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 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_;
};

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) {
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);
}
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_;

View File

@ -28,7 +28,9 @@ namespace Speaker {
*/
template <typename T> 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) {

View File

@ -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) {
}
};
}