mirror of
https://github.com/TomHarte/CLK.git
synced 2024-11-22 12:33:29 +00:00
Merge pull request #1328 from TomHarte/PerSampleAudio
Improve SampleSource infrastructure.
This commit is contained in:
commit
fd45745600
@ -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);
|
||||
|
@ -12,12 +12,12 @@
|
||||
#include "../../Concurrency/AsyncTaskQueue.hpp"
|
||||
#include "../../Outputs/CRT/CRT.hpp"
|
||||
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
|
||||
#include "../../Outputs/Speaker/Implementation/SampleSource.hpp"
|
||||
#include "../../Outputs/Speaker/Implementation/BufferSource.hpp"
|
||||
|
||||
namespace MOS::MOS6560 {
|
||||
|
||||
// audio state
|
||||
class AudioGenerator: public ::Outputs::Speaker::SampleSource {
|
||||
class AudioGenerator: public Outputs::Speaker::BufferSource<AudioGenerator, false> {
|
||||
public:
|
||||
AudioGenerator(Concurrency::AsyncTaskQueue<false> &audio_queue);
|
||||
|
||||
@ -25,10 +25,9 @@ class AudioGenerator: public ::Outputs::Speaker::SampleSource {
|
||||
void set_control(int channel, uint8_t value);
|
||||
|
||||
// For ::SampleSource.
|
||||
void get_samples(std::size_t number_of_samples, int16_t *target);
|
||||
void skip_samples(std::size_t number_of_samples);
|
||||
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);
|
||||
static constexpr bool get_is_stereo() { return false; }
|
||||
|
||||
private:
|
||||
Concurrency::AsyncTaskQueue<false> &audio_queue_;
|
||||
@ -37,6 +36,7 @@ class AudioGenerator: public ::Outputs::Speaker::SampleSource {
|
||||
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;
|
||||
};
|
||||
|
||||
|
@ -101,7 +101,12 @@ template <bool is_stereo> void AY38910<is_stereo>::set_output_mixing(float a_lef
|
||||
c_right_ = uint8_t(c_right * 255.0f);
|
||||
}
|
||||
|
||||
template <bool is_stereo> void AY38910<is_stereo>::get_samples(std::size_t number_of_samples, int16_t *target) {
|
||||
template <bool is_stereo>
|
||||
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
|
||||
) {
|
||||
// Note on structure below: the real AY has a built-in divider of 8
|
||||
// prior to applying its tone and noise dividers. But the YM fills the
|
||||
// same total periods for noise and tone with double-precision envelopes.
|
||||
@ -113,11 +118,7 @@ template <bool is_stereo> void AY38910<is_stereo>::get_samples(std::size_t numbe
|
||||
|
||||
std::size_t c = 0;
|
||||
while((master_divider_&3) && c < number_of_samples) {
|
||||
if constexpr (is_stereo) {
|
||||
reinterpret_cast<uint32_t *>(target)[c] = output_volume_;
|
||||
} else {
|
||||
target[c] = int16_t(output_volume_);
|
||||
}
|
||||
Outputs::Speaker::apply<action>(target[c], output_volume_);
|
||||
master_divider_++;
|
||||
c++;
|
||||
}
|
||||
@ -159,11 +160,7 @@ template <bool is_stereo> void AY38910<is_stereo>::get_samples(std::size_t numbe
|
||||
evaluate_output_volume();
|
||||
|
||||
for(int ic = 0; ic < 4 && c < number_of_samples; ic++) {
|
||||
if constexpr (is_stereo) {
|
||||
reinterpret_cast<uint32_t *>(target)[c] = output_volume_;
|
||||
} else {
|
||||
target[c] = int16_t(output_volume_);
|
||||
}
|
||||
Outputs::Speaker::apply<action>(target[c], output_volume_);
|
||||
c++;
|
||||
master_divider_++;
|
||||
}
|
||||
@ -172,6 +169,13 @@ template <bool is_stereo> void AY38910<is_stereo>::get_samples(std::size_t numbe
|
||||
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_];
|
||||
|
||||
@ -214,19 +218,18 @@ template <bool is_stereo> void AY38910<is_stereo>::evaluate_output_volume() {
|
||||
|
||||
// Mix additively, weighting if in stereo.
|
||||
if constexpr (is_stereo) {
|
||||
int16_t *const output_volumes = reinterpret_cast<int16_t *>(&output_volume_);
|
||||
output_volumes[0] = int16_t((
|
||||
output_volume_.left = int16_t((
|
||||
volumes_[volumes[0]] * channel_levels[0] * a_left_ +
|
||||
volumes_[volumes[1]] * channel_levels[1] * b_left_ +
|
||||
volumes_[volumes[2]] * channel_levels[2] * c_left_
|
||||
) >> 8);
|
||||
output_volumes[1] = int16_t((
|
||||
output_volume_.right = int16_t((
|
||||
volumes_[volumes[0]] * channel_levels[0] * a_right_ +
|
||||
volumes_[volumes[1]] * channel_levels[1] * b_right_ +
|
||||
volumes_[volumes[2]] * channel_levels[2] * c_right_
|
||||
) >> 8);
|
||||
} else {
|
||||
output_volume_ = uint32_t(
|
||||
output_volume_ = int16_t(
|
||||
volumes_[volumes[0]] * channel_levels[0] +
|
||||
volumes_[volumes[1]] * channel_levels[1] +
|
||||
volumes_[volumes[2]] * channel_levels[2]
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../../Outputs/Speaker/Implementation/SampleSource.hpp"
|
||||
#include "../../Outputs/Speaker/Implementation/BufferSource.hpp"
|
||||
#include "../../Concurrency/AsyncTaskQueue.hpp"
|
||||
|
||||
#include "../../Reflection/Struct.hpp"
|
||||
@ -66,7 +66,7 @@ enum class Personality {
|
||||
|
||||
This AY has an attached mono or stereo mixer.
|
||||
*/
|
||||
template <bool is_stereo> class AY38910: public ::Outputs::Speaker::SampleSource {
|
||||
template <bool stereo> class AY38910: public ::Outputs::Speaker::BufferSource<AY38910<stereo>, stereo> {
|
||||
public:
|
||||
/// Creates a new AY38910.
|
||||
AY38910(Personality, Concurrency::AsyncTaskQueue<false> &);
|
||||
@ -105,11 +105,11 @@ template <bool is_stereo> class AY38910: public ::Outputs::Speaker::SampleSource
|
||||
*/
|
||||
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, int16_t *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);
|
||||
static constexpr bool get_is_stereo() { return is_stereo; }
|
||||
|
||||
private:
|
||||
Concurrency::AsyncTaskQueue<false> &task_queue_;
|
||||
@ -150,7 +150,7 @@ template <bool is_stereo> class AY38910: public ::Outputs::Speaker::SampleSource
|
||||
|
||||
uint8_t data_input_, data_output_;
|
||||
|
||||
uint32_t output_volume_;
|
||||
typename Outputs::Speaker::SampleT<stereo>::type output_volume_;
|
||||
|
||||
void update_bus();
|
||||
PortHandler *port_handler_ = nullptr;
|
||||
|
@ -8,27 +8,23 @@
|
||||
|
||||
#include "AudioToggle.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
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) {
|
||||
for(std::size_t sample = 0; sample < number_of_samples; ++sample) {
|
||||
target[sample] = 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;
|
||||
audio_queue_.enqueue([this, enabled] {
|
||||
level_active_ = enabled;
|
||||
level_ = enabled ? volume_ : 0;
|
||||
});
|
||||
}
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../../Outputs/Speaker/Implementation/SampleSource.hpp"
|
||||
#include "../../Outputs/Speaker/Implementation/BufferSource.hpp"
|
||||
#include "../../Concurrency/AsyncTaskQueue.hpp"
|
||||
|
||||
namespace Audio {
|
||||
@ -16,13 +16,15 @@ namespace Audio {
|
||||
/*!
|
||||
Provides a sample source that can programmatically be set to one of two values.
|
||||
*/
|
||||
class Toggle: public Outputs::Speaker::SampleSource {
|
||||
class Toggle: public Outputs::Speaker::BufferSource<Toggle, false> {
|
||||
public:
|
||||
Toggle(Concurrency::AsyncTaskQueue<false> &audio_queue);
|
||||
|
||||
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) {
|
||||
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;
|
||||
@ -34,6 +36,7 @@ class Toggle: public Outputs::Speaker::SampleSource {
|
||||
|
||||
// Accessed on the audio thread.
|
||||
int16_t level_ = 0, volume_ = 0;
|
||||
bool level_active_ = false;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../../Outputs/Speaker/Implementation/SampleSource.hpp"
|
||||
#include "../../Outputs/Speaker/Implementation/BufferSource.hpp"
|
||||
#include "../../Concurrency/AsyncTaskQueue.hpp"
|
||||
|
||||
namespace Konami {
|
||||
@ -20,7 +20,7 @@ namespace Konami {
|
||||
and five channels of output. The original SCC uses the same wave for channels
|
||||
four and five, the SCC+ supports different waves for the two channels.
|
||||
*/
|
||||
class SCC: public ::Outputs::Speaker::SampleSource {
|
||||
class SCC: public ::Outputs::Speaker::BufferSource<SCC, false> {
|
||||
public:
|
||||
/// Creates a new SCC.
|
||||
SCC(Concurrency::AsyncTaskQueue<false> &task_queue);
|
||||
@ -29,9 +29,9 @@ class SCC: public ::Outputs::Speaker::SampleSource {
|
||||
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);
|
||||
static constexpr bool get_is_stereo() { return false; }
|
||||
|
||||
/// Writes to the SCC.
|
||||
void write(uint16_t address, uint8_t value);
|
||||
@ -45,7 +45,7 @@ class SCC: public ::Outputs::Speaker::SampleSource {
|
||||
// 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;
|
||||
|
@ -8,12 +8,12 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../../../Outputs/Speaker/Implementation/SampleSource.hpp"
|
||||
#include "../../../Outputs/Speaker/Implementation/BufferSource.hpp"
|
||||
#include "../../../Concurrency/AsyncTaskQueue.hpp"
|
||||
|
||||
namespace Yamaha::OPL {
|
||||
|
||||
template <typename Child> class OPLBase: public ::Outputs::Speaker::SampleSource {
|
||||
template <typename Child, bool stereo> class OPLBase: public ::Outputs::Speaker::BufferSource<Child, stereo> {
|
||||
public:
|
||||
void write(uint16_t address, uint8_t value) {
|
||||
if(address & 1) {
|
||||
|
@ -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();
|
||||
|
||||
|
@ -19,24 +19,25 @@
|
||||
|
||||
namespace Yamaha::OPL {
|
||||
|
||||
class OPLL: public OPLBase<OPLL> {
|
||||
class OPLL: public OPLBase<OPLL, false> {
|
||||
public:
|
||||
/// Creates a new OPLL or VRC7.
|
||||
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
|
||||
// rhythm mode, but it's correct for melodic output.
|
||||
double get_average_output_peak() const { return 0.5; }
|
||||
double average_output_peak() const { return 0.5; }
|
||||
|
||||
/// Reads from the OPL.
|
||||
uint8_t read(uint16_t address);
|
||||
|
||||
private:
|
||||
friend OPLBase<OPLL>;
|
||||
friend OPLBase<OPLL, false>;
|
||||
void write_register(uint8_t address, uint8_t value);
|
||||
|
||||
int audio_divider_ = 0;
|
||||
|
@ -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 *);
|
||||
|
@ -8,12 +8,12 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../../Outputs/Speaker/Implementation/SampleSource.hpp"
|
||||
#include "../../Outputs/Speaker/Implementation/BufferSource.hpp"
|
||||
#include "../../Concurrency/AsyncTaskQueue.hpp"
|
||||
|
||||
namespace TI {
|
||||
|
||||
class SN76489: public Outputs::Speaker::SampleSource {
|
||||
class SN76489: public Outputs::Speaker::BufferSource<SN76489, false> {
|
||||
public:
|
||||
enum class Personality {
|
||||
SN76489,
|
||||
@ -28,10 +28,10 @@ class SN76489: public Outputs::Speaker::SampleSource {
|
||||
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);
|
||||
static constexpr bool get_is_stereo() { return false; }
|
||||
|
||||
private:
|
||||
int master_divider_ = 0;
|
||||
|
@ -221,7 +221,7 @@ class ConcreteMachine:
|
||||
// MARK: - MachineTypes::MouseMachine.
|
||||
|
||||
Inputs::Mouse &get_mouse() final {
|
||||
return chipset_.get_mouse();;
|
||||
return chipset_.get_mouse();
|
||||
}
|
||||
|
||||
// MARK: - MachineTypes::JoystickMachine.
|
||||
|
@ -108,7 +108,7 @@ uint8_t GLU::get_data() {
|
||||
|
||||
switch(address & 0xe0) {
|
||||
case 0x00: return local_.oscillators[address & 0x1f].velocity & 0xff;
|
||||
case 0x20: return local_.oscillators[address & 0x1f].velocity >> 8;;
|
||||
case 0x20: return local_.oscillators[address & 0x1f].velocity >> 8;
|
||||
case 0x40: return local_.oscillators[address & 0x1f].volume;
|
||||
case 0x60: return local_.oscillators[address & 0x1f].sample(local_.ram_); // i.e. look up what the sample was on demand.
|
||||
case 0x80: return local_.oscillators[address & 0x1f].address;
|
||||
@ -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_;
|
||||
|
@ -12,11 +12,11 @@
|
||||
|
||||
#include "../../../ClockReceiver/ClockReceiver.hpp"
|
||||
#include "../../../Concurrency/AsyncTaskQueue.hpp"
|
||||
#include "../../../Outputs/Speaker/Implementation/SampleSource.hpp"
|
||||
#include "../../../Outputs/Speaker/Implementation/BufferSource.hpp"
|
||||
|
||||
namespace Apple::IIgs::Sound {
|
||||
|
||||
class GLU: public Outputs::Speaker::SampleSource {
|
||||
class GLU: public Outputs::Speaker::BufferSource<GLU, false> { // TODO: isn't this stereo?
|
||||
public:
|
||||
GLU(Concurrency::AsyncTaskQueue<false> &audio_queue);
|
||||
|
||||
@ -34,9 +34,9 @@ class GLU: public Outputs::Speaker::SampleSource {
|
||||
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::SampleSource {
|
||||
|
||||
// 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.
|
||||
|
@ -8,6 +8,8 @@
|
||||
|
||||
#include "Audio.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
using namespace Apple::Macintosh;
|
||||
|
||||
namespace {
|
||||
@ -69,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.
|
||||
@ -79,11 +82,8 @@ void Audio::get_samples(std::size_t number_of_samples, int16_t *target) {
|
||||
const auto cycles_left_in_sample = std::min(number_of_samples, sample_length - subcycle_offset_);
|
||||
|
||||
// Determine the output level, and output that many samples.
|
||||
// (Hoping that the copiler substitutes an effective memset16-type operation here).
|
||||
const int16_t output_level = volume_multiplier_ * (int16_t(sample_queue_.buffer[sample_queue_.read_pointer].load(std::memory_order::memory_order_relaxed)) - 128);
|
||||
for(size_t c = 0; c < cycles_left_in_sample; ++c) {
|
||||
target[c] = output_level;
|
||||
}
|
||||
Outputs::Speaker::fill<action>(target, target + cycles_left_in_sample, output_level);
|
||||
target += cycles_left_in_sample;
|
||||
|
||||
// Advance the sample pointer.
|
||||
@ -95,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 *);
|
||||
|
@ -10,7 +10,7 @@
|
||||
|
||||
#include "../../../Concurrency/AsyncTaskQueue.hpp"
|
||||
#include "../../../ClockReceiver/ClockReceiver.hpp"
|
||||
#include "../../../Outputs/Speaker/Implementation/SampleSource.hpp"
|
||||
#include "../../../Outputs/Speaker/Implementation/BufferSource.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
@ -23,7 +23,7 @@ namespace Apple::Macintosh {
|
||||
Designed to be clocked at half the rate of the real hardware — i.e.
|
||||
a shade less than 4Mhz.
|
||||
*/
|
||||
class Audio: public ::Outputs::Speaker::SampleSource {
|
||||
class Audio: public ::Outputs::Speaker::BufferSource<Audio, false> {
|
||||
public:
|
||||
Audio(Concurrency::AsyncTaskQueue<false> &task_queue);
|
||||
|
||||
@ -50,10 +50,10 @@ class Audio: public ::Outputs::Speaker::SampleSource {
|
||||
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);
|
||||
constexpr static bool get_is_stereo() { return false; }
|
||||
|
||||
private:
|
||||
Concurrency::AsyncTaskQueue<false> &task_queue_;
|
||||
|
@ -28,7 +28,7 @@ class DriveSpeedAccumulator {
|
||||
Sets the delegate to receive drive speed changes.
|
||||
*/
|
||||
void set_delegate(Delegate *delegate) {
|
||||
delegate_ = delegate;;
|
||||
delegate_ = delegate;
|
||||
}
|
||||
|
||||
private:
|
||||
|
@ -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;
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../../../Outputs/Speaker/Implementation/SampleSource.hpp"
|
||||
#include "../../../Outputs/Speaker/Implementation/BufferSource.hpp"
|
||||
#include "../../../Concurrency/AsyncTaskQueue.hpp"
|
||||
|
||||
namespace Atari2600 {
|
||||
@ -17,7 +17,7 @@ namespace Atari2600 {
|
||||
// will give greater resolution to changes in audio state. 1, 2 and 19 are the only divisors of 38.
|
||||
constexpr int CPUTicksPerAudioTick = 2;
|
||||
|
||||
class TIASound: public Outputs::Speaker::SampleSource {
|
||||
class TIASound: public Outputs::Speaker::BufferSource<TIASound, false> {
|
||||
public:
|
||||
TIASound(Concurrency::AsyncTaskQueue<false> &audio_queue);
|
||||
|
||||
@ -26,9 +26,9 @@ class TIASound: public Outputs::Speaker::SampleSource {
|
||||
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);
|
||||
static constexpr bool get_is_stereo() { return false; }
|
||||
|
||||
private:
|
||||
Concurrency::AsyncTaskQueue<false> &audio_queue_;
|
||||
|
@ -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]() {
|
||||
|
@ -8,12 +8,12 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../../Outputs/Speaker/Implementation/SampleSource.hpp"
|
||||
#include "../../Outputs/Speaker/Implementation/BufferSource.hpp"
|
||||
#include "../../Concurrency/AsyncTaskQueue.hpp"
|
||||
|
||||
namespace Electron {
|
||||
|
||||
class SoundGenerator: public ::Outputs::Speaker::SampleSource {
|
||||
class SoundGenerator: public ::Outputs::Speaker::BufferSource<SoundGenerator, false> {
|
||||
public:
|
||||
SoundGenerator(Concurrency::AsyncTaskQueue<false> &audio_queue);
|
||||
|
||||
@ -23,11 +23,10 @@ class SoundGenerator: public ::Outputs::Speaker::SampleSource {
|
||||
|
||||
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);
|
||||
// For BufferSource.
|
||||
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);
|
||||
static constexpr bool get_is_stereo() { return false; }
|
||||
|
||||
private:
|
||||
Concurrency::AsyncTaskQueue<false> &audio_queue_;
|
||||
|
@ -99,11 +99,9 @@ void Audio::update_channel(int c) {
|
||||
channels_[c].output |= output;
|
||||
}
|
||||
|
||||
void Audio::get_samples(std::size_t number_of_samples, int16_t *target) {
|
||||
struct Frame {
|
||||
int16_t left, right;
|
||||
} output_level;
|
||||
Frame *target_frames = reinterpret_cast<Frame *>(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;
|
||||
while(c < number_of_samples) {
|
||||
@ -133,14 +131,11 @@ void Audio::get_samples(std::size_t number_of_samples, int16_t *target) {
|
||||
|
||||
while(global_divider_ && c < number_of_samples) {
|
||||
--global_divider_;
|
||||
target_frames[c] = output_level;
|
||||
Outputs::Speaker::apply<action>(target[c], output_level);
|
||||
++c;
|
||||
}
|
||||
|
||||
global_divider_ = global_divider_reload_;
|
||||
if(!global_divider_) {
|
||||
global_divider_ = global_divider_reload_;
|
||||
}
|
||||
|
||||
poly_state_[int(Channel::Distortion::FourBit)] = poly4_.next();
|
||||
poly_state_[int(Channel::Distortion::FiveBit)] = poly5_.next();
|
||||
poly_state_[int(Channel::Distortion::SevenBit)] = poly7_.next();
|
||||
@ -209,6 +204,9 @@ void Audio::get_samples(std::size_t number_of_samples, int16_t *target) {
|
||||
}
|
||||
}
|
||||
}
|
||||
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
|
||||
|
||||
|
@ -13,7 +13,7 @@
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
#include "../../Concurrency/AsyncTaskQueue.hpp"
|
||||
#include "../../Numeric/LFSR.hpp"
|
||||
#include "../../Outputs/Speaker/Implementation/SampleSource.hpp"
|
||||
#include "../../Outputs/Speaker/Implementation/BufferSource.hpp"
|
||||
|
||||
namespace Enterprise::Dave {
|
||||
|
||||
@ -26,7 +26,7 @@ enum class Interrupt: uint8_t {
|
||||
/*!
|
||||
Models the audio-production subset of Dave's behaviour.
|
||||
*/
|
||||
class Audio: public Outputs::Speaker::SampleSource {
|
||||
class Audio: public Outputs::Speaker::BufferSource<Audio, true> {
|
||||
public:
|
||||
Audio(Concurrency::AsyncTaskQueue<false> &audio_queue);
|
||||
|
||||
@ -37,8 +37,8 @@ class Audio: public Outputs::Speaker::SampleSource {
|
||||
|
||||
// MARK: - SampleSource.
|
||||
void set_sample_volume_range(int16_t range);
|
||||
static constexpr bool get_is_stereo() { return true; } // Dave produces stereo sound.
|
||||
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::StereoSample *target);
|
||||
|
||||
private:
|
||||
Concurrency::AsyncTaskQueue<false> &audio_queue_;
|
||||
|
@ -41,7 +41,7 @@
|
||||
#include "../../Outputs/Log.hpp"
|
||||
#include "../../Outputs/Speaker/Implementation/CompoundSource.hpp"
|
||||
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
|
||||
#include "../../Outputs/Speaker/Implementation/SampleSource.hpp"
|
||||
#include "../../Outputs/Speaker/Implementation/BufferSource.hpp"
|
||||
|
||||
#include "../../Configurable/StandardOptions.hpp"
|
||||
#include "../../ClockReceiver/ForceInline.hpp"
|
||||
|
@ -28,7 +28,7 @@
|
||||
|
||||
#include "../../../Outputs/Speaker/Implementation/CompoundSource.hpp"
|
||||
#include "../../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
|
||||
#include "../../../Outputs/Speaker/Implementation/SampleSource.hpp"
|
||||
#include "../../../Outputs/Speaker/Implementation/BufferSource.hpp"
|
||||
|
||||
#include "../../../Storage/Tape/Tape.hpp"
|
||||
#include "../../../Storage/Tape/Parsers/Spectrum.hpp"
|
||||
|
@ -1533,7 +1533,7 @@
|
||||
4B680CE123A5553100451D43 /* 68000ComparativeTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = 68000ComparativeTests.mm; sourceTree = "<group>"; };
|
||||
4B680CE323A555CA00451D43 /* 68000 Comparative Tests */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "68000 Comparative Tests"; sourceTree = "<group>"; };
|
||||
4B683B002727BE6F0043E541 /* Amiga Blitter Tests */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "Amiga Blitter Tests"; sourceTree = "<group>"; };
|
||||
4B698D1A1FE768A100696C91 /* SampleSource.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = SampleSource.hpp; sourceTree = "<group>"; };
|
||||
4B698D1A1FE768A100696C91 /* BufferSource.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = BufferSource.hpp; sourceTree = "<group>"; };
|
||||
4B69DEB52AB79E4F0055B217 /* Instruction.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Instruction.cpp; sourceTree = "<group>"; };
|
||||
4B69FB3B1C4D908A00B5F0AA /* Tape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Tape.cpp; sourceTree = "<group>"; };
|
||||
4B69FB3C1C4D908A00B5F0AA /* Tape.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Tape.hpp; sourceTree = "<group>"; };
|
||||
@ -4004,7 +4004,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B8EF6071FE5AF830076CCDD /* LowpassSpeaker.hpp */,
|
||||
4B698D1A1FE768A100696C91 /* SampleSource.hpp */,
|
||||
4B698D1A1FE768A100696C91 /* BufferSource.hpp */,
|
||||
4B770A961FE9EE770026DC70 /* CompoundSource.hpp */,
|
||||
);
|
||||
path = Implementation;
|
||||
|
@ -562,7 +562,7 @@ int main(int argc, char *argv[]) {
|
||||
}
|
||||
std::cout << "." << std::endl << std::endl;
|
||||
|
||||
std::cout << "Further machine options:" << std::endl << std::endl;;
|
||||
std::cout << "Further machine options:" << std::endl << std::endl;
|
||||
|
||||
const auto targets = Machine::TargetsByMachineName(false);
|
||||
const auto runtime_options = Machine::AllOptionsByMachineName();
|
||||
|
153
Outputs/Speaker/Implementation/BufferSource.hpp
Normal file
153
Outputs/Speaker/Implementation/BufferSource.hpp
Normal file
@ -0,0 +1,153 @@
|
||||
//
|
||||
// SampleSource.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 17/12/2017.
|
||||
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
#include "../Speaker.hpp"
|
||||
|
||||
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
|
||||
by the template parameter to LowpassSpeaker.
|
||||
*/
|
||||
template <typename SourceT, bool stereo>
|
||||
class BufferSource {
|
||||
public:
|
||||
/*!
|
||||
Indicates whether this component will write stereo samples.
|
||||
*/
|
||||
static constexpr bool is_stereo = stereo;
|
||||
|
||||
/*!
|
||||
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).
|
||||
|
||||
No default implementation is provided.
|
||||
*/
|
||||
template <Action action>
|
||||
void apply_samples(std::size_t number_of_samples, typename SampleT<stereo>::type *target);
|
||||
|
||||
/*!
|
||||
@returns @c true if it is trivially true that a call to get_samples would just
|
||||
fill the target with zeroes; @c false if a call might return all zeroes or
|
||||
might not.
|
||||
*/
|
||||
bool is_zero_level() const { return false; }
|
||||
|
||||
/*!
|
||||
Sets the proper output range for this sample source; it should write values
|
||||
between 0 and volume.
|
||||
*/
|
||||
void set_sample_volume_range(std::int16_t volume);
|
||||
|
||||
/*!
|
||||
Permits a sample source to declare that, averaged over time, it will use only
|
||||
a certain proportion of the allocated volume range. This commonly happens
|
||||
in sample sources that use a time-multiplexed sound output — for example, if
|
||||
one were to output only every other sample then it would return 0.5.
|
||||
|
||||
This is permitted to vary over time but there is no contract as to when it will be
|
||||
used by a speaker. If it varies, it should do so very infrequently and only to
|
||||
represent changes in hardware configuration.
|
||||
*/
|
||||
double average_output_peak() const { return 1.0; }
|
||||
};
|
||||
|
||||
///
|
||||
template <typename SourceT, bool stereo, int divider = 1>
|
||||
struct SampleSource: public BufferSource<SourceT, stereo> {
|
||||
public:
|
||||
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--) {
|
||||
apply<mix>(*target, source.level());
|
||||
++target;
|
||||
source.advance();
|
||||
}
|
||||
} else {
|
||||
std::size_t c = 0;
|
||||
|
||||
// Fill in the tail of any partially-captured level.
|
||||
auto level = source.level();
|
||||
while(c < number_of_samples && master_divider_ != divider) {
|
||||
apply<mix>(target[c], level);
|
||||
++c;
|
||||
++master_divider_;
|
||||
}
|
||||
source.advance();
|
||||
|
||||
// Provide all full levels.
|
||||
int whole_steps = (number_of_samples - c) / divider;
|
||||
while(whole_steps--) {
|
||||
fill<mix>(&target[c], &target[c + divider], source.level());
|
||||
c += divider;
|
||||
source.advance();
|
||||
}
|
||||
|
||||
// Provide the head of a further partial capture.
|
||||
level = source.level();
|
||||
master_divider_ = number_of_samples - c;
|
||||
fill<mix>(&target[c], &target[number_of_samples], source.level());
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: use a concept here, when C++20 filters through.
|
||||
//
|
||||
// Until then: sample sources should implement this.
|
||||
auto level() const {
|
||||
return typename SampleT<stereo>::type();
|
||||
}
|
||||
void advance() {}
|
||||
|
||||
private:
|
||||
int master_divider_{};
|
||||
};
|
||||
|
||||
}
|
@ -8,21 +8,126 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "SampleSource.hpp"
|
||||
#include "BufferSource.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
#include <atomic>
|
||||
|
||||
namespace Outputs::Speaker {
|
||||
|
||||
/// @returns @c true if any of the templated sources is stereo; @c false otherwise.
|
||||
template <typename... S> constexpr bool is_stereo() {
|
||||
bool is_stereo = false;
|
||||
([&] {
|
||||
is_stereo |= S::is_stereo;
|
||||
}(), ...);
|
||||
return is_stereo;
|
||||
}
|
||||
|
||||
/// @returns @c true if the variadic template arguments are ordered as all stereo sources followed by
|
||||
/// all mono; @c false otherwise.
|
||||
template <typename... S> constexpr bool are_properly_ordered() {
|
||||
bool is_ordered = true;
|
||||
bool is_stereo = true;
|
||||
([&] {
|
||||
if(S::is_stereo && !is_stereo) {
|
||||
is_ordered = false;
|
||||
}
|
||||
is_stereo &= S::is_stereo;
|
||||
}(), ...);
|
||||
return is_ordered;
|
||||
}
|
||||
|
||||
/*!
|
||||
A CompoundSource adds together the sound generated by multiple individual SampleSources.
|
||||
An owner may optionally assign relative volumes.
|
||||
*/
|
||||
template <typename... T> class CompoundSource:
|
||||
public Outputs::Speaker::SampleSource {
|
||||
public Outputs::Speaker::BufferSource<CompoundSource<T...>, ::Outputs::Speaker::is_stereo<T...>()> {
|
||||
private:
|
||||
template <typename... S> class CompoundSourceHolder {
|
||||
public:
|
||||
static constexpr bool is_stereo = false;
|
||||
void set_scaled_volume_range(int16_t, double *, double) {}
|
||||
static constexpr std::size_t size() { return 0; }
|
||||
double total_scale(double *) const { return 0.0; }
|
||||
};
|
||||
|
||||
template <typename S, typename... R> class CompoundSourceHolder<S, R...> {
|
||||
public:
|
||||
CompoundSourceHolder(S &source, R &...next) : source_(source), next_source_(next...) {}
|
||||
|
||||
static constexpr bool is_stereo = S::is_stereo || CompoundSourceHolder<R...>::is_stereo;
|
||||
|
||||
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) {
|
||||
// There'll be only one place in the chain that this conversion happens, but it'll
|
||||
// happen there often. So avoid continuously reallocating.
|
||||
if(conversion_source_.size() < number_of_samples) {
|
||||
conversion_source_.resize(number_of_samples);
|
||||
}
|
||||
|
||||
// Populate the conversion buffer with this source and all below.
|
||||
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++) {
|
||||
Outputs::Speaker::apply<action>(target[c].left, StereoSample(conversion_source_[c]));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
constexpr bool is_final_source = sizeof...(R) == 0;
|
||||
|
||||
// Get the rest of the output, if any.
|
||||
if constexpr (!is_final_source) {
|
||||
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. Zero fill only if this is the final source (as everything above will be additive).
|
||||
if constexpr (is_final_source) {
|
||||
Outputs::Speaker::fill<action>(target, target + number_of_samples, typename SampleT<output_stereo>::type());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Add in this component's output.
|
||||
source_.template apply_samples<is_final_source ? Action::Store : Action::Mix>(number_of_samples, target);
|
||||
}
|
||||
|
||||
void set_scaled_volume_range(int16_t range, double *volumes, double scale) {
|
||||
const auto scaled_range = volumes[0] / double(source_.average_output_peak()) * double(range) / scale;
|
||||
source_.set_sample_volume_range(int16_t(scaled_range));
|
||||
next_source_.set_scaled_volume_range(range, &volumes[1], scale);
|
||||
}
|
||||
|
||||
static constexpr std::size_t size() {
|
||||
return 1 + CompoundSourceHolder<R...>::size();
|
||||
}
|
||||
|
||||
double total_scale(double *volumes) const {
|
||||
return (volumes[0] / source_.average_output_peak()) + next_source_.total_scale(&volumes[1]);
|
||||
}
|
||||
|
||||
private:
|
||||
S &source_;
|
||||
CompoundSourceHolder<R...> next_source_;
|
||||
std::vector<MonoSample> conversion_source_;
|
||||
};
|
||||
|
||||
public:
|
||||
using Sample = typename SampleT<::Outputs::Speaker::is_stereo<T...>()>::type;
|
||||
|
||||
// To ensure at most one mono to stereo conversion, require appropriate source ordering.
|
||||
static_assert(are_properly_ordered<T...>(), "Sources should be listed with all stereo sources before all mono sources");
|
||||
|
||||
CompoundSource(T &... sources) : source_holder_(sources...) {
|
||||
// Default: give all sources equal volume.
|
||||
const auto volume = 1.0 / double(source_holder_.size());
|
||||
@ -31,12 +136,9 @@ template <typename... T> class CompoundSource:
|
||||
}
|
||||
}
|
||||
|
||||
void get_samples(std::size_t number_of_samples, std::int16_t *target) {
|
||||
source_holder_.template get_samples<get_is_stereo()>(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);
|
||||
}
|
||||
|
||||
/*!
|
||||
@ -59,16 +161,11 @@ template <typename... T> class CompoundSource:
|
||||
average_output_peak_ = 1.0 / source_holder_.total_scale(volumes_.data());
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns true if any of the sources owned by this CompoundSource is stereo.
|
||||
*/
|
||||
static constexpr bool get_is_stereo() { return CompoundSourceHolder<T...>::get_is_stereo(); }
|
||||
|
||||
/*!
|
||||
@returns the average output peak given the sources owned by this CompoundSource and the
|
||||
current relative volumes.
|
||||
*/
|
||||
double get_average_output_peak() const {
|
||||
double average_output_peak() const {
|
||||
return average_output_peak_;
|
||||
}
|
||||
|
||||
@ -78,94 +175,6 @@ template <typename... T> class CompoundSource:
|
||||
source_holder_.set_scaled_volume_range(volume_range_, volumes_.data(), scale);
|
||||
}
|
||||
|
||||
template <typename... S> class CompoundSourceHolder: public Outputs::Speaker::SampleSource {
|
||||
public:
|
||||
template <bool output_stereo> void get_samples(std::size_t number_of_samples, std::int16_t *target) {
|
||||
std::memset(target, 0, sizeof(std::int16_t) * number_of_samples);
|
||||
}
|
||||
|
||||
void set_scaled_volume_range(int16_t, double *, double) {}
|
||||
|
||||
static constexpr std::size_t size() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static constexpr bool get_is_stereo() {
|
||||
return false;
|
||||
}
|
||||
|
||||
double total_scale(double *) const {
|
||||
return 0.0;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename S, typename... R> class CompoundSourceHolder<S, R...> {
|
||||
public:
|
||||
CompoundSourceHolder(S &source, R &...next) : source_(source), next_source_(next...) {}
|
||||
|
||||
template <bool output_stereo> void get_samples(std::size_t number_of_samples, std::int16_t *target) {
|
||||
// Get the rest of the output.
|
||||
next_source_.template get_samples<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.
|
||||
auto buffer_size = number_of_samples * (output_stereo ? 2 : 1);
|
||||
int16_t local_samples[buffer_size];
|
||||
source_.get_samples(number_of_samples, local_samples);
|
||||
|
||||
// Merge it in; furthermore if total output is stereo but this source isn't,
|
||||
// map it to stereo.
|
||||
if constexpr (output_stereo == S::get_is_stereo()) {
|
||||
while(buffer_size--) {
|
||||
target[buffer_size] += local_samples[buffer_size];
|
||||
}
|
||||
} else {
|
||||
// This will happen only if mapping from mono to stereo, never in the
|
||||
// other direction, because the compound source outputs stereo if any
|
||||
// subcomponent does. So it outputs mono only if no stereo devices are
|
||||
// in the mixing chain.
|
||||
while(buffer_size--) {
|
||||
target[buffer_size] += local_samples[buffer_size >> 1];
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
const auto scaled_range = volumes[0] / double(source_.get_average_output_peak()) * double(range) / scale;
|
||||
source_.set_sample_volume_range(int16_t(scaled_range));
|
||||
next_source_.set_scaled_volume_range(range, &volumes[1], scale);
|
||||
}
|
||||
|
||||
static constexpr std::size_t size() {
|
||||
return 1 + CompoundSourceHolder<R...>::size();
|
||||
}
|
||||
|
||||
static constexpr bool get_is_stereo() {
|
||||
return S::get_is_stereo() || CompoundSourceHolder<R...>::get_is_stereo();
|
||||
}
|
||||
|
||||
double total_scale(double *volumes) const {
|
||||
return (volumes[0] / source_.get_average_output_peak()) + next_source_.total_scale(&volumes[1]);
|
||||
}
|
||||
|
||||
private:
|
||||
S &source_;
|
||||
CompoundSourceHolder<R...> next_source_;
|
||||
};
|
||||
|
||||
CompoundSourceHolder<T...> source_holder_;
|
||||
std::vector<double> volumes_;
|
||||
int16_t volume_range_ = 0;
|
||||
|
@ -8,6 +8,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BufferSource.hpp"
|
||||
#include "../Speaker.hpp"
|
||||
#include "../../../SignalProcessing/FIRFilter.hpp"
|
||||
#include "../../../ClockReceiver/ClockReceiver.hpp"
|
||||
@ -348,7 +349,7 @@ template <bool is_stereo> class PushLowpass: public LowpassBase<PushLowpass<is_s
|
||||
source of a high-frequency stream of audio which it filters down to a
|
||||
lower-frequency output.
|
||||
*/
|
||||
template <typename SampleSource> class PullLowpass: public LowpassBase<PullLowpass<SampleSource>, SampleSource::get_is_stereo()> {
|
||||
template <typename SampleSource> class PullLowpass: public LowpassBase<PullLowpass<SampleSource>, SampleSource::is_stereo> {
|
||||
public:
|
||||
PullLowpass(SampleSource &sample_source) : sample_source_(sample_source) {
|
||||
// Propagate an initial volume level.
|
||||
@ -362,7 +363,7 @@ template <typename SampleSource> class PullLowpass: public LowpassBase<PullLowpa
|
||||
}
|
||||
|
||||
bool get_is_stereo() final {
|
||||
return SampleSource::get_is_stereo();
|
||||
return SampleSource::is_stereo;
|
||||
}
|
||||
|
||||
/*!
|
||||
@ -381,7 +382,7 @@ template <typename SampleSource> class PullLowpass: public LowpassBase<PullLowpa
|
||||
}
|
||||
|
||||
private:
|
||||
using BaseT = LowpassBase<PullLowpass<SampleSource>, SampleSource::get_is_stereo()>;
|
||||
using BaseT = LowpassBase<PullLowpass<SampleSource>, SampleSource::is_stereo>;
|
||||
friend BaseT;
|
||||
using BaseT::process;
|
||||
|
||||
@ -396,15 +397,20 @@ 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() {
|
||||
return int(65536.0 / sample_source_.get_average_output_peak());
|
||||
return int(65536.0 / sample_source_.average_output_peak());
|
||||
}
|
||||
|
||||
void get_samples(size_t length, int16_t *target) {
|
||||
sample_source_.get_samples(length, target);
|
||||
if constexpr (SampleSource::is_stereo) {
|
||||
StereoSample *const stereo_target = reinterpret_cast<StereoSample *>(target);
|
||||
sample_source_.template apply_samples<Action::Store>(length, stereo_target);
|
||||
} else {
|
||||
sample_source_.template apply_samples<Action::Store>(length, target);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1,72 +0,0 @@
|
||||
//
|
||||
// SampleSource.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 17/12/2017.
|
||||
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
namespace Outputs::Speaker {
|
||||
|
||||
/*!
|
||||
A sample source is something that can provide a stream of audio.
|
||||
This optional base class provides the interface expected to be exposed
|
||||
by the template parameter to LowpassSpeaker.
|
||||
*/
|
||||
class SampleSource {
|
||||
public:
|
||||
/*!
|
||||
Should write the next @c number_of_samples to @c target.
|
||||
*/
|
||||
void get_samples([[maybe_unused]] std::size_t number_of_samples, [[maybe_unused]] std::int16_t *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(const std::size_t number_of_samples) {
|
||||
std::int16_t 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
|
||||
fill the target with zeroes; @c false if a call might return all zeroes or
|
||||
might not.
|
||||
*/
|
||||
bool is_zero_level() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the proper output range for this sample source; it should write values
|
||||
between 0 and volume.
|
||||
*/
|
||||
void set_sample_volume_range([[maybe_unused]] std::int16_t volume) {}
|
||||
|
||||
/*!
|
||||
Indicates whether this component will write stereo samples.
|
||||
*/
|
||||
static constexpr bool get_is_stereo() { return false; }
|
||||
|
||||
/*!
|
||||
Permits a sample source to declare that, averaged over time, it will use only
|
||||
a certain proportion of the allocated volume range. This commonly happens
|
||||
in sample sources that use a time-multiplexed sound output — for example, if
|
||||
one were to output only every other sample then it would return 0.5.
|
||||
|
||||
This is permitted to vary over time but there is no contract as to when it will be
|
||||
used by a speaker. If it varies, it should do so very infrequently and only to
|
||||
represent changes in hardware configuration.
|
||||
*/
|
||||
double get_average_output_peak() const { return 1.0; }
|
||||
};
|
||||
|
||||
}
|
@ -14,6 +14,41 @@
|
||||
|
||||
namespace Outputs::Speaker {
|
||||
|
||||
using MonoSample = int16_t;
|
||||
struct StereoSample {
|
||||
#if TARGET_RT_BIG_ENDIAN
|
||||
int16_t right = 0, left = 0;
|
||||
#else
|
||||
int16_t left = 0, right = 0;
|
||||
#endif
|
||||
|
||||
StereoSample() = default;
|
||||
StereoSample(const StereoSample &rhs) {
|
||||
left = rhs.left;
|
||||
right = rhs.right;
|
||||
}
|
||||
StereoSample(MonoSample value) {
|
||||
left = right = value;
|
||||
}
|
||||
|
||||
StereoSample &operator +=(const StereoSample &rhs) {
|
||||
left += rhs.left;
|
||||
right += rhs.right;
|
||||
return *this;
|
||||
}
|
||||
|
||||
StereoSample operator +(const StereoSample &rhs) {
|
||||
StereoSample result;
|
||||
result.left = left + rhs.left;
|
||||
result.right = right + rhs.right;
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
template <bool stereo> struct SampleT {
|
||||
using type = std::conditional_t<stereo, StereoSample, MonoSample>;
|
||||
};
|
||||
|
||||
/*!
|
||||
Provides a communication point for sound; machines that have a speaker provide an
|
||||
audio output.
|
||||
|
@ -793,7 +793,7 @@ template <typename BusHandler, bool uses_ready_line> void Processor<BusHandler,
|
||||
assert(data_buffer_.size == 2 - m_flag());
|
||||
++data_buffer_.value;
|
||||
registers_.flags.set_nz(uint16_t(data_buffer_.value), registers_.m_shift);
|
||||
break;;
|
||||
break;
|
||||
|
||||
case DEC:
|
||||
assert(data_buffer_.size == 2 - m_flag());
|
||||
|
@ -110,7 +110,7 @@ template <> bool Reflection::set(Struct &target, const std::string &name, bool v
|
||||
if(!target_type) return false;
|
||||
|
||||
if(*target_type == typeid(bool)) {
|
||||
target.set(name, &value, offset);;
|
||||
target.set(name, &value, offset);
|
||||
}
|
||||
|
||||
return false;
|
||||
|
@ -37,7 +37,7 @@ void Parser::install_track(const Storage::Disk::Track::Address &address) {
|
||||
// Just try all three in succession.
|
||||
append(parse_track(*track, Density::Single), sectors_by_id);
|
||||
append(parse_track(*track, Density::Double), sectors_by_id);
|
||||
append(parse_track(*track, Density::High), sectors_by_id);;
|
||||
append(parse_track(*track, Density::High), sectors_by_id);
|
||||
}
|
||||
|
||||
sectors_by_address_by_track_.emplace(address, std::move(sectors_by_id));
|
||||
|
Loading…
Reference in New Issue
Block a user