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

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) { void AudioGenerator::set_volume(uint8_t volume) {
audio_queue_.enqueue([this, volume]() { audio_queue_.enqueue([this, volume]() {
volume_ = int16_t(volume) * range_multiplier_; 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 // 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. // 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) { for(unsigned int c = 0; c < number_of_samples; ++c) {
update(0, 2, shift); update(0, 2, shift);
update(1, 1, 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; // this sums the output of all three sounds channels plus a DC offset for volume;
// TODO: what's the real ratio of this stuff? // TODO: what's the real ratio of this stuff?
target[c] = int16_t( const int16_t sample =
(shift_registers_[0]&1) + (shift_registers_[0]&1) +
(shift_registers_[1]&1) + (shift_registers_[1]&1) +
(shift_registers_[2]&1) + (shift_registers_[2]&1) +
((noise_pattern[shift_registers_[3] >> 3] >> (shift_registers_[3]&7))&(control_registers_[3] >> 7)&1) ((noise_pattern[shift_registers_[3] >> 3] >> (shift_registers_[3]&7))&(control_registers_[3] >> 7)&1);
) * volume_ + (volume_ >> 4);
}
}
void AudioGenerator::skip_samples(std::size_t number_of_samples) { Outputs::Speaker::apply<action>(
for(unsigned int c = 0; c < number_of_samples; ++c) { target[c],
update(0, 2, shift); Outputs::Speaker::MonoSample(
update(1, 1, shift); sample * volume_ + dc_offset_
update(2, 0, shift); ));
update(3, 1, increment);
} }
} }
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) { void AudioGenerator::set_sample_volume_range(std::int16_t range) {
range_multiplier_ = int16_t(range / 64); 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); void set_control(int channel, uint8_t value);
// For ::SampleSource. // For ::SampleSource.
void get_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *target); template <Outputs::Speaker::Action action>
void skip_samples(std::size_t number_of_samples); void apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *target);
void set_sample_volume_range(std::int16_t range); void set_sample_volume_range(std::int16_t range);
private: private:
@ -36,6 +36,7 @@ class AudioGenerator: public Outputs::Speaker::BufferSource<AudioGenerator, fals
unsigned int shift_registers_[4] = {0, 0, 0, 0}; unsigned int shift_registers_[4] = {0, 0, 0, 0};
uint8_t control_registers_[4] = {0, 0, 0, 0}; uint8_t control_registers_[4] = {0, 0, 0, 0};
int16_t volume_ = 0; int16_t volume_ = 0;
int16_t dc_offset_ = 0;
int16_t range_multiplier_ = 1; 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> 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, std::size_t number_of_samples,
typename Outputs::Speaker::SampleT<is_stereo>::type *target typename Outputs::Speaker::SampleT<is_stereo>::type *target
) { ) {
@ -117,7 +118,7 @@ void AY38910<is_stereo>::get_samples(
std::size_t c = 0; std::size_t c = 0;
while((master_divider_&3) && c < number_of_samples) { while((master_divider_&3) && c < number_of_samples) {
target[c] = output_volume_; Outputs::Speaker::apply<action>(target[c], output_volume_);
master_divider_++; master_divider_++;
c++; c++;
} }
@ -159,7 +160,7 @@ void AY38910<is_stereo>::get_samples(
evaluate_output_volume(); evaluate_output_volume();
for(int ic = 0; ic < 4 && c < number_of_samples; ic++) { for(int ic = 0; ic < 4 && c < number_of_samples; ic++) {
target[c] = output_volume_; Outputs::Speaker::apply<action>(target[c], output_volume_);
c++; c++;
master_divider_++; master_divider_++;
} }
@ -168,6 +169,13 @@ void AY38910<is_stereo>::get_samples(
master_divider_ &= 3; 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() { template <bool is_stereo> void AY38910<is_stereo>::evaluate_output_volume() {
int envelope_volume = envelope_shapes_[output_registers_[13]][envelope_position_ | envelope_position_mask_]; 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); 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. // Buffer generation.
void get_samples(std::size_t number_of_samples, typename Outputs::Speaker::SampleT<stereo>::type *target); 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; bool is_zero_level() const;
void set_sample_volume_range(std::int16_t range); 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::Toggle::Toggle(Concurrency::AsyncTaskQueue<false> &audio_queue) :
audio_queue_(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) { void Toggle::set_sample_volume_range(std::int16_t range) {
volume_ = range; volume_ = range;
level_ = level_active_ ? volume_ : 0; level_ = level_active_ ? volume_ : 0;
} }
void Toggle::skip_samples(std::size_t) {}
void Toggle::set_output(bool enabled) { void Toggle::set_output(bool enabled) {
if(is_enabled_ == enabled) return; if(is_enabled_ == enabled) return;
is_enabled_ = enabled; is_enabled_ = enabled;

View File

@ -20,9 +20,11 @@ class Toggle: public Outputs::Speaker::BufferSource<Toggle, false> {
public: public:
Toggle(Concurrency::AsyncTaskQueue<false> &audio_queue); 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 set_sample_volume_range(std::int16_t range);
void skip_samples(const std::size_t number_of_samples);
void set_output(bool enabled); void set_output(bool enabled);
bool get_output() const; bool get_output() const;

View File

@ -19,15 +19,16 @@ bool SCC::is_zero_level() const {
return !(channel_enable_ & 0x1f); 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()) { 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; return;
} }
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] = transient_output_level_; Outputs::Speaker::apply<action>(target[c], transient_output_level_);
master_divider_++; master_divider_++;
c++; c++;
} }
@ -44,12 +45,15 @@ 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] = transient_output_level_; Outputs::Speaker::apply<action>(target[c], transient_output_level_);
c++; c++;
master_divider_++; 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) { void SCC::write(uint16_t address, uint8_t value) {
address &= 0xff; address &= 0xff;
@ -111,5 +115,3 @@ uint8_t SCC::read(uint16_t address) {
} }
return 0xff; return 0xff;
} }

View File

@ -29,7 +29,8 @@ class SCC: public ::Outputs::Speaker::BufferSource<SCC, false> {
bool is_zero_level() const; bool is_zero_level() const;
/// 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); 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 set_sample_volume_range(std::int16_t range);
/// Writes to the SCC. /// 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. // State from here on down is accessed ony from the audio thread.
int master_divider_ = 0; int master_divider_ = 0;
std::int16_t master_volume_ = 0; std::int16_t master_volume_ = 0;
int16_t transient_output_level_ = 0; Outputs::Speaker::MonoSample transient_output_level_ = 0;
struct Channel { struct Channel {
int period = 0; int period = 0;

View File

@ -278,7 +278,8 @@ void OPLL::set_sample_volume_range(std::int16_t range) {
total_volume_ = 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; // 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'. // 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--) { while(number_of_samples--) {
if(!audio_offset_) update_all_channels(); 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; ++target;
audio_offset_ = (audio_offset_ + 1) % update_period; 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() { void OPLL::update_all_channels() {
oscillator_.update(); 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); OPLL(Concurrency::AsyncTaskQueue<false> &task_queue, int audio_divider = 1, bool is_vrc7 = false);
/// 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); 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 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 // 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; std::size_t c = 0;
while((master_divider_& (master_divider_period_ - 1)) && c < number_of_samples) { 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_++; master_divider_++;
c++; c++;
} }
@ -151,7 +152,7 @@ void SN76489::get_samples(std::size_t number_of_samples, std::int16_t *target) {
evaluate_output_volume(); evaluate_output_volume();
for(int ic = 0; ic < master_divider_period_ && c < number_of_samples; ++ic) { 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++; c++;
master_divider_++; 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); 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); void write(uint8_t value);
// As per SampleSource. // 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; bool is_zero_level() const;
void set_sample_volume_range(std::int16_t range); 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>(); 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. // 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. //void GLU::skip_samples(const std::size_t number_of_samples) {
std::atomic_thread_fence(std::memory_order::memory_order_acquire); // // Update remote state, without generating audio.
const uint32_t final_time = pending_store_read_time_ + uint32_t(number_of_samples); // skip_audio(remote_, number_of_samples);
while(true) { //
auto next_store = pending_stores_[pending_store_read_].load(std::memory_order::memory_order_acquire); // // Apply any pending stores.
if(!next_store.enabled) break; // std::atomic_thread_fence(std::memory_order::memory_order_acquire);
if(next_store.time >= final_time) break; // const uint32_t final_time = pending_store_read_time_ + uint32_t(number_of_samples);
remote_.ram_[next_store.address] = next_store.value; // while(true) {
next_store.enabled = false; // auto next_store = pending_stores_[pending_store_read_].load(std::memory_order::memory_order_acquire);
pending_stores_[pending_store_read_].store(next_store, std::memory_order::memory_order_relaxed); // if(!next_store.enabled) break;
// if(next_store.time >= final_time) break;
pending_store_read_ = (pending_store_read_ + 1) & (StoreBufferSize - 1); // 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) { void GLU::set_sample_volume_range(std::int16_t range) {
output_range_ = 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); auto next_store = pending_stores_[pending_store_read_].load(std::memory_order::memory_order_acquire);
uint8_t next_amplitude = 255; uint8_t next_amplitude = 255;
for(size_t sample = 0; sample < number_of_samples; sample++) { 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. // Maximum total output was 32 channels times a 16-bit range. Map that down.
// TODO: dynamic total volume? // 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. // Apply any RAM writes that interleave here.
++pending_store_read_time_; ++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(); bool get_interrupt_line();
// SampleSource. // 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 set_sample_volume_range(std::int16_t range);
void skip_samples(const std::size_t number_of_samples);
private: private:
Concurrency::AsyncTaskQueue<false> &audio_queue_; 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 // Functions to update an EnsoniqState; these don't belong to the state itself
// because they also access the pending stores (inter alia). // 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); void skip_audio(EnsoniqState &state, size_t number_of_samples);
// Audio-thread state. // Audio-thread state.

View File

@ -71,7 +71,8 @@ void Audio::set_volume_multiplier() {
volume_multiplier_ = int16_t(output_volume_ * volume_ * enabled_mask_); 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; // 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 // in fact it uses pulse-width modulation. But the scale for pulses isn't specified, so
// that's something to return to. // 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. // 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); 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; target += cycles_left_in_sample;
// Advance the sample pointer. // 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; 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); void set_enabled(bool on);
// to satisfy ::Outputs::Speaker (included via ::Outputs::Filter. // 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; bool is_zero_level() const;
void set_sample_volume_range(std::int16_t range); 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_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) #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++) { 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++) { for(int channel = 0; channel < 2; channel++) {
divider_counter_[channel] ++; divider_counter_[channel] ++;
int divider_value = divider_counter_[channel] / (38 / CPUTicksPerAudioTick); 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; 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) { void Atari2600::TIASound::set_sample_volume_range(std::int16_t range) {
per_channel_volume_ = range / 2; 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); void set_control(int channel, uint8_t control);
// To satisfy ::SampleSource. // 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); void set_sample_volume_range(std::int16_t range);
private: private:

View File

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

View File

@ -99,7 +99,8 @@ void Audio::update_channel(int c) {
channels_[c].output |= output; 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; Outputs::Speaker::StereoSample output_level;
size_t c = 0; 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) { while(global_divider_ && c < number_of_samples) {
--global_divider_; --global_divider_;
target[c] = output_level; Outputs::Speaker::apply<action>(target[c], output_level);
++c; ++c;
} }
global_divider_ = global_divider_reload_; 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 // MARK: - Interrupt source

View File

@ -37,7 +37,8 @@ class Audio: public Outputs::Speaker::BufferSource<Audio, true> {
// MARK: - SampleSource. // MARK: - SampleSource.
void set_sample_volume_range(int16_t range); 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: private:
Concurrency::AsyncTaskQueue<false> &audio_queue_; Concurrency::AsyncTaskQueue<false> &audio_queue_;

View File

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

View File

@ -8,6 +8,7 @@
#pragma once #pragma once
#include <algorithm>
#include <array> #include <array>
#include <cstddef> #include <cstddef>
#include <cstdint> #include <cstdint>
@ -16,6 +17,38 @@
namespace Outputs::Speaker { 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. A sample source is something that can provide a stream of audio.
This optional base class provides the interface expected to be exposed This optional base class provides the interface expected to be exposed
@ -30,20 +63,11 @@ class BufferSource {
static constexpr bool is_stereo = stereo; 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) {} template <Action action>
void apply_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);
}
/*! /*!
@returns @c true if it is trivially true that a call to get_samples would just @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> template <typename SourceT, bool stereo, int divider = 1>
struct SampleSource: public BufferSource<SourceT, stereo> { struct SampleSource: public BufferSource<SourceT, stereo> {
public: 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); const auto &source = *static_cast<SourceT *>(this);
if constexpr (divider == 1) { if constexpr (divider == 1) {
while(number_of_samples--) { while(number_of_samples--) {
*target = source.level(); apply<mix>(*target, source.level());
++target; ++target;
source.advance();
} }
} else { } else {
std::size_t c = 0; std::size_t c = 0;
@ -89,7 +115,7 @@ struct SampleSource: public BufferSource<SourceT, stereo> {
// Fill in the tail of any partially-captured level. // Fill in the tail of any partially-captured level.
auto level = source.level(); auto level = source.level();
while(c < number_of_samples && master_divider_ != divider) { while(c < number_of_samples && master_divider_ != divider) {
target[c] = level; apply<mix>(target[c], level);
++c; ++c;
++master_divider_; ++master_divider_;
} }
@ -98,7 +124,7 @@ struct SampleSource: public BufferSource<SourceT, stereo> {
// Provide all full levels. // Provide all full levels.
int whole_steps = (number_of_samples - c) / divider; int whole_steps = (number_of_samples - c) / divider;
while(whole_steps--) { while(whole_steps--) {
std::fill(&target[c], &target[c + divider], source.level()); fill<mix>(&target[c], &target[c + divider], source.level());
c += divider; c += divider;
source.advance(); source.advance();
} }
@ -106,31 +132,7 @@ struct SampleSource: public BufferSource<SourceT, stereo> {
// Provide the head of a further partial capture. // Provide the head of a further partial capture.
level = source.level(); level = source.level();
master_divider_ = number_of_samples - c; master_divider_ = number_of_samples - c;
std::fill(&target[c], &target[number_of_samples], source.level()); fill<mix>(&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;
} }
} }

View File

@ -49,14 +49,13 @@ template <typename... T> class CompoundSource:
private: private:
template <typename... S> class CompoundSourceHolder { template <typename... S> class CompoundSourceHolder {
public: public:
template <bool output_stereo> template <Outputs::Speaker::Action action, bool output_stereo>
void get_samples(std::size_t number_of_samples, typename SampleT<output_stereo>::type *target) { void apply_samples(std::size_t number_of_samples, typename SampleT<output_stereo>::type *target) {
// Default-construct all samples, to fill with silence. // 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 set_scaled_volume_range(int16_t, double *, double) {}
void skip_samples(const std::size_t) {}
static constexpr std::size_t size() { static constexpr std::size_t size() {
return 0; return 0;
@ -75,8 +74,8 @@ template <typename... T> class CompoundSource:
static constexpr bool is_stereo = S::is_stereo || CompoundSourceHolder<R...>::is_stereo; static constexpr bool is_stereo = S::is_stereo || CompoundSourceHolder<R...>::is_stereo;
template <bool output_stereo> template <Outputs::Speaker::Action action, bool output_stereo>
void get_samples(std::size_t number_of_samples, typename ::Outputs::Speaker::SampleT<output_stereo>::type *target) { 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 this is the step at which a mono-to-stereo adaptation happens, apply it.
if constexpr (output_stereo && !S::is_stereo) { 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. // 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. // Map up and return.
for(std::size_t c = 0; c < number_of_samples; c++) { 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; return;
} }
// Get the rest of the output. // 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()) { if(source_.is_zero_level()) {
// This component is currently outputting silence; therefore don't add anything to the output // This component is currently outputting silence; therefore don't add anything to the output
// audio — just pass the call onward. // audio — just pass the call onward.
source_.skip_samples(number_of_samples);
return; return;
} }
// Get this component's output. // Add in this component's output.
typename SampleT<output_stereo>::type local_samples[number_of_samples]; source_.template apply_samples<Action::Mix>(number_of_samples, target);
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);
} }
void set_scaled_volume_range(int16_t range, double *volumes, double scale) { 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) { template <Outputs::Speaker::Action action>
source_holder_.template get_samples<::Outputs::Speaker::is_stereo<T...>()>(number_of_samples, target); 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);
void skip_samples(const std::size_t number_of_samples) {
source_holder_.skip_samples(number_of_samples);
} }
/*! /*!

View File

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