mirror of
https://github.com/TomHarte/CLK.git
synced 2024-11-26 08:49:37 +00:00
Merge pull request #1330 from TomHarte/SampleProducingAY
Convert AY to a SampleSource.
This commit is contained in:
commit
d43f050922
@ -10,17 +10,24 @@
|
|||||||
|
|
||||||
#include "AY38910.hpp"
|
#include "AY38910.hpp"
|
||||||
|
|
||||||
//namespace GI {
|
|
||||||
//namespace AY38910 {
|
|
||||||
|
|
||||||
using namespace GI::AY38910;
|
using namespace GI::AY38910;
|
||||||
|
|
||||||
|
// Note on dividers: 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.
|
||||||
|
// Therefore this class implements a divider of 4 and doubles the tone
|
||||||
|
// and noise periods. The envelope ticks along at the divide-by-four rate,
|
||||||
|
// but if this is an AY rather than a YM then its lowest bit is forced to 1,
|
||||||
|
// matching the YM datasheet's depiction of envelope level 31 as equal to
|
||||||
|
// programmatic volume 15, envelope level 29 as equal to programmatic 14, etc.
|
||||||
|
|
||||||
template <bool is_stereo>
|
template <bool is_stereo>
|
||||||
AY38910<is_stereo>::AY38910(Personality personality, Concurrency::AsyncTaskQueue<false> &task_queue) : task_queue_(task_queue) {
|
AY38910SampleSource<is_stereo>::AY38910SampleSource(Personality personality, Concurrency::AsyncTaskQueue<false> &task_queue) : task_queue_(task_queue) {
|
||||||
// Don't use the low bit of the envelope position if this is an AY.
|
// Don't use the low bit of the envelope position if this is an AY.
|
||||||
envelope_position_mask_ |= personality == Personality::AY38910;
|
envelope_position_mask_ |= personality == Personality::AY38910;
|
||||||
|
|
||||||
// Set up envelope lookup tables.
|
// Set up envelope lookup tables; these are based on 32 volume levels as used by the YM2149F.
|
||||||
|
// The AY38910 will just use only even table entries, and therefore only even volumes.
|
||||||
for(int c = 0; c < 16; c++) {
|
for(int c = 0; c < 16; c++) {
|
||||||
for(int p = 0; p < 64; p++) {
|
for(int p = 0; p < 64; p++) {
|
||||||
switch(c) {
|
switch(c) {
|
||||||
@ -74,7 +81,8 @@ AY38910<is_stereo>::AY38910(Personality personality, Concurrency::AsyncTaskQueue
|
|||||||
set_sample_volume_range(0);
|
set_sample_volume_range(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <bool is_stereo> void AY38910<is_stereo>::set_sample_volume_range(std::int16_t range) {
|
template <bool is_stereo>
|
||||||
|
void AY38910SampleSource<is_stereo>::set_sample_volume_range(std::int16_t range) {
|
||||||
// Set up volume lookup table; the function below is based on a combination of the graph
|
// Set up volume lookup table; the function below is based on a combination of the graph
|
||||||
// from the YM's datasheet, showing a clear power curve, and fitting that to observed
|
// from the YM's datasheet, showing a clear power curve, and fitting that to observed
|
||||||
// values reported elsewhere.
|
// values reported elsewhere.
|
||||||
@ -92,7 +100,8 @@ template <bool is_stereo> void AY38910<is_stereo>::set_sample_volume_range(std::
|
|||||||
evaluate_output_volume();
|
evaluate_output_volume();
|
||||||
}
|
}
|
||||||
|
|
||||||
template <bool is_stereo> void AY38910<is_stereo>::set_output_mixing(float a_left, float b_left, float c_left, float a_right, float b_right, float c_right) {
|
template <bool is_stereo>
|
||||||
|
void AY38910SampleSource<is_stereo>::set_output_mixing(float a_left, float b_left, float c_left, float a_right, float b_right, float c_right) {
|
||||||
a_left_ = uint8_t(a_left * 255.0f);
|
a_left_ = uint8_t(a_left * 255.0f);
|
||||||
b_left_ = uint8_t(b_left * 255.0f);
|
b_left_ = uint8_t(b_left * 255.0f);
|
||||||
c_left_ = uint8_t(c_left * 255.0f);
|
c_left_ = uint8_t(c_left * 255.0f);
|
||||||
@ -102,81 +111,49 @@ template <bool is_stereo> void AY38910<is_stereo>::set_output_mixing(float a_lef
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <bool is_stereo>
|
template <bool is_stereo>
|
||||||
template <Outputs::Speaker::Action action>
|
void AY38910SampleSource<is_stereo>::advance() {
|
||||||
void AY38910<is_stereo>::apply_samples(
|
const auto step_channel = [&](int c) {
|
||||||
std::size_t number_of_samples,
|
if(tone_counters_[c]) --tone_counters_[c];
|
||||||
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.
|
|
||||||
// Therefore this class implements a divider of 4 and doubles the tone
|
|
||||||
// and noise periods. The envelope ticks along at the divide-by-four rate,
|
|
||||||
// but if this is an AY rather than a YM then its lowest bit is forced to 1,
|
|
||||||
// matching the YM datasheet's depiction of envelope level 31 as equal to
|
|
||||||
// programmatic volume 15, envelope level 29 as equal to programmatic 14, etc.
|
|
||||||
|
|
||||||
std::size_t c = 0;
|
|
||||||
while((master_divider_&3) && c < number_of_samples) {
|
|
||||||
Outputs::Speaker::apply<action>(target[c], output_volume_);
|
|
||||||
master_divider_++;
|
|
||||||
c++;
|
|
||||||
}
|
|
||||||
|
|
||||||
while(c < number_of_samples) {
|
|
||||||
#define step_channel(c) \
|
|
||||||
if(tone_counters_[c]) tone_counters_[c]--;\
|
|
||||||
else {\
|
|
||||||
tone_outputs_[c] ^= 1;\
|
|
||||||
tone_counters_[c] = tone_periods_[c] << 1;\
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the tone channels.
|
|
||||||
step_channel(0);
|
|
||||||
step_channel(1);
|
|
||||||
step_channel(2);
|
|
||||||
|
|
||||||
#undef step_channel
|
|
||||||
|
|
||||||
// Update the noise generator. This recomputes the new bit repeatedly but harmlessly, only shifting
|
|
||||||
// it into the official 17 upon divider underflow.
|
|
||||||
if(noise_counter_) noise_counter_--;
|
|
||||||
else {
|
else {
|
||||||
noise_counter_ = noise_period_ << 1; // To cover the double resolution of envelopes.
|
tone_outputs_[c] ^= 1;
|
||||||
noise_output_ ^= noise_shift_register_&1;
|
tone_counters_[c] = tone_periods_[c] << 1;
|
||||||
noise_shift_register_ |= ((noise_shift_register_ ^ (noise_shift_register_ >> 3))&1) << 17;
|
|
||||||
noise_shift_register_ >>= 1;
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Update the envelope generator. Table based for pattern lookup, with a 'refill' step: a way of
|
// Update the tone channels.
|
||||||
// implementing non-repeating patterns by locking them to the final table position.
|
step_channel(0);
|
||||||
if(envelope_divider_) envelope_divider_--;
|
step_channel(1);
|
||||||
else {
|
step_channel(2);
|
||||||
envelope_divider_ = envelope_period_;
|
|
||||||
envelope_position_ ++;
|
|
||||||
if(envelope_position_ == 64) envelope_position_ = envelope_overflow_masks_[output_registers_[13]];
|
|
||||||
}
|
|
||||||
|
|
||||||
evaluate_output_volume();
|
// Update the noise generator. This recomputes the new bit repeatedly but harmlessly, only shifting
|
||||||
|
// it into the official 17 upon divider underflow.
|
||||||
for(int ic = 0; ic < 4 && c < number_of_samples; ic++) {
|
if(noise_counter_) noise_counter_--;
|
||||||
Outputs::Speaker::apply<action>(target[c], output_volume_);
|
else {
|
||||||
c++;
|
noise_counter_ = noise_period_ << 1; // To cover the double resolution of envelopes.
|
||||||
master_divider_++;
|
noise_output_ ^= noise_shift_register_&1;
|
||||||
}
|
noise_shift_register_ |= ((noise_shift_register_ ^ (noise_shift_register_ >> 3))&1) << 17;
|
||||||
|
noise_shift_register_ >>= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
master_divider_ &= 3;
|
// Update the envelope generator. Table based for pattern lookup, with a 'refill' step: a way of
|
||||||
|
// implementing non-repeating patterns by locking them to the final table position.
|
||||||
|
if(envelope_divider_) envelope_divider_--;
|
||||||
|
else {
|
||||||
|
envelope_divider_ = envelope_period_;
|
||||||
|
envelope_position_ ++;
|
||||||
|
if(envelope_position_ == 64) envelope_position_ = envelope_overflow_masks_[output_registers_[13]];
|
||||||
|
}
|
||||||
|
|
||||||
|
evaluate_output_volume();
|
||||||
}
|
}
|
||||||
|
|
||||||
template void AY38910<false>::apply_samples<Outputs::Speaker::Action::Mix>(std::size_t, typename Outputs::Speaker::SampleT<false>::type *);
|
template <bool is_stereo>
|
||||||
template void AY38910<false>::apply_samples<Outputs::Speaker::Action::Store>(std::size_t, typename Outputs::Speaker::SampleT<false>::type *);
|
typename Outputs::Speaker::SampleT<is_stereo>::type AY38910SampleSource<is_stereo>::level() const {
|
||||||
template void AY38910<false>::apply_samples<Outputs::Speaker::Action::Ignore>(std::size_t, typename Outputs::Speaker::SampleT<false>::type *);
|
return output_volume_;
|
||||||
template void AY38910<true>::apply_samples<Outputs::Speaker::Action::Mix>(std::size_t, typename Outputs::Speaker::SampleT<true>::type *);
|
}
|
||||||
template void AY38910<true>::apply_samples<Outputs::Speaker::Action::Store>(std::size_t, typename Outputs::Speaker::SampleT<true>::type *);
|
|
||||||
template void AY38910<true>::apply_samples<Outputs::Speaker::Action::Ignore>(std::size_t, typename Outputs::Speaker::SampleT<true>::type *);
|
|
||||||
|
|
||||||
template <bool is_stereo> void AY38910<is_stereo>::evaluate_output_volume() {
|
template <bool is_stereo>
|
||||||
|
void AY38910SampleSource<is_stereo>::evaluate_output_volume() {
|
||||||
int envelope_volume = envelope_shapes_[output_registers_[13]][envelope_position_ | envelope_position_mask_];
|
int envelope_volume = envelope_shapes_[output_registers_[13]][envelope_position_ | envelope_position_mask_];
|
||||||
|
|
||||||
// The output level for a channel is:
|
// The output level for a channel is:
|
||||||
@ -237,18 +214,21 @@ template <bool is_stereo> void AY38910<is_stereo>::evaluate_output_volume() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <bool is_stereo> bool AY38910<is_stereo>::is_zero_level() const {
|
template <bool is_stereo>
|
||||||
|
bool AY38910SampleSource<is_stereo>::is_zero_level() const {
|
||||||
// Confirm that the AY is trivially at the zero level if all three volume controls are set to fixed zero.
|
// Confirm that the AY is trivially at the zero level if all three volume controls are set to fixed zero.
|
||||||
return output_registers_[0x8] == 0 && output_registers_[0x9] == 0 && output_registers_[0xa] == 0;
|
return output_registers_[0x8] == 0 && output_registers_[0x9] == 0 && output_registers_[0xa] == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Register manipulation
|
// MARK: - Register manipulation
|
||||||
|
|
||||||
template <bool is_stereo> void AY38910<is_stereo>::select_register(uint8_t r) {
|
template <bool is_stereo>
|
||||||
|
void AY38910SampleSource<is_stereo>::select_register(uint8_t r) {
|
||||||
selected_register_ = r;
|
selected_register_ = r;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <bool is_stereo> void AY38910<is_stereo>::set_register_value(uint8_t value) {
|
template <bool is_stereo>
|
||||||
|
void AY38910SampleSource<is_stereo>::set_register_value(uint8_t value) {
|
||||||
// There are only 16 registers.
|
// There are only 16 registers.
|
||||||
if(selected_register_ > 15) return;
|
if(selected_register_ > 15) return;
|
||||||
|
|
||||||
@ -317,7 +297,8 @@ template <bool is_stereo> void AY38910<is_stereo>::set_register_value(uint8_t va
|
|||||||
if(update_port_a) set_port_output(false);
|
if(update_port_a) set_port_output(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <bool is_stereo> uint8_t AY38910<is_stereo>::get_register_value() {
|
template <bool is_stereo>
|
||||||
|
uint8_t AY38910SampleSource<is_stereo>::get_register_value() {
|
||||||
// This table ensures that bits that aren't defined within the AY are returned as 0s
|
// This table ensures that bits that aren't defined within the AY are returned as 0s
|
||||||
// when read, conforming to CPC-sourced unit tests.
|
// when read, conforming to CPC-sourced unit tests.
|
||||||
const uint8_t register_masks[16] = {
|
const uint8_t register_masks[16] = {
|
||||||
@ -331,24 +312,28 @@ template <bool is_stereo> uint8_t AY38910<is_stereo>::get_register_value() {
|
|||||||
|
|
||||||
// MARK: - Port querying
|
// MARK: - Port querying
|
||||||
|
|
||||||
template <bool is_stereo> uint8_t AY38910<is_stereo>::get_port_output(bool port_b) {
|
template <bool is_stereo>
|
||||||
|
uint8_t AY38910SampleSource<is_stereo>::get_port_output(bool port_b) {
|
||||||
return registers_[port_b ? 15 : 14];
|
return registers_[port_b ? 15 : 14];
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Bus handling
|
// MARK: - Bus handling
|
||||||
|
|
||||||
template <bool is_stereo> void AY38910<is_stereo>::set_port_handler(PortHandler *handler) {
|
template <bool is_stereo>
|
||||||
|
void AY38910SampleSource<is_stereo>::set_port_handler(PortHandler *handler) {
|
||||||
port_handler_ = handler;
|
port_handler_ = handler;
|
||||||
set_port_output(true);
|
set_port_output(true);
|
||||||
set_port_output(false);
|
set_port_output(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <bool is_stereo> void AY38910<is_stereo>::set_data_input(uint8_t r) {
|
template <bool is_stereo>
|
||||||
|
void AY38910SampleSource<is_stereo>::set_data_input(uint8_t r) {
|
||||||
data_input_ = r;
|
data_input_ = r;
|
||||||
update_bus();
|
update_bus();
|
||||||
}
|
}
|
||||||
|
|
||||||
template <bool is_stereo> void AY38910<is_stereo>::set_port_output(bool port_b) {
|
template <bool is_stereo>
|
||||||
|
void AY38910SampleSource<is_stereo>::set_port_output(bool port_b) {
|
||||||
// Per the data sheet: "each [IO] pin is provided with an on-chip pull-up resistor,
|
// Per the data sheet: "each [IO] pin is provided with an on-chip pull-up resistor,
|
||||||
// so that when in the "input" mode, all pins will read normally high". Therefore,
|
// so that when in the "input" mode, all pins will read normally high". Therefore,
|
||||||
// report programmer selection of input mode as creating an output of 0xff.
|
// report programmer selection of input mode as creating an output of 0xff.
|
||||||
@ -358,7 +343,8 @@ template <bool is_stereo> void AY38910<is_stereo>::set_port_output(bool port_b)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <bool is_stereo> uint8_t AY38910<is_stereo>::get_data_output() {
|
template <bool is_stereo>
|
||||||
|
uint8_t AY38910SampleSource<is_stereo>::get_data_output() {
|
||||||
if(control_state_ == Read && selected_register_ >= 14 && selected_register_ < 16) {
|
if(control_state_ == Read && selected_register_ >= 14 && selected_register_ < 16) {
|
||||||
// Per http://cpctech.cpc-live.com/docs/psgnotes.htm if a port is defined as output then the
|
// Per http://cpctech.cpc-live.com/docs/psgnotes.htm if a port is defined as output then the
|
||||||
// value returned to the CPU when reading it is the and of the output value and any input.
|
// value returned to the CPU when reading it is the and of the output value and any input.
|
||||||
@ -374,7 +360,8 @@ template <bool is_stereo> uint8_t AY38910<is_stereo>::get_data_output() {
|
|||||||
return data_output_;
|
return data_output_;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <bool is_stereo> void AY38910<is_stereo>::set_control_lines(ControlLines control_lines) {
|
template <bool is_stereo>
|
||||||
|
void AY38910SampleSource<is_stereo>::set_control_lines(ControlLines control_lines) {
|
||||||
switch(int(control_lines)) {
|
switch(int(control_lines)) {
|
||||||
default: control_state_ = Inactive; break;
|
default: control_state_ = Inactive; break;
|
||||||
|
|
||||||
@ -389,7 +376,8 @@ template <bool is_stereo> void AY38910<is_stereo>::set_control_lines(ControlLine
|
|||||||
update_bus();
|
update_bus();
|
||||||
}
|
}
|
||||||
|
|
||||||
template <bool is_stereo> void AY38910<is_stereo>::update_bus() {
|
template <bool is_stereo>
|
||||||
|
void AY38910SampleSource<is_stereo>::update_bus() {
|
||||||
// Assume no output, unless this turns out to be a read.
|
// Assume no output, unless this turns out to be a read.
|
||||||
data_output_ = 0xff;
|
data_output_ = 0xff;
|
||||||
switch(control_state_) {
|
switch(control_state_) {
|
||||||
@ -401,5 +389,10 @@ template <bool is_stereo> void AY38910<is_stereo>::update_bus() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Ensure both mono and stereo versions of the AY are built.
|
// Ensure both mono and stereo versions of the AY are built.
|
||||||
template class GI::AY38910::AY38910<true>;
|
template class GI::AY38910::AY38910SampleSource<true>;
|
||||||
template class GI::AY38910::AY38910<false>;
|
template class GI::AY38910::AY38910SampleSource<false>;
|
||||||
|
|
||||||
|
// Perform an explicit instantiation of the BufferSource to hope for
|
||||||
|
// appropriate inlining of advance() and level().
|
||||||
|
template struct GI::AY38910::AY38910<true>;
|
||||||
|
template struct GI::AY38910::AY38910<false>;
|
||||||
|
@ -66,10 +66,10 @@ enum class Personality {
|
|||||||
|
|
||||||
This AY has an attached mono or stereo mixer.
|
This AY has an attached mono or stereo mixer.
|
||||||
*/
|
*/
|
||||||
template <bool stereo> class AY38910: public ::Outputs::Speaker::BufferSource<AY38910<stereo>, stereo> {
|
template <bool stereo> class AY38910SampleSource {
|
||||||
public:
|
public:
|
||||||
/// Creates a new AY38910.
|
/// Creates a new AY38910.
|
||||||
AY38910(Personality, Concurrency::AsyncTaskQueue<false> &);
|
AY38910SampleSource(Personality, Concurrency::AsyncTaskQueue<false> &);
|
||||||
|
|
||||||
/// Sets the value the AY would read from its data lines if it were not outputting.
|
/// Sets the value the AY would read from its data lines if it were not outputting.
|
||||||
void set_data_input(uint8_t r);
|
void set_data_input(uint8_t r);
|
||||||
@ -105,9 +105,9 @@ template <bool stereo> class AY38910: public ::Outputs::Speaker::BufferSource<AY
|
|||||||
*/
|
*/
|
||||||
void set_output_mixing(float a_left, float b_left, float c_left, float a_right = 1.0, float b_right = 1.0, float c_right = 1.0);
|
void set_output_mixing(float a_left, float b_left, float c_left, float a_right = 1.0, float b_right = 1.0, float c_right = 1.0);
|
||||||
|
|
||||||
// Buffer generation.
|
// Sample generation.
|
||||||
template <Outputs::Speaker::Action action>
|
typename Outputs::Speaker::SampleT<stereo>::type level() const;
|
||||||
void apply_samples(std::size_t number_of_samples, typename Outputs::Speaker::SampleT<stereo>::type *target);
|
void advance();
|
||||||
bool is_zero_level() const;
|
bool is_zero_level() const;
|
||||||
void set_sample_volume_range(std::int16_t range);
|
void set_sample_volume_range(std::int16_t range);
|
||||||
|
|
||||||
@ -118,8 +118,6 @@ template <bool stereo> class AY38910: public ::Outputs::Speaker::BufferSource<AY
|
|||||||
uint8_t registers_[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
|
uint8_t registers_[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
|
||||||
uint8_t output_registers_[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
|
uint8_t output_registers_[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
|
||||||
|
|
||||||
int master_divider_ = 0;
|
|
||||||
|
|
||||||
int tone_periods_[3] = {0, 0, 0};
|
int tone_periods_[3] = {0, 0, 0};
|
||||||
int tone_counters_[3] = {0, 0, 0};
|
int tone_counters_[3] = {0, 0, 0};
|
||||||
int tone_outputs_[3] = {0, 0, 0};
|
int tone_outputs_[3] = {0, 0, 0};
|
||||||
@ -166,6 +164,20 @@ template <bool stereo> class AY38910: public ::Outputs::Speaker::BufferSource<AY
|
|||||||
friend struct State;
|
friend struct State;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Defines a default AY to be the sample source with a master divider of 4;
|
||||||
|
/// real AYs have a divide-by-8 step built in but YMs apply only a divide by 4.
|
||||||
|
///
|
||||||
|
/// The implementation of AY38910SampleSource combines those two worlds
|
||||||
|
/// by always applying a divide by four and scaling other things as appropriate.
|
||||||
|
template <bool stereo> struct AY38910:
|
||||||
|
public AY38910SampleSource<stereo>,
|
||||||
|
public Outputs::Speaker::SampleSource<AY38910<stereo>, stereo, 4> {
|
||||||
|
|
||||||
|
// Use the same constructor as `AY38910SampleSource` (along with inheriting
|
||||||
|
// the rest of its interface).
|
||||||
|
using AY38910SampleSource<stereo>::AY38910SampleSource;
|
||||||
|
};
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Provides helper code, to provide something closer to the interface exposed by many
|
Provides helper code, to provide something closer to the interface exposed by many
|
||||||
AY-deploying machines of the era.
|
AY-deploying machines of the era.
|
||||||
|
@ -25,6 +25,9 @@ class Toggle: public Outputs::Speaker::BufferSource<Toggle, false> {
|
|||||||
Outputs::Speaker::fill<action>(target, target + number_of_samples, level_);
|
Outputs::Speaker::fill<action>(target, target + number_of_samples, level_);
|
||||||
}
|
}
|
||||||
void set_sample_volume_range(std::int16_t range);
|
void set_sample_volume_range(std::int16_t range);
|
||||||
|
bool is_zero_level() const {
|
||||||
|
return !level_;
|
||||||
|
}
|
||||||
|
|
||||||
void set_output(bool enabled);
|
void set_output(bool enabled);
|
||||||
bool get_output() const;
|
bool get_output() const;
|
||||||
|
@ -28,6 +28,7 @@ class OPLL: public OPLBase<OPLL, false> {
|
|||||||
template <Outputs::Speaker::Action action>
|
template <Outputs::Speaker::Action action>
|
||||||
void apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *target);
|
void apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *target);
|
||||||
void set_sample_volume_range(std::int16_t range);
|
void set_sample_volume_range(std::int16_t range);
|
||||||
|
bool is_zero_level() const { return false; } // TODO.
|
||||||
|
|
||||||
// The OPLL is generally 'half' as loud as it's told to be. This won't strictly be true in
|
// The OPLL is generally 'half' as loud as it's told to be. This won't strictly be true in
|
||||||
// rhythm mode, but it's correct for melodic output.
|
// rhythm mode, but it's correct for melodic output.
|
||||||
|
@ -37,6 +37,7 @@ class GLU: public Outputs::Speaker::BufferSource<GLU, false> { // TODO: isn't th
|
|||||||
template <Outputs::Speaker::Action action>
|
template <Outputs::Speaker::Action action>
|
||||||
void apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *target);
|
void apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *target);
|
||||||
void set_sample_volume_range(std::int16_t range);
|
void set_sample_volume_range(std::int16_t range);
|
||||||
|
bool is_zero_level() const { return false; } // TODO.
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Concurrency::AsyncTaskQueue<false> &audio_queue_;
|
Concurrency::AsyncTaskQueue<false> &audio_queue_;
|
||||||
|
@ -76,13 +76,13 @@ class BufferSource {
|
|||||||
fill the target with zeroes; @c false if a call might return all zeroes or
|
fill the target with zeroes; @c false if a call might return all zeroes or
|
||||||
might not.
|
might not.
|
||||||
*/
|
*/
|
||||||
bool is_zero_level() const { return false; }
|
// bool is_zero_level() const { return false; }
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Sets the proper output range for this sample source; it should write values
|
Sets the proper output range for this sample source; it should write values
|
||||||
between 0 and volume.
|
between 0 and volume.
|
||||||
*/
|
*/
|
||||||
void set_sample_volume_range(std::int16_t volume);
|
// void set_sample_volume_range(std::int16_t volume);
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Permits a sample source to declare that, averaged over time, it will use only
|
Permits a sample source to declare that, averaged over time, it will use only
|
||||||
@ -101,13 +101,13 @@ class BufferSource {
|
|||||||
template <typename SourceT, bool stereo, int divider = 1>
|
template <typename SourceT, bool stereo, int divider = 1>
|
||||||
struct SampleSource: public BufferSource<SourceT, stereo> {
|
struct SampleSource: public BufferSource<SourceT, stereo> {
|
||||||
public:
|
public:
|
||||||
template <bool mix>
|
template <Action action>
|
||||||
void apply_samples(std::size_t number_of_samples, typename SampleT<stereo>::type *target) {
|
void apply_samples(std::size_t number_of_samples, typename SampleT<stereo>::type *target) {
|
||||||
const auto &source = *static_cast<SourceT *>(this);
|
auto &source = *static_cast<SourceT *>(this);
|
||||||
|
|
||||||
if constexpr (divider == 1) {
|
if constexpr (divider == 1) {
|
||||||
while(number_of_samples--) {
|
while(number_of_samples--) {
|
||||||
apply<mix>(*target, source.level());
|
apply<action>(*target, source.level());
|
||||||
++target;
|
++target;
|
||||||
source.advance();
|
source.advance();
|
||||||
}
|
}
|
||||||
@ -117,34 +117,32 @@ struct SampleSource: public BufferSource<SourceT, stereo> {
|
|||||||
// Fill in the tail of any partially-captured level.
|
// Fill in the tail of any partially-captured level.
|
||||||
auto level = source.level();
|
auto level = source.level();
|
||||||
while(c < number_of_samples && master_divider_ != divider) {
|
while(c < number_of_samples && master_divider_ != divider) {
|
||||||
apply<mix>(target[c], level);
|
apply<action>(target[c], level);
|
||||||
++c;
|
++c;
|
||||||
++master_divider_;
|
++master_divider_;
|
||||||
}
|
}
|
||||||
source.advance();
|
source.advance();
|
||||||
|
|
||||||
// Provide all full levels.
|
// Provide all full levels.
|
||||||
int whole_steps = (number_of_samples - c) / divider;
|
auto whole_steps = static_cast<int>((number_of_samples - c) / divider);
|
||||||
while(whole_steps--) {
|
while(whole_steps--) {
|
||||||
fill<mix>(&target[c], &target[c + divider], source.level());
|
fill<action>(&target[c], &target[c + divider], source.level());
|
||||||
c += divider;
|
c += divider;
|
||||||
source.advance();
|
source.advance();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Provide the head of a further partial capture.
|
// Provide the head of a further partial capture.
|
||||||
level = source.level();
|
level = source.level();
|
||||||
master_divider_ = number_of_samples - c;
|
master_divider_ = static_cast<int>(number_of_samples - c);
|
||||||
fill<mix>(&target[c], &target[number_of_samples], source.level());
|
fill<action>(&target[c], &target[number_of_samples], source.level());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: use a concept here, when C++20 filters through.
|
// TODO: use a concept here, when C++20 filters through.
|
||||||
//
|
//
|
||||||
// Until then: sample sources should implement this.
|
// Until then: sample sources should implement this.
|
||||||
auto level() const {
|
// typename SampleT<stereo>::type level() const;
|
||||||
return typename SampleT<stereo>::type();
|
// void advance();
|
||||||
}
|
|
||||||
void advance() {}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int master_divider_{};
|
int master_divider_{};
|
||||||
|
Loading…
Reference in New Issue
Block a user