mirror of
https://github.com/TomHarte/CLK.git
synced 2024-12-23 20:29:42 +00:00
Unify [get_/skip_]samples, adding a third option for in-place mixing.
This commit is contained in:
parent
3a208460e2
commit
d49c07687c
@ -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);
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -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_];
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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
|
||||
|
@ -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 *);
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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_;
|
||||
|
@ -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.
|
||||
|
@ -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 *);
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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:
|
||||
|
@ -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]() {
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
||||
|
@ -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_;
|
||||
|
@ -62,7 +62,7 @@
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Release"
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
enableASanStackUseAfterReturn = "YES"
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user