mirror of
https://github.com/TomHarte/CLK.git
synced 2024-11-26 23:52:26 +00:00
Merge pull request #367 from TomHarte/DynamicVolume
Introduces formal setting of the output volume to `SampleSource`.
This commit is contained in:
commit
719f5d79c2
@ -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
|
||||
|
@ -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_;
|
||||
|
@ -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) {
|
||||
|
@ -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_;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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_;
|
||||
};
|
||||
|
||||
|
@ -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_;
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user