mirror of
https://github.com/TomHarte/CLK.git
synced 2024-08-16 16:28:59 +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 shift
|
||||||
#undef increment
|
#undef increment
|
||||||
#undef update
|
#undef update
|
||||||
|
@ -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_;
|
||||||
|
@ -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) {
|
||||||
|
@ -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_;
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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) {
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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_;
|
||||||
|
@ -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) {
|
||||||
|
@ -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) {
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user