1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-06-27 01:29:31 +00:00

Introduces formal setting of the output volume to SampleSource.

Previously every output device was making its own decision. Which is increasingly less sustainable due to the CompoundSource.
This commit is contained in:
Thomas Harte 2018-03-09 13:23:18 -05:00
parent 53f05efb2d
commit 48737a32a7
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) {
}
};
}