Unify [get_/skip_]samples, adding a third option for in-place mixing.

This commit is contained in:
Thomas Harte 2024-02-12 10:55:52 -05:00
parent 3a208460e2
commit d49c07687c
27 changed files with 204 additions and 162 deletions

View File

@ -19,6 +19,7 @@ AudioGenerator::AudioGenerator(Concurrency::AsyncTaskQueue<false> &audio_queue)
void AudioGenerator::set_volume(uint8_t volume) {
audio_queue_.enqueue([this, volume]() {
volume_ = int16_t(volume) * range_multiplier_;
dc_offset_ = volume_ >> 4;
});
}
@ -105,7 +106,8 @@ static uint8_t noise_pattern[] = {
// testing against 0x80. The effect should be the same: loading with 0x7f means an output update every cycle, loading with 0x7e
// means every second cycle, etc.
void AudioGenerator::get_samples(std::size_t number_of_samples, int16_t *target) {
template <Outputs::Speaker::Action action>
void AudioGenerator::apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *target) {
for(unsigned int c = 0; c < number_of_samples; ++c) {
update(0, 2, shift);
update(1, 1, shift);
@ -114,23 +116,22 @@ void AudioGenerator::get_samples(std::size_t number_of_samples, int16_t *target)
// this sums the output of all three sounds channels plus a DC offset for volume;
// TODO: what's the real ratio of this stuff?
target[c] = int16_t(
const int16_t sample =
(shift_registers_[0]&1) +
(shift_registers_[1]&1) +
(shift_registers_[2]&1) +
((noise_pattern[shift_registers_[3] >> 3] >> (shift_registers_[3]&7))&(control_registers_[3] >> 7)&1)
) * volume_ + (volume_ >> 4);
}
}
((noise_pattern[shift_registers_[3] >> 3] >> (shift_registers_[3]&7))&(control_registers_[3] >> 7)&1);
void AudioGenerator::skip_samples(std::size_t number_of_samples) {
for(unsigned int c = 0; c < number_of_samples; ++c) {
update(0, 2, shift);
update(1, 1, shift);
update(2, 0, shift);
update(3, 1, increment);
Outputs::Speaker::apply<action>(
target[c],
Outputs::Speaker::MonoSample(
sample * volume_ + dc_offset_
));
}
}
template void AudioGenerator::apply_samples<Outputs::Speaker::Action::Mix>(std::size_t, Outputs::Speaker::MonoSample *);
template void AudioGenerator::apply_samples<Outputs::Speaker::Action::Store>(std::size_t, Outputs::Speaker::MonoSample *);
template void AudioGenerator::apply_samples<Outputs::Speaker::Action::Ignore>(std::size_t, Outputs::Speaker::MonoSample *);
void AudioGenerator::set_sample_volume_range(std::int16_t range) {
range_multiplier_ = int16_t(range / 64);

View File

@ -25,8 +25,8 @@ class AudioGenerator: public Outputs::Speaker::BufferSource<AudioGenerator, fals
void set_control(int channel, uint8_t value);
// For ::SampleSource.
void get_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *target);
void skip_samples(std::size_t number_of_samples);
template <Outputs::Speaker::Action action>
void apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *target);
void set_sample_volume_range(std::int16_t range);
private:
@ -36,6 +36,7 @@ class AudioGenerator: public Outputs::Speaker::BufferSource<AudioGenerator, fals
unsigned int shift_registers_[4] = {0, 0, 0, 0};
uint8_t control_registers_[4] = {0, 0, 0, 0};
int16_t volume_ = 0;
int16_t dc_offset_ = 0;
int16_t range_multiplier_ = 1;
};

View File

@ -102,7 +102,8 @@ template <bool is_stereo> void AY38910<is_stereo>::set_output_mixing(float a_lef
}
template <bool is_stereo>
void AY38910<is_stereo>::get_samples(
template <Outputs::Speaker::Action action>
void AY38910<is_stereo>::apply_samples(
std::size_t number_of_samples,
typename Outputs::Speaker::SampleT<is_stereo>::type *target
) {
@ -117,7 +118,7 @@ void AY38910<is_stereo>::get_samples(
std::size_t c = 0;
while((master_divider_&3) && c < number_of_samples) {
target[c] = output_volume_;
Outputs::Speaker::apply<action>(target[c], output_volume_);
master_divider_++;
c++;
}
@ -159,7 +160,7 @@ void AY38910<is_stereo>::get_samples(
evaluate_output_volume();
for(int ic = 0; ic < 4 && c < number_of_samples; ic++) {
target[c] = output_volume_;
Outputs::Speaker::apply<action>(target[c], output_volume_);
c++;
master_divider_++;
}
@ -168,6 +169,13 @@ void AY38910<is_stereo>::get_samples(
master_divider_ &= 3;
}
template void AY38910<false>::apply_samples<Outputs::Speaker::Action::Mix>(std::size_t, typename Outputs::Speaker::SampleT<false>::type *);
template void AY38910<false>::apply_samples<Outputs::Speaker::Action::Store>(std::size_t, typename Outputs::Speaker::SampleT<false>::type *);
template void AY38910<false>::apply_samples<Outputs::Speaker::Action::Ignore>(std::size_t, typename Outputs::Speaker::SampleT<false>::type *);
template void AY38910<true>::apply_samples<Outputs::Speaker::Action::Mix>(std::size_t, typename Outputs::Speaker::SampleT<true>::type *);
template void AY38910<true>::apply_samples<Outputs::Speaker::Action::Store>(std::size_t, typename Outputs::Speaker::SampleT<true>::type *);
template void AY38910<true>::apply_samples<Outputs::Speaker::Action::Ignore>(std::size_t, typename Outputs::Speaker::SampleT<true>::type *);
template <bool is_stereo> void AY38910<is_stereo>::evaluate_output_volume() {
int envelope_volume = envelope_shapes_[output_registers_[13]][envelope_position_ | envelope_position_mask_];

View File

@ -105,8 +105,9 @@ template <bool stereo> class AY38910: public ::Outputs::Speaker::BufferSource<AY
*/
void set_output_mixing(float a_left, float b_left, float c_left, float a_right = 1.0, float b_right = 1.0, float c_right = 1.0);
// to satisfy ::Outputs::Speaker (included via ::Outputs::Filter.
void get_samples(std::size_t number_of_samples, typename Outputs::Speaker::SampleT<stereo>::type *target);
// Buffer generation.
template <Outputs::Speaker::Action action>
void apply_samples(std::size_t number_of_samples, typename Outputs::Speaker::SampleT<stereo>::type *target);
bool is_zero_level() const;
void set_sample_volume_range(std::int16_t range);

View File

@ -15,17 +15,11 @@ using namespace Audio;
Audio::Toggle::Toggle(Concurrency::AsyncTaskQueue<false> &audio_queue) :
audio_queue_(audio_queue) {}
void Toggle::get_samples(std::size_t number_of_samples, std::int16_t *target) {
std::fill(target, target + number_of_samples, level_);
}
void Toggle::set_sample_volume_range(std::int16_t range) {
volume_ = range;
level_ = level_active_ ? volume_ : 0;
}
void Toggle::skip_samples(std::size_t) {}
void Toggle::set_output(bool enabled) {
if(is_enabled_ == enabled) return;
is_enabled_ = enabled;

View File

@ -20,9 +20,11 @@ class Toggle: public Outputs::Speaker::BufferSource<Toggle, false> {
public:
Toggle(Concurrency::AsyncTaskQueue<false> &audio_queue);
void get_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *target);
template <Outputs::Speaker::Action action>
void apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *target) {
Outputs::Speaker::fill<action>(target, target + number_of_samples, level_);
}
void set_sample_volume_range(std::int16_t range);
void skip_samples(const std::size_t number_of_samples);
void set_output(bool enabled);
bool get_output() const;

View File

@ -19,15 +19,16 @@ bool SCC::is_zero_level() const {
return !(channel_enable_ & 0x1f);
}
void SCC::get_samples(std::size_t number_of_samples, std::int16_t *target) {
template <Outputs::Speaker::Action action>
void SCC::apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *target) {
if(is_zero_level()) {
std::memset(target, 0, sizeof(std::int16_t) * number_of_samples);
Outputs::Speaker::fill<action>(target, target + number_of_samples, Outputs::Speaker::MonoSample());
return;
}
std::size_t c = 0;
while((master_divider_&7) && c < number_of_samples) {
target[c] = transient_output_level_;
Outputs::Speaker::apply<action>(target[c], transient_output_level_);
master_divider_++;
c++;
}
@ -44,12 +45,15 @@ 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] = transient_output_level_;
Outputs::Speaker::apply<action>(target[c], transient_output_level_);
c++;
master_divider_++;
}
}
}
template void SCC::apply_samples<Outputs::Speaker::Action::Mix>(std::size_t, Outputs::Speaker::MonoSample *);
template void SCC::apply_samples<Outputs::Speaker::Action::Store>(std::size_t, Outputs::Speaker::MonoSample *);
template void SCC::apply_samples<Outputs::Speaker::Action::Ignore>(std::size_t, Outputs::Speaker::MonoSample *);
void SCC::write(uint16_t address, uint8_t value) {
address &= 0xff;
@ -111,5 +115,3 @@ uint8_t SCC::read(uint16_t address) {
}
return 0xff;
}

View File

@ -29,7 +29,8 @@ class SCC: public ::Outputs::Speaker::BufferSource<SCC, false> {
bool is_zero_level() const;
/// As per ::SampleSource; provides audio output.
void get_samples(std::size_t number_of_samples, std::int16_t *target);
template <Outputs::Speaker::Action action>
void apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *target);
void set_sample_volume_range(std::int16_t range);
/// Writes to the SCC.
@ -44,7 +45,7 @@ class SCC: public ::Outputs::Speaker::BufferSource<SCC, false> {
// State from here on down is accessed ony from the audio thread.
int master_divider_ = 0;
std::int16_t master_volume_ = 0;
int16_t transient_output_level_ = 0;
Outputs::Speaker::MonoSample transient_output_level_ = 0;
struct Channel {
int period = 0;

View File

@ -278,7 +278,8 @@ void OPLL::set_sample_volume_range(std::int16_t range) {
total_volume_ = range;
}
void OPLL::get_samples(std::size_t number_of_samples, std::int16_t *target) {
template <Outputs::Speaker::Action action>
void OPLL::apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *target) {
// Both the OPLL and the OPL2 divide the input clock by 72 to get the base tick frequency;
// unlike the OPL2 the OPLL time-divides the output for 'mixing'.
@ -289,12 +290,16 @@ void OPLL::get_samples(std::size_t number_of_samples, std::int16_t *target) {
while(number_of_samples--) {
if(!audio_offset_) update_all_channels();
*target = output_levels_[audio_offset_ / channel_output_period];
Outputs::Speaker::apply<action>(*target, output_levels_[audio_offset_ / channel_output_period]);
++target;
audio_offset_ = (audio_offset_ + 1) % update_period;
}
}
template void OPLL::apply_samples<Outputs::Speaker::Action::Mix>(std::size_t, Outputs::Speaker::MonoSample *);
template void OPLL::apply_samples<Outputs::Speaker::Action::Store>(std::size_t, Outputs::Speaker::MonoSample *);
template void OPLL::apply_samples<Outputs::Speaker::Action::Ignore>(std::size_t, Outputs::Speaker::MonoSample *);
void OPLL::update_all_channels() {
oscillator_.update();

View File

@ -25,7 +25,8 @@ class OPLL: public OPLBase<OPLL, false> {
OPLL(Concurrency::AsyncTaskQueue<false> &task_queue, int audio_divider = 1, bool is_vrc7 = false);
/// As per ::SampleSource; provides audio output.
void get_samples(std::size_t number_of_samples, std::int16_t *target);
template <Outputs::Speaker::Action action>
void apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *target);
void set_sample_volume_range(std::int16_t range);
// The OPLL is generally 'half' as loud as it's told to be. This won't strictly be true in

View File

@ -99,10 +99,11 @@ void SN76489::evaluate_output_volume() {
);
}
void SN76489::get_samples(std::size_t number_of_samples, std::int16_t *target) {
template <Outputs::Speaker::Action action>
void SN76489::apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *target) {
std::size_t c = 0;
while((master_divider_& (master_divider_period_ - 1)) && c < number_of_samples) {
target[c] = output_volume_;
Outputs::Speaker::apply<action>(target[c], output_volume_);
master_divider_++;
c++;
}
@ -151,7 +152,7 @@ void SN76489::get_samples(std::size_t number_of_samples, std::int16_t *target) {
evaluate_output_volume();
for(int ic = 0; ic < master_divider_period_ && c < number_of_samples; ++ic) {
target[c] = output_volume_;
Outputs::Speaker::apply<action>(target[c], output_volume_);
c++;
master_divider_++;
}
@ -159,3 +160,6 @@ void SN76489::get_samples(std::size_t number_of_samples, std::int16_t *target) {
master_divider_ &= (master_divider_period_ - 1);
}
template void SN76489::apply_samples<Outputs::Speaker::Action::Mix>(std::size_t, Outputs::Speaker::MonoSample *);
template void SN76489::apply_samples<Outputs::Speaker::Action::Store>(std::size_t, Outputs::Speaker::MonoSample *);
template void SN76489::apply_samples<Outputs::Speaker::Action::Ignore>(std::size_t, Outputs::Speaker::MonoSample *);

View File

@ -28,7 +28,8 @@ class SN76489: public Outputs::Speaker::BufferSource<SN76489, false> {
void write(uint8_t value);
// As per SampleSource.
void get_samples(std::size_t number_of_samples, std::int16_t *target);
template <Outputs::Speaker::Action action>
void apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *target);
bool is_zero_level() const;
void set_sample_volume_range(std::int16_t range);

View File

@ -159,29 +159,34 @@ void GLU::run_for(Cycles cycles) {
pending_store_write_time_ += cycles.as<uint32_t>();
}
void GLU::get_samples(std::size_t number_of_samples, std::int16_t *target) {
template <Outputs::Speaker::Action action>
void GLU::apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *target) {
// Update remote state, generating audio.
generate_audio(number_of_samples, target);
generate_audio<action>(number_of_samples, target);
}
template void GLU::apply_samples<Outputs::Speaker::Action::Mix>(std::size_t, Outputs::Speaker::MonoSample *);
template void GLU::apply_samples<Outputs::Speaker::Action::Store>(std::size_t, Outputs::Speaker::MonoSample *);
template void GLU::apply_samples<Outputs::Speaker::Action::Ignore>(std::size_t, Outputs::Speaker::MonoSample *);
void GLU::skip_samples(const std::size_t number_of_samples) {
// Update remote state, without generating audio.
skip_audio(remote_, number_of_samples);
// Apply any pending stores.
std::atomic_thread_fence(std::memory_order::memory_order_acquire);
const uint32_t final_time = pending_store_read_time_ + uint32_t(number_of_samples);
while(true) {
auto next_store = pending_stores_[pending_store_read_].load(std::memory_order::memory_order_acquire);
if(!next_store.enabled) break;
if(next_store.time >= final_time) break;
remote_.ram_[next_store.address] = next_store.value;
next_store.enabled = false;
pending_stores_[pending_store_read_].store(next_store, std::memory_order::memory_order_relaxed);
pending_store_read_ = (pending_store_read_ + 1) & (StoreBufferSize - 1);
}
}
//void GLU::skip_samples(const std::size_t number_of_samples) {
// // Update remote state, without generating audio.
// skip_audio(remote_, number_of_samples);
//
// // Apply any pending stores.
// std::atomic_thread_fence(std::memory_order::memory_order_acquire);
// const uint32_t final_time = pending_store_read_time_ + uint32_t(number_of_samples);
// while(true) {
// auto next_store = pending_stores_[pending_store_read_].load(std::memory_order::memory_order_acquire);
// if(!next_store.enabled) break;
// if(next_store.time >= final_time) break;
// remote_.ram_[next_store.address] = next_store.value;
// next_store.enabled = false;
// pending_stores_[pending_store_read_].store(next_store, std::memory_order::memory_order_relaxed);
//
// pending_store_read_ = (pending_store_read_ + 1) & (StoreBufferSize - 1);
// }
//}
void GLU::set_sample_volume_range(std::int16_t range) {
output_range_ = range;
@ -256,7 +261,8 @@ void GLU::skip_audio(EnsoniqState &state, size_t number_of_samples) {
}
}
void GLU::generate_audio(size_t number_of_samples, std::int16_t *target) {
template <Outputs::Speaker::Action action>
void GLU::generate_audio(size_t number_of_samples, Outputs::Speaker::MonoSample *target) {
auto next_store = pending_stores_[pending_store_read_].load(std::memory_order::memory_order_acquire);
uint8_t next_amplitude = 255;
for(size_t sample = 0; sample < number_of_samples; sample++) {
@ -325,7 +331,12 @@ void GLU::generate_audio(size_t number_of_samples, std::int16_t *target) {
// Maximum total output was 32 channels times a 16-bit range. Map that down.
// TODO: dynamic total volume?
target[sample] = (output * output_range_) >> 20;
Outputs::Speaker::apply<action>(
target[sample],
Outputs::Speaker::MonoSample(
(output * output_range_) >> 20
)
);
// Apply any RAM writes that interleave here.
++pending_store_read_time_;

View File

@ -34,9 +34,9 @@ class GLU: public Outputs::Speaker::BufferSource<GLU, false> { // TODO: isn't th
bool get_interrupt_line();
// SampleSource.
void get_samples(std::size_t number_of_samples, std::int16_t *target);
template <Outputs::Speaker::Action action>
void apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *target);
void set_sample_volume_range(std::int16_t range);
void skip_samples(const std::size_t number_of_samples);
private:
Concurrency::AsyncTaskQueue<false> &audio_queue_;
@ -94,7 +94,8 @@ class GLU: public Outputs::Speaker::BufferSource<GLU, false> { // TODO: isn't th
// Functions to update an EnsoniqState; these don't belong to the state itself
// because they also access the pending stores (inter alia).
void generate_audio(size_t number_of_samples, std::int16_t *target);
template <Outputs::Speaker::Action action>
void generate_audio(size_t number_of_samples, Outputs::Speaker::MonoSample *target);
void skip_audio(EnsoniqState &state, size_t number_of_samples);
// Audio-thread state.

View File

@ -71,7 +71,8 @@ void Audio::set_volume_multiplier() {
volume_multiplier_ = int16_t(output_volume_ * volume_ * enabled_mask_);
}
void Audio::get_samples(std::size_t number_of_samples, int16_t *target) {
template <Outputs::Speaker::Action action>
void Audio::apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *target) {
// TODO: the implementation below acts as if the hardware uses pulse-amplitude modulation;
// in fact it uses pulse-width modulation. But the scale for pulses isn't specified, so
// that's something to return to.
@ -82,7 +83,7 @@ void Audio::get_samples(std::size_t number_of_samples, int16_t *target) {
// Determine the output level, and output that many samples.
const int16_t output_level = volume_multiplier_ * (int16_t(sample_queue_.buffer[sample_queue_.read_pointer].load(std::memory_order::memory_order_relaxed)) - 128);
std::fill(target, target + cycles_left_in_sample, output_level);
Outputs::Speaker::fill<action>(target, target + cycles_left_in_sample, output_level);
target += cycles_left_in_sample;
// Advance the sample pointer.
@ -94,3 +95,6 @@ void Audio::get_samples(std::size_t number_of_samples, int16_t *target) {
number_of_samples -= cycles_left_in_sample;
}
}
template void Audio::apply_samples<Outputs::Speaker::Action::Mix>(std::size_t, Outputs::Speaker::MonoSample *);
template void Audio::apply_samples<Outputs::Speaker::Action::Store>(std::size_t, Outputs::Speaker::MonoSample *);
template void Audio::apply_samples<Outputs::Speaker::Action::Ignore>(std::size_t, Outputs::Speaker::MonoSample *);

View File

@ -50,7 +50,8 @@ class Audio: public ::Outputs::Speaker::BufferSource<Audio, false> {
void set_enabled(bool on);
// to satisfy ::Outputs::Speaker (included via ::Outputs::Filter.
void get_samples(std::size_t number_of_samples, int16_t *target);
template <Outputs::Speaker::Action action>
void apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *target);
bool is_zero_level() const;
void set_sample_volume_range(std::int16_t range);

View File

@ -40,9 +40,10 @@ void Atari2600::TIASound::set_control(int channel, uint8_t control) {
#define advance_poly5(c) poly5_counter_[channel] = (poly5_counter_[channel] >> 1) | (((poly5_counter_[channel] << 4) ^ (poly5_counter_[channel] << 2))&0x010)
#define advance_poly9(c) poly9_counter_[channel] = (poly9_counter_[channel] >> 1) | (((poly9_counter_[channel] << 4) ^ (poly9_counter_[channel] << 8))&0x100)
void Atari2600::TIASound::get_samples(std::size_t number_of_samples, int16_t *target) {
template <Outputs::Speaker::Action action>
void Atari2600::TIASound::apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *target) {
for(unsigned int c = 0; c < number_of_samples; c++) {
target[c] = 0;
Outputs::Speaker::MonoSample output = 0;
for(int channel = 0; channel < 2; channel++) {
divider_counter_[channel] ++;
int divider_value = divider_counter_[channel] / (38 / CPUTicksPerAudioTick);
@ -119,10 +120,14 @@ void Atari2600::TIASound::get_samples(std::size_t number_of_samples, int16_t *ta
break;
}
target[c] += (volume_[channel] * per_channel_volume_ * level) >> 4;
output += (volume_[channel] * per_channel_volume_ * level) >> 4;
}
Outputs::Speaker::apply<action>(target[c], output);
}
}
template void Atari2600::TIASound::apply_samples<Outputs::Speaker::Action::Mix>(std::size_t, Outputs::Speaker::MonoSample *);
template void Atari2600::TIASound::apply_samples<Outputs::Speaker::Action::Store>(std::size_t, Outputs::Speaker::MonoSample *);
template void Atari2600::TIASound::apply_samples<Outputs::Speaker::Action::Ignore>(std::size_t, Outputs::Speaker::MonoSample *);
void Atari2600::TIASound::set_sample_volume_range(std::int16_t range) {
per_channel_volume_ = range / 2;

View File

@ -26,7 +26,8 @@ class TIASound: public Outputs::Speaker::BufferSource<TIASound, false> {
void set_control(int channel, uint8_t control);
// To satisfy ::SampleSource.
void get_samples(std::size_t number_of_samples, int16_t *target);
template <Outputs::Speaker::Action action>
void apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *target);
void set_sample_volume_range(std::int16_t range);
private:

View File

@ -19,21 +19,26 @@ void SoundGenerator::set_sample_volume_range(std::int16_t range) {
volume_ = unsigned(range / 2);
}
void SoundGenerator::get_samples(std::size_t number_of_samples, int16_t *target) {
template <Outputs::Speaker::Action action>
void SoundGenerator::apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *target) {
if constexpr (action == Outputs::Speaker::Action::Ignore) {
counter_ = (counter_ + number_of_samples) % ((divider_+1) * 2);
return;
}
if(is_enabled_) {
while(number_of_samples--) {
*target = int16_t((counter_ / (divider_+1)) * volume_);
Outputs::Speaker::apply<action>(*target, Outputs::Speaker::MonoSample((counter_ / (divider_+1)) * volume_));
target++;
counter_ = (counter_ + 1) % ((divider_+1) * 2);
}
} else {
std::memset(target, 0, sizeof(int16_t) * number_of_samples);
Outputs::Speaker::fill<action>(target, target + number_of_samples, Outputs::Speaker::MonoSample(0));
}
}
void SoundGenerator::skip_samples(std::size_t number_of_samples) {
counter_ = (counter_ + number_of_samples) % ((divider_+1) * 2);
}
template void SoundGenerator::apply_samples<Outputs::Speaker::Action::Mix>(std::size_t, Outputs::Speaker::MonoSample *);
template void SoundGenerator::apply_samples<Outputs::Speaker::Action::Store>(std::size_t, Outputs::Speaker::MonoSample *);
template void SoundGenerator::apply_samples<Outputs::Speaker::Action::Ignore>(std::size_t, Outputs::Speaker::MonoSample *);
void SoundGenerator::set_divider(uint8_t divider) {
audio_queue_.enqueue([this, divider]() {

View File

@ -24,8 +24,8 @@ class SoundGenerator: public ::Outputs::Speaker::BufferSource<SoundGenerator, fa
static constexpr 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);
template <Outputs::Speaker::Action action>
void apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *target);
void set_sample_volume_range(std::int16_t range);
private:

View File

@ -99,7 +99,8 @@ void Audio::update_channel(int c) {
channels_[c].output |= output;
}
void Audio::get_samples(std::size_t number_of_samples, Outputs::Speaker::StereoSample *target) {
template <Outputs::Speaker::Action action>
void Audio::apply_samples(std::size_t number_of_samples, Outputs::Speaker::StereoSample *target) {
Outputs::Speaker::StereoSample output_level;
size_t c = 0;
@ -130,7 +131,7 @@ void Audio::get_samples(std::size_t number_of_samples, Outputs::Speaker::StereoS
while(global_divider_ && c < number_of_samples) {
--global_divider_;
target[c] = output_level;
Outputs::Speaker::apply<action>(target[c], output_level);
++c;
}
global_divider_ = global_divider_reload_;
@ -203,6 +204,9 @@ void Audio::get_samples(std::size_t number_of_samples, Outputs::Speaker::StereoS
}
}
}
template void Audio::apply_samples<Outputs::Speaker::Action::Mix>(std::size_t, Outputs::Speaker::StereoSample *);
template void Audio::apply_samples<Outputs::Speaker::Action::Store>(std::size_t, Outputs::Speaker::StereoSample *);
template void Audio::apply_samples<Outputs::Speaker::Action::Ignore>(std::size_t, Outputs::Speaker::StereoSample *);
// MARK: - Interrupt source

View File

@ -37,7 +37,8 @@ class Audio: public Outputs::Speaker::BufferSource<Audio, true> {
// MARK: - SampleSource.
void set_sample_volume_range(int16_t range);
void get_samples(std::size_t number_of_samples, Outputs::Speaker::StereoSample *target);
template <Outputs::Speaker::Action action>
void apply_samples(std::size_t number_of_samples, Outputs::Speaker::StereoSample *target);
private:
Concurrency::AsyncTaskQueue<false> &audio_queue_;

View File

@ -62,7 +62,7 @@
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Release"
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
enableASanStackUseAfterReturn = "YES"

View File

@ -8,6 +8,7 @@
#pragma once
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
@ -16,6 +17,38 @@
namespace Outputs::Speaker {
enum class Action {
/// New values should be _stored_ to the sample buffer.
Store,
/// New values should be _added_ to the sample buffer.
Mix,
/// New values shouldn't be stored; the source can skip generation of them if desired.
Ignore,
};
template <Action action, typename SampleT> void apply(SampleT &lhs, SampleT rhs) {
switch(action) {
case Action::Mix: lhs += rhs; break;
case Action::Store: lhs = rhs; break;
case Action::Ignore: break;
}
}
template <Action action, typename IteratorT, typename SampleT> void fill(IteratorT begin, IteratorT end, SampleT value) {
switch(action) {
case Action::Mix:
while(begin != end) {
apply<action>(*begin, value);
++begin;
}
break;
case Action::Store:
std::fill(begin, end, value);
break;
case Action::Ignore: break;
}
}
/*!
A sample source is something that can provide a stream of audio.
This optional base class provides the interface expected to be exposed
@ -30,20 +63,11 @@ class BufferSource {
static constexpr bool is_stereo = stereo;
/*!
Should write the next @c number_of_samples to @c target.
Should 'apply' the next @c number_of_samples to @c target ; application means applying @c action which can be achieved either via the
helper functions above @c apply and @c fill or by semantic inspection (primarily, if an obvious quick route for @c Action::Ignore is available)
*/
void get_samples([[maybe_unused]] std::size_t number_of_samples, [[maybe_unused]] typename SampleT<stereo>::type *target) {}
/*!
Should skip the next @c number_of_samples. Subclasses of this SampleSource
need not implement this if it would no more efficient to do so than it is
merely to call get_samples and throw the result away, as per the default
implementation below.
*/
void skip_samples(std::size_t number_of_samples) {
typename SampleT<stereo>::type scratch_pad[number_of_samples];
get_samples(number_of_samples, scratch_pad);
}
template <Action action>
void apply_samples([[maybe_unused]] std::size_t number_of_samples, [[maybe_unused]] typename SampleT<stereo>::type *target) {}
/*!
@returns @c true if it is trivially true that a call to get_samples would just
@ -75,13 +99,15 @@ class BufferSource {
template <typename SourceT, bool stereo, int divider = 1>
struct SampleSource: public BufferSource<SourceT, stereo> {
public:
void get_samples(std::size_t number_of_samples, typename SampleT<stereo>::type *target) {
template <bool mix>
void apply_samples(std::size_t number_of_samples, typename SampleT<stereo>::type *target) {
const auto &source = *static_cast<SourceT *>(this);
if constexpr (divider == 1) {
while(number_of_samples--) {
*target = source.level();
apply<mix>(*target, source.level());
++target;
source.advance();
}
} else {
std::size_t c = 0;
@ -89,7 +115,7 @@ struct SampleSource: public BufferSource<SourceT, stereo> {
// Fill in the tail of any partially-captured level.
auto level = source.level();
while(c < number_of_samples && master_divider_ != divider) {
target[c] = level;
apply<mix>(target[c], level);
++c;
++master_divider_;
}
@ -98,7 +124,7 @@ struct SampleSource: public BufferSource<SourceT, stereo> {
// Provide all full levels.
int whole_steps = (number_of_samples - c) / divider;
while(whole_steps--) {
std::fill(&target[c], &target[c + divider], source.level());
fill<mix>(&target[c], &target[c + divider], source.level());
c += divider;
source.advance();
}
@ -106,31 +132,7 @@ struct SampleSource: public BufferSource<SourceT, stereo> {
// Provide the head of a further partial capture.
level = source.level();
master_divider_ = number_of_samples - c;
std::fill(&target[c], &target[number_of_samples], source.level());
}
}
void skip_samples(std::size_t number_of_samples) {
const auto &source = *static_cast<SourceT *>(this);
if constexpr (&SourceT::advance == &SampleSource::advance) {
return;
}
if constexpr (divider == 1) {
while(--number_of_samples) {
source.advance();
}
} else {
if(number_of_samples >= divider - master_divider_) {
source.advance();
number_of_samples -= (divider - master_divider_);
}
while(number_of_samples > divider) {
advance();
number_of_samples -= divider;
}
master_divider_ = number_of_samples;
fill<mix>(&target[c], &target[number_of_samples], source.level());
}
}

View File

@ -49,14 +49,13 @@ template <typename... T> class CompoundSource:
private:
template <typename... S> class CompoundSourceHolder {
public:
template <bool output_stereo>
void get_samples(std::size_t number_of_samples, typename SampleT<output_stereo>::type *target) {
template <Outputs::Speaker::Action action, bool output_stereo>
void apply_samples(std::size_t number_of_samples, typename SampleT<output_stereo>::type *target) {
// Default-construct all samples, to fill with silence.
std::fill(target, target + number_of_samples, typename SampleT<output_stereo>::type());
Outputs::Speaker::fill<action>(target, target + number_of_samples, typename SampleT<output_stereo>::type());
}
void set_scaled_volume_range(int16_t, double *, double) {}
void skip_samples(const std::size_t) {}
static constexpr std::size_t size() {
return 0;
@ -75,8 +74,8 @@ template <typename... T> class CompoundSource:
static constexpr bool is_stereo = S::is_stereo || CompoundSourceHolder<R...>::is_stereo;
template <bool output_stereo>
void get_samples(std::size_t number_of_samples, typename ::Outputs::Speaker::SampleT<output_stereo>::type *target) {
template <Outputs::Speaker::Action action, bool output_stereo>
void apply_samples(std::size_t number_of_samples, typename ::Outputs::Speaker::SampleT<output_stereo>::type *target) {
// If this is the step at which a mono-to-stereo adaptation happens, apply it.
if constexpr (output_stereo && !S::is_stereo) {
@ -87,40 +86,26 @@ template <typename... T> class CompoundSource:
}
// Populate the conversion buffer with this source and all below.
get_samples<false>(number_of_samples, conversion_source_.data());
apply_samples<Action::Store, false>(number_of_samples, conversion_source_.data());
// Map up and return.
for(std::size_t c = 0; c < number_of_samples; c++) {
target[c].left = target[c].right = conversion_source_[c];
Outputs::Speaker::apply<action>(target[c].left, StereoSample(conversion_source_[c]));
}
return;
}
// Get the rest of the output.
next_source_.template get_samples<output_stereo>(number_of_samples, target);
next_source_.template apply_samples<action, output_stereo>(number_of_samples, target);
if(source_.is_zero_level()) {
// This component is currently outputting silence; therefore don't add anything to the output
// audio — just pass the call onward.
source_.skip_samples(number_of_samples);
return;
}
// Get this component's output.
typename SampleT<output_stereo>::type local_samples[number_of_samples];
source_.get_samples(number_of_samples, local_samples);
// Merge it in.
while(number_of_samples--) {
target[number_of_samples] += local_samples[number_of_samples];
}
// TODO: accelerate above?
}
void skip_samples(const std::size_t number_of_samples) {
source_.skip_samples(number_of_samples);
next_source_.skip_samples(number_of_samples);
// Add in this component's output.
source_.template apply_samples<Action::Mix>(number_of_samples, target);
}
void set_scaled_volume_range(int16_t range, double *volumes, double scale) {
@ -157,12 +142,9 @@ template <typename... T> class CompoundSource:
}
}
void get_samples(std::size_t number_of_samples, Sample *target) {
source_holder_.template get_samples<::Outputs::Speaker::is_stereo<T...>()>(number_of_samples, target);
}
void skip_samples(const std::size_t number_of_samples) {
source_holder_.skip_samples(number_of_samples);
template <Outputs::Speaker::Action action>
void apply_samples(std::size_t number_of_samples, Sample *target) {
source_holder_.template apply_samples<action, ::Outputs::Speaker::is_stereo<T...>()>(number_of_samples, target);
}
/*!

View File

@ -8,6 +8,7 @@
#pragma once
#include "BufferSource.hpp"
#include "../Speaker.hpp"
#include "../../../SignalProcessing/FIRFilter.hpp"
#include "../../../ClockReceiver/ClockReceiver.hpp"
@ -396,7 +397,7 @@ template <typename SampleSource> class PullLowpass: public LowpassBase<PullLowpa
SampleSource &sample_source_;
void skip_samples(size_t count) {
sample_source_.skip_samples(count);
sample_source_.template apply_samples<Action::Ignore>(count, nullptr);
}
int get_scale() {
@ -406,9 +407,9 @@ template <typename SampleSource> class PullLowpass: public LowpassBase<PullLowpa
void get_samples(size_t length, int16_t *target) {
if constexpr (SampleSource::is_stereo) {
StereoSample *const stereo_target = reinterpret_cast<StereoSample *>(target);
sample_source_.get_samples(length, stereo_target);
sample_source_.template apply_samples<Action::Store>(length, stereo_target);
} else {
sample_source_.get_samples(length, target);
sample_source_.template apply_samples<Action::Store>(length, target);
}
}
};

View File

@ -27,6 +27,9 @@ struct StereoSample {
left = rhs.left;
right = rhs.right;
}
StereoSample(MonoSample value) {
left = right = value;
}
StereoSample &operator +=(const StereoSample &rhs) {
left += rhs.left;