1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-12-27 01:31:42 +00:00

Finish updating components.

This commit is contained in:
Thomas Harte 2024-11-30 17:21:00 -05:00
parent 5545906063
commit 3addb8d72b
19 changed files with 666 additions and 648 deletions

View File

@ -18,10 +18,10 @@ Log::Logger<Log::Source::I2C> logger;
}
void Bus::set_data(bool pulled) {
void Bus::set_data(const bool pulled) {
set_clock_data(clock_, pulled);
}
bool Bus::data() {
bool Bus::data() const {
bool result = data_;
if(peripheral_bits_) {
result |= !(peripheral_response_ & 0x80);
@ -29,14 +29,14 @@ bool Bus::data() {
return result;
}
void Bus::set_clock(bool pulled) {
void Bus::set_clock(const bool pulled) {
set_clock_data(pulled, data_);
}
bool Bus::clock() {
bool Bus::clock() const {
return clock_;
}
void Bus::set_clock_data(bool clock_pulled, bool data_pulled) {
void Bus::set_clock_data(const bool clock_pulled, const bool data_pulled) {
// Proceed only if changes are evidenced.
if(clock_pulled == clock_ && data_pulled == data_) {
return;
@ -95,7 +95,7 @@ void Bus::set_clock_data(bool clock_pulled, bool data_pulled) {
}
}
void Bus::signal(Event event) {
void Bus::signal(const Event event) {
const auto capture_bit = [&]() {
input_ = uint16_t((input_ << 1) | (event == Event::Zero ? 0 : 1));
++input_count_;
@ -221,6 +221,6 @@ void Bus::signal(Event event) {
}
}
void Bus::add_peripheral(Peripheral *peripheral, int address) {
void Bus::add_peripheral(Peripheral *const peripheral, const int address) {
peripherals_[address] = peripheral;
}

View File

@ -41,10 +41,10 @@ struct Peripheral {
class Bus {
public:
void set_data(bool pulled);
bool data();
bool data() const;
void set_clock(bool pulled);
bool clock();
bool clock() const;
void set_clock_data(bool clock_pulled, bool data_pulled);

View File

@ -20,7 +20,7 @@ bool SCC::is_zero_level() const {
}
template <Outputs::Speaker::Action action>
void SCC::apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *target) {
void SCC::apply_samples(const std::size_t number_of_samples, Outputs::Speaker::MonoSample *const target) {
if(is_zero_level()) {
Outputs::Speaker::fill<action>(target, target + number_of_samples, Outputs::Speaker::MonoSample());
return;
@ -55,7 +55,7 @@ template void SCC::apply_samples<Outputs::Speaker::Action::Mix>(std::size_t, Out
template void SCC::apply_samples<Outputs::Speaker::Action::Store>(std::size_t, Outputs::Speaker::MonoSample *);
template void SCC::apply_samples<Outputs::Speaker::Action::Ignore>(std::size_t, Outputs::Speaker::MonoSample *);
void SCC::write(uint16_t address, uint8_t value) {
void SCC::write(uint16_t address, const uint8_t value) {
address &= 0xff;
if(address < 0x80) ram_[address] = value;
@ -108,7 +108,7 @@ void SCC::set_sample_volume_range(std::int16_t range) {
evaluate_output_volume();
}
uint8_t SCC::read(uint16_t address) {
uint8_t SCC::read(uint16_t address) const {
address &= 0xff;
if(address < 0x80) {
return ram_[address];

View File

@ -21,51 +21,51 @@ namespace Konami {
four and five, the SCC+ supports different waves for the two channels.
*/
class SCC: public ::Outputs::Speaker::BufferSource<SCC, false> {
public:
/// Creates a new SCC.
SCC(Concurrency::AsyncTaskQueue<false> &task_queue);
public:
/// Creates a new SCC.
SCC(Concurrency::AsyncTaskQueue<false> &);
/// As per ::SampleSource; provides a broadphase test for silence.
bool is_zero_level() const;
/// As per ::SampleSource; provides a broadphase test for silence.
bool is_zero_level() const;
/// As per ::SampleSource; provides audio output.
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);
/// As per ::SampleSource; provides audio output.
template <Outputs::Speaker::Action action>
void apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *);
void set_sample_volume_range(std::int16_t);
/// Writes to the SCC.
void write(uint16_t address, uint8_t value);
/// Writes to the SCC.
void write(uint16_t address, uint8_t value);
/// Reads from the SCC.
uint8_t read(uint16_t address);
/// Reads from the SCC.
uint8_t read(uint16_t address) const;
private:
Concurrency::AsyncTaskQueue<false> &task_queue_;
private:
Concurrency::AsyncTaskQueue<false> &task_queue_;
// State from here on down is accessed ony from the audio thread.
int master_divider_ = 0;
std::int16_t master_volume_ = 0;
Outputs::Speaker::MonoSample transient_output_level_ = 0;
// State from here on down is accessed ony from the audio thread.
int master_divider_ = 0;
std::int16_t master_volume_ = 0;
Outputs::Speaker::MonoSample transient_output_level_ = 0;
struct Channel {
int period = 0;
int amplitude = 0;
struct Channel {
int period = 0;
int amplitude = 0;
int tone_counter = 0;
int offset = 0;
} channels_[5];
int tone_counter = 0;
int offset = 0;
} channels_[5];
struct Wavetable {
std::uint8_t samples[32];
} waves_[4];
struct Wavetable {
std::uint8_t samples[32];
} waves_[4];
std::uint8_t channel_enable_ = 0;
std::uint8_t channel_enable_ = 0;
void evaluate_output_volume();
void evaluate_output_volume();
// This keeps a copy of wave memory that is accessed from the
// main emulation thread.
std::uint8_t ram_[128];
// This keeps a copy of wave memory that is accessed from the
// main emulation thread.
std::uint8_t ram_[128];
};
}

View File

@ -31,229 +31,229 @@ namespace Yamaha::OPL {
TODO: use envelope_precision.
*/
template <int envelope_precision, int period_precision> class EnvelopeGenerator {
public:
/*!
Advances the envelope generator a single step, given the current state of the low-frequency oscillator, @c oscillator.
*/
void update(const LowFrequencyOscillator &oscillator) {
// Apply tremolo, which is fairly easy.
tremolo_ = tremolo_enable_ * oscillator.tremolo << 4;
public:
/*!
Advances the envelope generator a single step, given the current state of the low-frequency oscillator, @c oscillator.
*/
void update(const LowFrequencyOscillator &oscillator) {
// Apply tremolo, which is fairly easy.
tremolo_ = tremolo_enable_ * oscillator.tremolo << 4;
// Something something something...
const int key_scaling_rate = key_scale_rate_ >> key_scale_rate_shift_;
switch(phase_) {
case Phase::Damp:
update_decay(oscillator, 12 << 2);
if(attenuation_ == 511) {
(*will_attack_)();
phase_ = Phase::Attack;
}
break;
case Phase::Attack:
update_attack(oscillator, attack_rate_ + key_scaling_rate);
// Two possible terminating conditions: (i) the attack rate is 15; (ii) full volume has been reached.
if(attenuation_ <= 0) {
attenuation_ = 0;
phase_ = Phase::Decay;
}
break;
case Phase::Decay:
update_decay(oscillator, decay_rate_ + key_scaling_rate);
if(attenuation_ >= sustain_level_) {
attenuation_ = sustain_level_;
phase_ = use_sustain_level_ ? Phase::Sustain : Phase::Release;
}
break;
case Phase::Sustain:
// Nothing to do.
break;
case Phase::Release:
update_decay(oscillator, release_rate_ + key_scaling_rate);
break;
}
}
/*!
@returns The current attenuation from this envelope generator. This is independent of the envelope precision.
*/
int attenuation() const {
// TODO: if this envelope is fully released, should tremolo still be able to vocalise it?
return (attenuation_ << 3) + tremolo_;
}
/*!
Enables or disables damping on this envelope generator. If damping is enabled then this envelope generator will
use the damping phase when necessary (i.e. when transitioning to key on if attenuation is not already at maximum)
and in any case will call @c will_attack before transitioning from any other state to attack.
@param will_attack Supply a will_attack callback to enable damping mode; supply nullopt to disable damping mode.
*/
void set_should_damp(const std::optional<std::function<void(void)>> &will_attack) {
will_attack_ = will_attack;
}
/*!
Sets the current state of the key-on input.
*/
void set_key_on(bool key_on) {
// Do nothing if this is not a leading or trailing edge.
if(key_on == key_on_) return;
key_on_ = key_on;
// Always transition to release upon a key off.
if(!key_on_) {
phase_ = Phase::Release;
return;
}
// On key on: if this is an envelope generator with damping, and damping is required,
// schedule that. If damping is not required, announce a pending attack now and
// transition to attack.
if(will_attack_) {
if(attenuation_ != 511) {
phase_ = Phase::Damp;
return;
// Something something something...
const int key_scaling_rate = key_scale_rate_ >> key_scale_rate_shift_;
switch(phase_) {
case Phase::Damp:
update_decay(oscillator, 12 << 2);
if(attenuation_ == 511) {
(*will_attack_)();
phase_ = Phase::Attack;
}
break;
(*will_attack_)();
}
phase_ = Phase::Attack;
case Phase::Attack:
update_attack(oscillator, attack_rate_ + key_scaling_rate);
// Two possible terminating conditions: (i) the attack rate is 15; (ii) full volume has been reached.
if(attenuation_ <= 0) {
attenuation_ = 0;
phase_ = Phase::Decay;
}
break;
case Phase::Decay:
update_decay(oscillator, decay_rate_ + key_scaling_rate);
if(attenuation_ >= sustain_level_) {
attenuation_ = sustain_level_;
phase_ = use_sustain_level_ ? Phase::Sustain : Phase::Release;
}
break;
case Phase::Sustain:
// Nothing to do.
break;
case Phase::Release:
update_decay(oscillator, release_rate_ + key_scaling_rate);
break;
}
}
/*!
@returns The current attenuation from this envelope generator. This is independent of the envelope precision.
*/
int attenuation() const {
// TODO: if this envelope is fully released, should tremolo still be able to vocalise it?
return (attenuation_ << 3) + tremolo_;
}
/*!
Enables or disables damping on this envelope generator. If damping is enabled then this envelope generator will
use the damping phase when necessary (i.e. when transitioning to key on if attenuation is not already at maximum)
and in any case will call @c will_attack before transitioning from any other state to attack.
@param will_attack Supply a will_attack callback to enable damping mode; supply nullopt to disable damping mode.
*/
void set_should_damp(const std::optional<std::function<void(void)>> &will_attack) {
will_attack_ = will_attack;
}
/*!
Sets the current state of the key-on input.
*/
void set_key_on(const bool key_on) {
// Do nothing if this is not a leading or trailing edge.
if(key_on == key_on_) return;
key_on_ = key_on;
// Always transition to release upon a key off.
if(!key_on_) {
phase_ = Phase::Release;
return;
}
/*!
Sets the attack rate, which should be in the range 015.
*/
void set_attack_rate(int rate) {
attack_rate_ = rate << 2;
}
/*!
Sets the decay rate, which should be in the range 015.
*/
void set_decay_rate(int rate) {
decay_rate_ = rate << 2;
}
/*!
Sets the release rate, which should be in the range 015.
*/
void set_release_rate(int rate) {
release_rate_ = rate << 2;
}
/*!
Sets the sustain level, which should be in the range 015.
*/
void set_sustain_level(int level) {
sustain_level_ = level << 3;
// TODO: verify the shift level here. Especially re: precision.
}
/*!
Enables or disables use of the sustain level. If this is disabled, the envelope proceeds
directly from decay to release.
*/
void set_use_sustain_level(bool use) {
use_sustain_level_ = use;
}
/*!
Enables or disables key-rate scaling.
*/
void set_key_scaling_rate_enabled(bool enabled) {
key_scale_rate_shift_ = int(enabled) * 2;
}
/*!
Enables or disables application of the low-frequency oscillator's tremolo.
*/
void set_tremolo_enabled(bool enabled) {
tremolo_enable_ = int(enabled);
}
/*!
Sets the current period associated with the channel that owns this envelope generator;
this is used to select a key scaling rate if key-rate scaling is enabled.
*/
void set_period(int period, int octave) {
key_scale_rate_ = (octave << 1) | (period >> (period_precision - 1));
}
private:
enum class Phase {
Attack, Decay, Sustain, Release, Damp
} phase_ = Phase::Release;
int attenuation_ = 511, tremolo_ = 0;
bool key_on_ = false;
std::optional<std::function<void(void)>> will_attack_;
int key_scale_rate_ = 0;
int key_scale_rate_shift_ = 0;
int tremolo_enable_ = 0;
int attack_rate_ = 0;
int decay_rate_ = 0;
int release_rate_ = 0;
int sustain_level_ = 0;
bool use_sustain_level_ = false;
static constexpr int dithering_patterns[4][8] = {
{0, 1, 0, 1, 0, 1, 0, 1},
{0, 1, 0, 1, 1, 1, 0, 1},
{0, 1, 1, 1, 0, 1, 1, 1},
{0, 1, 1, 1, 1, 1, 1, 1},
};
void update_attack(const LowFrequencyOscillator &oscillator, int rate) {
// Special case: no attack.
if(rate < 4) {
// On key on: if this is an envelope generator with damping, and damping is required,
// schedule that. If damping is not required, announce a pending attack now and
// transition to attack.
if(will_attack_) {
if(attenuation_ != 511) {
phase_ = Phase::Damp;
return;
}
// Special case: instant attack.
if(rate >= 60) {
attenuation_ = 0;
return;
}
(*will_attack_)();
}
phase_ = Phase::Attack;
}
// Work out the number of cycles between each adjustment tick, and stop now
// if not at the next adjustment boundary.
const int shift_size = 13 - (std::min(rate, 52) >> 2);
if(oscillator.counter & ((1 << shift_size) - 1)) {
return;
}
/*!
Sets the attack rate, which should be in the range 015.
*/
void set_attack_rate(const int rate) {
attack_rate_ = rate << 2;
}
// Apply dithered adjustment.
const int rate_shift = (rate > 55);
const int step = dithering_patterns[rate & 3][(oscillator.counter >> shift_size) & 7];
attenuation_ -= ((attenuation_ >> (3 - rate_shift)) + 1) * step;
/*!
Sets the decay rate, which should be in the range 015.
*/
void set_decay_rate(const int rate) {
decay_rate_ = rate << 2;
}
/*!
Sets the release rate, which should be in the range 015.
*/
void set_release_rate(const int rate) {
release_rate_ = rate << 2;
}
/*!
Sets the sustain level, which should be in the range 015.
*/
void set_sustain_level(const int level) {
sustain_level_ = level << 3;
// TODO: verify the shift level here. Especially re: precision.
}
/*!
Enables or disables use of the sustain level. If this is disabled, the envelope proceeds
directly from decay to release.
*/
void set_use_sustain_level(const bool use) {
use_sustain_level_ = use;
}
/*!
Enables or disables key-rate scaling.
*/
void set_key_scaling_rate_enabled(const bool enabled) {
key_scale_rate_shift_ = int(enabled) * 2;
}
/*!
Enables or disables application of the low-frequency oscillator's tremolo.
*/
void set_tremolo_enabled(const bool enabled) {
tremolo_enable_ = int(enabled);
}
/*!
Sets the current period associated with the channel that owns this envelope generator;
this is used to select a key scaling rate if key-rate scaling is enabled.
*/
void set_period(const int period, const int octave) {
key_scale_rate_ = (octave << 1) | (period >> (period_precision - 1));
}
private:
enum class Phase {
Attack, Decay, Sustain, Release, Damp
} phase_ = Phase::Release;
int attenuation_ = 511, tremolo_ = 0;
bool key_on_ = false;
std::optional<std::function<void(void)>> will_attack_;
int key_scale_rate_ = 0;
int key_scale_rate_shift_ = 0;
int tremolo_enable_ = 0;
int attack_rate_ = 0;
int decay_rate_ = 0;
int release_rate_ = 0;
int sustain_level_ = 0;
bool use_sustain_level_ = false;
static constexpr int dithering_patterns[4][8] = {
{0, 1, 0, 1, 0, 1, 0, 1},
{0, 1, 0, 1, 1, 1, 0, 1},
{0, 1, 1, 1, 0, 1, 1, 1},
{0, 1, 1, 1, 1, 1, 1, 1},
};
void update_attack(const LowFrequencyOscillator &oscillator, const int rate) {
// Special case: no attack.
if(rate < 4) {
return;
}
void update_decay(const LowFrequencyOscillator &oscillator, int rate) {
// Special case: no decay.
if(rate < 4) {
return;
}
// Work out the number of cycles between each adjustment tick, and stop now
// if not at the next adjustment boundary.
const int shift_size = 13 - (std::min(rate, 52) >> 2);
if(oscillator.counter & ((1 << shift_size) - 1)) {
return;
}
// Apply dithered adjustment and clamp.
const int rate_shift = 1 + (rate > 59) + (rate > 55);
attenuation_ += dithering_patterns[rate & 3][(oscillator.counter >> shift_size) & 7] * (4 << rate_shift);
attenuation_ = std::min(attenuation_, 511);
// Special case: instant attack.
if(rate >= 60) {
attenuation_ = 0;
return;
}
// Work out the number of cycles between each adjustment tick, and stop now
// if not at the next adjustment boundary.
const int shift_size = 13 - (std::min(rate, 52) >> 2);
if(oscillator.counter & ((1 << shift_size) - 1)) {
return;
}
// Apply dithered adjustment.
const int rate_shift = (rate > 55);
const int step = dithering_patterns[rate & 3][(oscillator.counter >> shift_size) & 7];
attenuation_ -= ((attenuation_ >> (3 - rate_shift)) + 1) * step;
}
void update_decay(const LowFrequencyOscillator &oscillator, const int rate) {
// Special case: no decay.
if(rate < 4) {
return;
}
// Work out the number of cycles between each adjustment tick, and stop now
// if not at the next adjustment boundary.
const int shift_size = 13 - (std::min(rate, 52) >> 2);
if(oscillator.counter & ((1 << shift_size) - 1)) {
return;
}
// Apply dithered adjustment and clamp.
const int rate_shift = 1 + (rate > 59) + (rate > 55);
attenuation_ += dithering_patterns[rate & 3][(oscillator.counter >> shift_size) & 7] * (4 << rate_shift);
attenuation_ = std::min(attenuation_, 511);
}
};
}

View File

@ -11,42 +11,42 @@
namespace Yamaha::OPL {
template <int frequency_precision> class KeyLevelScaler {
public:
public:
/*!
Sets the current period associated with the channel that owns this envelope generator;
this is used to select a key scaling rate if key-rate scaling is enabled.
*/
void set_period(int period, int octave) {
constexpr int key_level_scales[16] = {0, 48, 64, 74, 80, 86, 90, 94, 96, 100, 102, 104, 106, 108, 110, 112};
constexpr int masks[2] = {~0, 0};
/*!
Sets the current period associated with the channel that owns this envelope generator;
this is used to select a key scaling rate if key-rate scaling is enabled.
*/
void set_period(const int period, const int octave) {
constexpr int key_level_scales[16] = {0, 48, 64, 74, 80, 86, 90, 94, 96, 100, 102, 104, 106, 108, 110, 112};
constexpr int masks[2] = {~0, 0};
// A two's complement assumption is embedded below; the use of masks relies
// on the sign bit to clamp to zero.
level_ = key_level_scales[period >> (frequency_precision - 4)];
level_ -= 16 * (octave ^ 7);
level_ &= masks[(level_ >> ((sizeof(int) * 8) - 1)) & 1];
}
// A two's complement assumption is embedded below; the use of masks relies
// on the sign bit to clamp to zero.
level_ = key_level_scales[period >> (frequency_precision - 4)];
level_ -= 16 * (octave ^ 7);
level_ &= masks[(level_ >> ((sizeof(int) * 8) - 1)) & 1];
}
/*!
Enables or disables key-rate scaling.
*/
void set_key_scaling_level(int level) {
// '7' is just a number large enough to render all possible scaling coefficients as 0.
constexpr int key_level_scale_shifts[4] = {7, 1, 2, 0};
shift_ = key_level_scale_shifts[level];
}
/*!
Enables or disables key-rate scaling.
*/
void set_key_scaling_level(const int level) {
// '7' is just a number large enough to render all possible scaling coefficients as 0.
constexpr int key_level_scale_shifts[4] = {7, 1, 2, 0};
shift_ = key_level_scale_shifts[level];
}
/*!
@returns The current attenuation level due to key-level scaling.
*/
int attenuation() const {
return level_ >> shift_;
}
/*!
@returns The current attenuation level due to key-level scaling.
*/
int attenuation() const {
return level_ >> shift_;
}
private:
int level_ = 0;
int shift_ = 0;
private:
int level_ = 0;
int shift_ = 0;
};
}

View File

@ -18,46 +18,46 @@ namespace Yamaha::OPL {
as part of their ADSR envelope.
*/
class LowFrequencyOscillator {
public:
/// Current attenuation due to tremolo / amplitude modulation, between 0 and 26.
int tremolo = 0;
public:
/// Current attenuation due to tremolo / amplitude modulation, between 0 and 26.
int tremolo = 0;
/// A number between 0 and 7 indicating the current vibrato offset; this should be combined by operators
/// with their frequency number to get the actual vibrato.
int vibrato = 0;
/// A number between 0 and 7 indicating the current vibrato offset; this should be combined by operators
/// with their frequency number to get the actual vibrato.
int vibrato = 0;
/// A counter of the number of operator update cycles (i.e. input clock / 72) since an arbitrary time.
int counter = 0;
/// A counter of the number of operator update cycles (i.e. input clock / 72) since an arbitrary time.
int counter = 0;
/// Describes the current output of the LFSR; will be either 0 or 1.
int lfsr = 0;
/// Describes the current output of the LFSR; will be either 0 or 1.
int lfsr = 0;
/// Updates the oscillator outputs. Should be called at the (input clock/72) rate.
void update() {
++counter;
/// Updates the oscillator outputs. Should be called at the (input clock/72) rate.
void update() {
++counter;
// This produces output of:
//
// four instances of 0, four instances of 1... _three_ instances of 26,
// four instances of 25, four instances of 24... _three_ instances of 0.
//
// ... advancing once every 64th update.
const int tremolo_index = (counter >> 6) % 210;
const int tremolo_levels[2] = {tremolo_index >> 2, 52 - ((tremolo_index+1) >> 2)};
tremolo = tremolo_levels[tremolo_index / 107];
// This produces output of:
//
// four instances of 0, four instances of 1... _three_ instances of 26,
// four instances of 25, four instances of 24... _three_ instances of 0.
//
// ... advancing once every 64th update.
const int tremolo_index = (counter >> 6) % 210;
const int tremolo_levels[2] = {tremolo_index >> 2, 52 - ((tremolo_index+1) >> 2)};
tremolo = tremolo_levels[tremolo_index / 107];
// Vibrato is relatively simple: it's just three bits from the counter.
vibrato = (counter >> 10) & 7;
}
// Vibrato is relatively simple: it's just three bits from the counter.
vibrato = (counter >> 10) & 7;
}
/// Updartes the LFSR output. Should be called at the input clock rate.
void update_lfsr() {
lfsr = noise_source_.next();
}
/// Updartes the LFSR output. Should be called at the input clock rate.
void update_lfsr() {
lfsr = noise_source_.next();
}
private:
// This is the correct LSFR per forums.submarine.org.uk.
Numeric::LFSR<int, 0x800302> noise_source_;
private:
// This is the correct LSFR per forums.submarine.org.uk.
Numeric::LFSR<int, 0x800302> noise_source_;
};
}

View File

@ -14,22 +14,22 @@
namespace Yamaha::OPL {
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) {
static_cast<Child *>(this)->write_register(selected_register_, value);
} else {
selected_register_ = value;
}
public:
void write(const uint16_t address, const uint8_t value) {
if(address & 1) {
static_cast<Child *>(this)->write_register(selected_register_, value);
} else {
selected_register_ = value;
}
}
protected:
OPLBase(Concurrency::AsyncTaskQueue<false> &task_queue) : task_queue_(task_queue) {}
protected:
OPLBase(Concurrency::AsyncTaskQueue<false> &task_queue) : task_queue_(task_queue) {}
Concurrency::AsyncTaskQueue<false> &task_queue_;
Concurrency::AsyncTaskQueue<false> &task_queue_;
private:
uint8_t selected_register_ = 0;
private:
uint8_t selected_register_ = 0;
};
}

View File

@ -19,102 +19,102 @@ namespace Yamaha::OPL {
multiple, and whether to apply vibrato, this will then appropriately update and return phase.
*/
template <int precision> class PhaseGenerator {
public:
/*!
Advances the phase generator a single step, given the current state of the low-frequency oscillator, @c oscillator.
*/
void update(const LowFrequencyOscillator &oscillator) {
constexpr int vibrato_shifts[4] = {3, 1, 0, 1};
constexpr int vibrato_signs[2] = {1, -1};
public:
/*!
Advances the phase generator a single step, given the current state of the low-frequency oscillator, @c oscillator.
*/
void update(const LowFrequencyOscillator &oscillator) {
constexpr int vibrato_shifts[4] = {3, 1, 0, 1};
constexpr int vibrato_signs[2] = {1, -1};
// Get just the top three bits of the period_.
const int top_freq = period_ >> (precision - 3);
// Get just the top three bits of the period_.
const int top_freq = period_ >> (precision - 3);
// Cacluaute applicable vibrato as a function of (i) the top three bits of the
// oscillator period; (ii) the current low-frequency oscillator vibrato output; and
// (iii) whether vibrato is enabled.
const int vibrato = (top_freq >> vibrato_shifts[oscillator.vibrato & 3]) * vibrato_signs[oscillator.vibrato >> 2] * enable_vibrato_;
// Cacluaute applicable vibrato as a function of (i) the top three bits of the
// oscillator period; (ii) the current low-frequency oscillator vibrato output; and
// (iii) whether vibrato is enabled.
const int vibrato = (top_freq >> vibrato_shifts[oscillator.vibrato & 3]) * vibrato_signs[oscillator.vibrato >> 2] * enable_vibrato_;
// Apply phase update with vibrato from the low-frequency oscillator.
phase_ += (multiple_ * ((period_ << 1) + vibrato) << octave_) >> 1;
}
// Apply phase update with vibrato from the low-frequency oscillator.
phase_ += (multiple_ * ((period_ << 1) + vibrato) << octave_) >> 1;
}
/*!
@returns Current phase; real hardware provides only the low ten bits of this result.
*/
int phase() const {
// My table if multipliers is multiplied by two, so shift by one more
// than the stated precision.
return phase_ >> precision_shift;
}
/*!
@returns Current phase; real hardware provides only the low ten bits of this result.
*/
int phase() const {
// My table if multipliers is multiplied by two, so shift by one more
// than the stated precision.
return phase_ >> precision_shift;
}
/*!
@returns Current phase, scaled up by (1 << precision).
*/
int scaled_phase() const {
return phase_ >> 1;
}
/*!
@returns Current phase, scaled up by (1 << precision).
*/
int scaled_phase() const {
return phase_ >> 1;
}
/*!
Applies feedback based on two historic samples of a total output level,
plus the degree of feedback to apply
*/
void apply_feedback(LogSign first, LogSign second, int level) {
constexpr int masks[] = {0, ~0, ~0, ~0, ~0, ~0, ~0, ~0};
phase_ += ((second.level(precision) + first.level(precision)) >> (8 - level)) & masks[level];
}
/*!
Applies feedback based on two historic samples of a total output level,
plus the degree of feedback to apply
*/
void apply_feedback(const LogSign first, const LogSign second, const int level) {
constexpr int masks[] = {0, ~0, ~0, ~0, ~0, ~0, ~0, ~0};
phase_ += ((second.level(precision) + first.level(precision)) >> (8 - level)) & masks[level];
}
/*!
Sets the multiple for this phase generator, in the same terms as an OPL programmer,
i.e. a 4-bit number that is used as a lookup into the internal multiples table.
*/
void set_multiple(int multiple) {
// This encodes the MUL -> multiple table given on page 12,
// multiplied by two.
constexpr int multipliers[] = {
1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 20, 24, 24, 30, 30
};
assert(multiple < 16);
multiple_ = multipliers[multiple];
}
/*!
Sets the multiple for this phase generator, in the same terms as an OPL programmer,
i.e. a 4-bit number that is used as a lookup into the internal multiples table.
*/
void set_multiple(const int multiple) {
// This encodes the MUL -> multiple table given on page 12,
// multiplied by two.
constexpr int multipliers[] = {
1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 20, 24, 24, 30, 30
};
assert(multiple < 16);
multiple_ = multipliers[multiple];
}
/*!
Sets the period of this generator, along with its current octave.
/*!
Sets the period of this generator, along with its current octave.
Yamaha tends to refer to the period as the 'f-number', and used both 'octave' and 'block' for octave.
*/
void set_period(int period, int octave) {
period_ = period;
octave_ = octave;
Yamaha tends to refer to the period as the 'f-number', and used both 'octave' and 'block' for octave.
*/
void set_period(const int period, const int octave) {
period_ = period;
octave_ = octave;
assert(octave_ < 8);
assert(period_ < (1 << precision));
}
assert(octave_ < 8);
assert(period_ < (1 << precision));
}
/*!
Enables or disables vibrato.
*/
void set_vibrato_enabled(bool enabled) {
enable_vibrato_ = int(enabled);
}
/*!
Enables or disables vibrato.
*/
void set_vibrato_enabled(const bool enabled) {
enable_vibrato_ = int(enabled);
}
/*!
Resets the current phase.
*/
void reset() {
phase_ = 0;
}
/*!
Resets the current phase.
*/
void reset() {
phase_ = 0;
}
private:
static constexpr int precision_shift = 1 + precision;
private:
static constexpr int precision_shift = 1 + precision;
int phase_ = 0;
int phase_ = 0;
int multiple_ = 0;
int period_ = 0;
int octave_ = 0;
int enable_vibrato_ = 0;
int multiple_ = 0;
int period_ = 0;
int octave_ = 0;
int enable_vibrato_ = 0;
};
}

View File

@ -32,12 +32,12 @@ struct LogSign {
sign = 1;
}
LogSign &operator +=(int attenuation) {
LogSign &operator +=(const int attenuation) {
log += attenuation;
return *this;
}
LogSign &operator +=(LogSign log_sign) {
LogSign &operator +=(const LogSign log_sign) {
log += log_sign.log;
sign *= log_sign.sign;
return *this;
@ -49,7 +49,7 @@ struct LogSign {
/*!
@returns Negative log sin of x, assuming a 1024-unit circle.
*/
constexpr LogSign negative_log_sin(int x) {
constexpr LogSign negative_log_sin(const int x) {
/// Defines the first quadrant of 1024-unit negative log to the base two of sine (that conveniently misses sin(0)).
///
/// Expected branchless usage for a full 1024 unit output:
@ -107,7 +107,7 @@ constexpr LogSign negative_log_sin(int x) {
Computes the linear value represented by the log-sign @c ls, shifted left by @c fractional prior
to loss of precision.
*/
constexpr int power_two(LogSign ls, int fractional = 0) {
constexpr int power_two(const LogSign ls, const int fractional = 0) {
/// A derivative of the exponent table in a real OPL2; mapped_exp[x] = (source[c ^ 0xff] << 1) | 0x800.
///
/// The ahead-of-time transformation represents fixed work the OPL2 does when reading its table
@ -215,7 +215,7 @@ constexpr uint8_t percussion_patch_set[] = {
};
inline int LogSign::level(int fractional) const {
inline int LogSign::level(const int fractional) const {
return power_two(*this, fractional);
}

View File

@ -18,70 +18,74 @@ enum class Waveform {
};
template <int phase_precision> class WaveformGenerator {
public:
/*!
@returns The output of waveform @c form at [integral] phase @c phase.
*/
static constexpr LogSign wave(Waveform form, int phase) {
constexpr int waveforms[4][4] = {
{1023, 1023, 1023, 1023}, // Sine: don't mask in any quadrant.
{511, 511, 0, 0}, // Half sine: keep the first half intact, lock to 0 in the second half.
{511, 511, 511, 511}, // AbsSine: endlessly repeat the first half of the sine wave.
{255, 0, 255, 0}, // PulseSine: act as if the first quadrant is in the first and third; lock the other two to 0.
};
return negative_log_sin(phase & waveforms[int(form)][(phase >> 8) & 3]);
}
public:
/*!
@returns The output of waveform @c form at [integral] phase @c phase.
*/
static constexpr LogSign wave(Waveform form, int phase) {
constexpr int waveforms[4][4] = {
{1023, 1023, 1023, 1023}, // Sine: don't mask in any quadrant.
{511, 511, 0, 0}, // Half sine: keep the first half intact, lock to 0 in the second half.
{511, 511, 511, 511}, // AbsSine: endlessly repeat the first half of the sine wave.
{255, 0, 255, 0}, // PulseSine: act as if the first quadrant is in the first and third; lock the other two to 0.
};
return negative_log_sin(phase & waveforms[int(form)][(phase >> 8) & 3]);
}
/*!
@returns The output of waveform @c form at [scaled] phase @c scaled_phase given the modulation input @c modulation.
*/
static constexpr LogSign wave(Waveform form, int scaled_phase, LogSign modulation) {
const int scaled_phase_offset = modulation.level(phase_precision);
const int phase = (scaled_phase + scaled_phase_offset) >> phase_precision;
return wave(form, phase);
}
/*!
@returns The output of waveform @c form at [scaled] phase @c scaled_phase given the modulation input @c modulation.
*/
static constexpr LogSign wave(const Waveform form, const int scaled_phase, const LogSign modulation) {
const int scaled_phase_offset = modulation.level(phase_precision);
const int phase = (scaled_phase + scaled_phase_offset) >> phase_precision;
return wave(form, phase);
}
/*!
@returns Snare output, calculated from the current LFSR state as captured in @c oscillator and an operator's phase.
*/
static constexpr LogSign snare(const LowFrequencyOscillator &oscillator, int phase) {
// If noise is 0, output is positive.
// If noise is 1, output is negative.
// If (noise ^ sign) is 0, output is 0. Otherwise it is max.
const int sign = phase & 0x200;
const int level = ((phase >> 9) & 1) ^ oscillator.lfsr;
return negative_log_sin(sign + (level << 8));
}
/*!
@returns Snare output, calculated from the current LFSR state as captured in @c oscillator and an operator's phase.
*/
static constexpr LogSign snare(const LowFrequencyOscillator &oscillator, const int phase) {
// If noise is 0, output is positive.
// If noise is 1, output is negative.
// If (noise ^ sign) is 0, output is 0. Otherwise it is max.
const int sign = phase & 0x200;
const int level = ((phase >> 9) & 1) ^ oscillator.lfsr;
return negative_log_sin(sign + (level << 8));
}
/*!
@returns Cymbal output, calculated from an operator's phase and a modulator's phase.
*/
static constexpr LogSign cymbal(int carrier_phase, int modulator_phase) {
return negative_log_sin(256 + (phase_combination(carrier_phase, modulator_phase) << 9));
}
/*!
@returns Cymbal output, calculated from an operator's phase and a modulator's phase.
*/
static constexpr LogSign cymbal(const int carrier_phase, const int modulator_phase) {
return negative_log_sin(256 + (phase_combination(carrier_phase, modulator_phase) << 9));
}
/*!
@returns High-hat output, calculated from the current LFSR state as captured in @c oscillator, an operator's phase and a modulator's phase.
*/
static constexpr LogSign high_hat(const LowFrequencyOscillator &oscillator, int carrier_phase, int modulator_phase) {
constexpr int angles[] = {0x234, 0xd0, 0x2d0, 0x34};
return negative_log_sin(angles[
phase_combination(carrier_phase, modulator_phase) |
(oscillator.lfsr << 1)
]);
}
/*!
@returns High-hat output, calculated from the current LFSR state as captured in @c oscillator, an operator's phase and a modulator's phase.
*/
static constexpr LogSign high_hat(
const LowFrequencyOscillator &oscillator,
const int carrier_phase,
const int modulator_phase
) {
constexpr int angles[] = {0x234, 0xd0, 0x2d0, 0x34};
return negative_log_sin(angles[
phase_combination(carrier_phase, modulator_phase) |
(oscillator.lfsr << 1)
]);
}
private:
/*!
@returns The phase bit used for cymbal and high-hat generation, which is a function of two operators' phases.
*/
static constexpr int phase_combination(int carrier_phase, int modulator_phase) {
return (
((carrier_phase >> 5) ^ (carrier_phase >> 3)) &
((modulator_phase >> 7) ^ (modulator_phase >> 2)) &
((carrier_phase >> 5) ^ (modulator_phase >> 3))
) & 1;
}
private:
/*!
@returns The phase bit used for cymbal and high-hat generation, which is a function of two operators' phases.
*/
static constexpr int phase_combination(const int carrier_phase, const int modulator_phase) {
return (
((carrier_phase >> 5) ^ (carrier_phase >> 3)) &
((modulator_phase >> 7) ^ (modulator_phase >> 2)) &
((carrier_phase >> 5) ^ (modulator_phase >> 3))
) & 1;
}
};
}

View File

@ -12,7 +12,7 @@
using namespace Yamaha::OPL;
OPLL::OPLL(Concurrency::AsyncTaskQueue<false> &task_queue, int audio_divider, bool is_vrc7):
OPLL::OPLL(Concurrency::AsyncTaskQueue<false> &task_queue, const int audio_divider, const bool is_vrc7):
OPLBase(task_queue), audio_divider_(audio_divider), is_vrc7_(is_vrc7) {
// Due to the way that sound mixing works on the OPLL, the audio divider may not
// be larger than 4.
@ -71,7 +71,7 @@ OPLL::OPLL(Concurrency::AsyncTaskQueue<false> &task_queue, int audio_divider, bo
// MARK: - Machine-facing programmatic input.
void OPLL::write_register(uint8_t address, uint8_t value) {
void OPLL::write_register(const uint8_t address, const uint8_t value) {
// The OPLL doesn't have timers or other non-audio functions, so all writes
// go to the audio queue.
task_queue_.enqueue([this, address, value] {
@ -172,7 +172,7 @@ void OPLL::write_register(uint8_t address, uint8_t value) {
});
}
void OPLL::set_channel_period(int channel) {
void OPLL::set_channel_period(const int channel) {
phase_generators_[channel + 0].set_period(channels_[channel].period, channels_[channel].octave);
phase_generators_[channel + 9].set_period(channels_[channel].period, channels_[channel].octave);
@ -183,7 +183,7 @@ void OPLL::set_channel_period(int channel) {
key_level_scalers_[channel + 9].set_period(channels_[channel].period, channels_[channel].octave);
}
const uint8_t *OPLL::instrument_definition(int instrument, int channel) {
const uint8_t *OPLL::instrument_definition(const int instrument, const int channel) const {
// Divert to the appropriate rhythm instrument if in rhythm mode.
if(channel >= 6 && rhythm_mode_enabled_) {
return &percussion_patch_set[(channel - 6) * 8];
@ -197,7 +197,7 @@ const uint8_t *OPLL::instrument_definition(int instrument, int channel) {
return is_vrc7_ ? &vrc7_patch_set[index] : &opll_patch_set[index];
}
void OPLL::install_instrument(int channel) {
void OPLL::install_instrument(const int channel) {
auto &carrier_envelope = envelope_generators_[channel + 0];
auto &carrier_phase = phase_generators_[channel + 0];
auto &carrier_scaler = key_level_scalers_[channel + 0];
@ -266,7 +266,7 @@ void OPLL::install_instrument(int channel) {
carrier_envelope.set_sustain_level(instrument[7] >> 4);
}
void OPLL::set_use_sustain(int channel) {
void OPLL::set_use_sustain(const int channel) {
const uint8_t *const instrument = instrument_definition(channels_[channel].instrument, channel);
envelope_generators_[channel + 0].set_use_sustain_level((instrument[1] & 0x20) || channels_[channel].use_sustain);
envelope_generators_[channel + 9].set_use_sustain_level((instrument[0] & 0x20) || channels_[channel].use_sustain);
@ -274,7 +274,7 @@ void OPLL::set_use_sustain(int channel) {
// MARK: - Output generation.
void OPLL::set_sample_volume_range(std::int16_t range) {
void OPLL::set_sample_volume_range(const std::int16_t range) {
total_volume_ = range;
}
@ -387,7 +387,7 @@ void OPLL::update_all_channels() {
#define ATTENUATION(x) ((x) << 7)
int OPLL::melodic_output(int channel) {
int OPLL::melodic_output(const int channel) {
// The modulator always updates after the carrier, oddly enough. So calculate actual output first, based on the modulator's last value.
auto carrier = WaveformGenerator<period_precision>::wave(channels_[channel].carrier_waveform, phase_generators_[channel].scaled_phase(), channels_[channel].modulator_output);
carrier += envelope_generators_[channel].attenuation() + ATTENUATION(channels_[channel].attenuation) + key_level_scalers_[channel].attenuation();
@ -403,7 +403,7 @@ int OPLL::melodic_output(int channel) {
return carrier.level();
}
int OPLL::bass_drum() {
int OPLL::bass_drum() const {
// Use modulator 6 and carrier 6, attenuated as per the bass-specific envelope generators and the attenuation level for channel 6.
auto modulation = WaveformGenerator<period_precision>::wave(Waveform::Sine, phase_generators_[6 + 9].phase());
modulation += rhythm_envelope_generators_[RhythmIndices::BassModulator].attenuation();
@ -413,7 +413,7 @@ int OPLL::bass_drum() {
return carrier.level();
}
int OPLL::tom_tom() {
int OPLL::tom_tom() const {
// Use modulator 8 and the 'instrument' selection for channel 8 as an attenuation.
auto tom_tom = WaveformGenerator<period_precision>::wave(Waveform::Sine, phase_generators_[8 + 9].phase());
tom_tom += rhythm_envelope_generators_[RhythmIndices::TomTom].attenuation();
@ -421,7 +421,7 @@ int OPLL::tom_tom() {
return tom_tom.level();
}
int OPLL::snare_drum() {
int OPLL::snare_drum() const {
// Use modulator 7 and the carrier attenuation level for channel 7.
LogSign snare = WaveformGenerator<period_precision>::snare(oscillator_, phase_generators_[7 + 9].phase());
snare += rhythm_envelope_generators_[RhythmIndices::Snare].attenuation();
@ -429,7 +429,7 @@ int OPLL::snare_drum() {
return snare.level();
}
int OPLL::cymbal() {
int OPLL::cymbal() const {
// Use modulator 7, carrier 8 and the attenuation level for channel 8.
LogSign cymbal = WaveformGenerator<period_precision>::cymbal(phase_generators_[8].phase(), phase_generators_[7 + 9].phase());
cymbal += rhythm_envelope_generators_[RhythmIndices::Cymbal].attenuation();
@ -437,7 +437,7 @@ int OPLL::cymbal() {
return cymbal.level();
}
int OPLL::high_hat() {
int OPLL::high_hat() const {
// Use modulator 7, carrier 8 a and the 'instrument' selection for channel 7 as an attenuation.
LogSign high_hat = WaveformGenerator<period_precision>::high_hat(oscillator_, phase_generators_[8].phase(), phase_generators_[7 + 9].phase());
high_hat += rhythm_envelope_generators_[RhythmIndices::HighHat].attenuation();

View File

@ -26,7 +26,7 @@ class OPLL: public OPLBase<OPLL, false> {
/// As per ::SampleSource; provides audio output.
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 *);
void set_sample_volume_range(std::int16_t range);
bool is_zero_level() const { return false; } // TODO.
@ -49,11 +49,11 @@ class OPLL: public OPLBase<OPLL, false> {
void update_all_channels();
int melodic_output(int channel);
int bass_drum();
int tom_tom();
int snare_drum();
int cymbal();
int high_hat();
int bass_drum() const;
int tom_tom() const;
int snare_drum() const;
int cymbal() const;
int high_hat() const;
static constexpr int period_precision = 9;
static constexpr int envelope_precision = 7;
@ -122,7 +122,7 @@ class OPLL: public OPLBase<OPLL, false> {
void set_use_sustain(int channel);
/// @returns The 8-byte definition of instrument @c instrument.
const uint8_t *instrument_definition(int instrument, int channel);
const uint8_t *instrument_definition(int instrument, int channel) const;
};
}

View File

@ -31,7 +31,7 @@ RP5C01::RP5C01(HalfCycles clock_rate) : clock_rate_(clock_rate) {
leap_year_ = time_date->tm_year % 4;
}
void RP5C01::run_for(HalfCycles cycles) {
void RP5C01::run_for(const HalfCycles cycles) {
sub_seconds_ += cycles;
// Guess: this happens so rarely (i.e. once a second, ordinarily) that
@ -96,13 +96,13 @@ void RP5C01::run_for(HalfCycles cycles) {
namespace {
constexpr int Reg(int mode, int address) {
constexpr int Reg(const int mode, const int address) {
return address | mode << 4;
}
constexpr int PM = 1 << 4;
constexpr int twenty_four_to_twelve(int hours) {
constexpr int twenty_four_to_twelve(const int hours) {
switch(hours) {
default: return (hours % 12) + (hours > 12 ? PM : 0);
case 0: return 12;

View File

@ -16,41 +16,41 @@
namespace Ricoh::RP5C01 {
class RP5C01 {
public:
RP5C01(HalfCycles clock_rate);
public:
RP5C01(HalfCycles clock_rate);
/// @returns the result of a read from @c address.
uint8_t read(int address);
/// @returns the result of a read from @c address.
uint8_t read(int address);
/// Performs a write of @c value to @c address.
void write(int address, uint8_t value);
/// Performs a write of @c value to @c address.
void write(int address, uint8_t value);
/// Advances time.
void run_for(HalfCycles);
/// Advances time.
void run_for(HalfCycles);
private:
std::array<uint8_t, 26> ram_;
private:
std::array<uint8_t, 26> ram_;
HalfCycles sub_seconds_;
const HalfCycles clock_rate_;
HalfCycles sub_seconds_;
const HalfCycles clock_rate_;
// Contains the seconds, minutes and hours fields.
int seconds_ = 0;
// Contains the seconds, minutes and hours fields.
int seconds_ = 0;
// Calendar entries.
int day_of_the_week_ = 0;
int day_ = 0;
int month_ = 0;
int year_ = 0;
int leap_year_ = 0;
// Calendar entries.
int day_of_the_week_ = 0;
int day_ = 0;
int month_ = 0;
int year_ = 0;
int leap_year_ = 0;
// Other flags.
bool timer_enabled_ = false;
bool alarm_enabled_ = false;
int mode_ = 0;
bool one_hz_on_ = false;
bool sixteen_hz_on_ = false;
bool twentyfour_hour_clock_ = true;
// Other flags.
bool timer_enabled_ = false;
bool alarm_enabled_ = false;
int mode_ = 0;
bool one_hz_on_ = false;
bool sixteen_hz_on_ = false;
bool twentyfour_hour_clock_ = true;
};
}

View File

@ -13,7 +13,11 @@
using namespace TI;
SN76489::SN76489(Personality personality, Concurrency::AsyncTaskQueue<false> &task_queue, int additional_divider) : task_queue_(task_queue) {
SN76489::SN76489(
const Personality personality,
Concurrency::AsyncTaskQueue<false> &task_queue,
const int additional_divider
) : task_queue_(task_queue) {
set_sample_volume_range(0);
switch(personality) {
@ -36,7 +40,7 @@ SN76489::SN76489(Personality personality, Concurrency::AsyncTaskQueue<false> &ta
master_divider_period_ /= additional_divider;
}
void SN76489::set_sample_volume_range(std::int16_t range) {
void SN76489::set_sample_volume_range(const std::int16_t range) {
// Build a volume table.
double multiplier = pow(10.0, -0.1);
double volume = float(range) / 4.0f; // As there are four channels.
@ -48,7 +52,7 @@ void SN76489::set_sample_volume_range(std::int16_t range) {
evaluate_output_volume();
}
void SN76489::write(uint8_t value) {
void SN76489::write(const uint8_t value) {
task_queue_.enqueue([value, this] () {
if(value & 0x80) {
active_register_ = value;
@ -87,7 +91,11 @@ void SN76489::write(uint8_t value) {
}
bool SN76489::is_zero_level() const {
return channels_[0].volume == 0xf && channels_[1].volume == 0xf && channels_[2].volume == 0xf && channels_[3].volume == 0xf;
return
channels_[0].volume == 0xf &&
channels_[1].volume == 0xf &&
channels_[2].volume == 0xf &&
channels_[3].volume == 0xf;
}
void SN76489::evaluate_output_volume() {
@ -100,7 +108,7 @@ void SN76489::evaluate_output_volume() {
}
template <Outputs::Speaker::Action action>
void SN76489::apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *target) {
void SN76489::apply_samples(const std::size_t number_of_samples, Outputs::Speaker::MonoSample *const target) {
std::size_t c = 0;
while((master_divider_& (master_divider_period_ - 1)) && c < number_of_samples) {
Outputs::Speaker::apply<action>(target[c], output_volume_);

View File

@ -14,53 +14,53 @@
namespace TI {
class SN76489: public Outputs::Speaker::BufferSource<SN76489, false> {
public:
enum class Personality {
SN76489,
SN76494,
SMS
};
public:
enum class Personality {
SN76489,
SN76494,
SMS
};
/// Creates a new SN76489.
SN76489(Personality personality, Concurrency::AsyncTaskQueue<false> &task_queue, int additional_divider = 1);
/// Creates a new SN76489.
SN76489(Personality, Concurrency::AsyncTaskQueue<false> &, int additional_divider = 1);
/// Writes a new value to the SN76489.
void write(uint8_t value);
/// Writes a new value to the SN76489.
void write(uint8_t);
// As per SampleSource.
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);
// As per SampleSource.
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);
private:
int master_divider_ = 0;
int master_divider_period_ = 16;
int16_t output_volume_ = 0;
void evaluate_output_volume();
int volumes_[16];
private:
int master_divider_ = 0;
int master_divider_period_ = 16;
int16_t output_volume_ = 0;
void evaluate_output_volume();
int volumes_[16];
Concurrency::AsyncTaskQueue<false> &task_queue_;
Concurrency::AsyncTaskQueue<false> &task_queue_;
struct ToneChannel {
// Programmatically-set state; updated by the processor.
uint16_t divider = 0;
uint8_t volume = 0xf;
struct ToneChannel {
// Programmatically-set state; updated by the processor.
uint16_t divider = 0;
uint8_t volume = 0xf;
// Active state; self-evolving as a function of time.
uint16_t counter = 0;
int level = 0;
} channels_[4];
enum {
Periodic15,
Periodic16,
Noise15,
Noise16
} noise_mode_ = Periodic15;
uint16_t noise_shifter_ = 0;
int active_register_ = 0;
// Active state; self-evolving as a function of time.
uint16_t counter = 0;
int level = 0;
} channels_[4];
enum {
Periodic15,
Periodic16,
Noise15,
Noise16
} noise_mode_ = Periodic15;
uint16_t noise_shifter_ = 0;
int active_register_ = 0;
bool shifter_is_16bit_ = false;
bool shifter_is_16bit_ = false;
};
}

View File

@ -14,7 +14,7 @@
using namespace Serial;
template <bool include_clock>
void Line<include_clock>::set_writer_clock_rate(HalfCycles clock_rate) {
void Line<include_clock>::set_writer_clock_rate(const HalfCycles clock_rate) {
clock_rate_ = clock_rate;
}
@ -73,7 +73,7 @@ void Line<include_clock>::advance_writer(HalfCycles cycles) {
}
template <bool include_clock>
void Line<include_clock>::write(bool level) {
void Line<include_clock>::write(const bool level) {
if(!events_.empty()) {
events_.emplace_back();
events_.back().type = level ? Event::SetHigh : Event::SetLow;
@ -84,7 +84,11 @@ void Line<include_clock>::write(bool level) {
}
template <bool include_clock>
template <bool lsb_first, typename IntT> void Line<include_clock>::write_internal(HalfCycles cycles, int count, IntT levels) {
template <bool lsb_first, typename IntT> void Line<include_clock>::write_internal(
const HalfCycles cycles,
int count,
const IntT levels
) {
remaining_delays_ += count * cycles.as_integral();
auto event = events_.size();
@ -108,12 +112,12 @@ template <bool lsb_first, typename IntT> void Line<include_clock>::write_interna
}
template <bool include_clock>
void Line<include_clock>::write(HalfCycles cycles, int count, int levels) {
void Line<include_clock>::write(const HalfCycles cycles, const int count, const int levels) {
write_internal<true, int>(cycles, count, levels);
}
template <bool include_clock>
template <bool lsb_first, typename IntT> void Line<include_clock>::write(HalfCycles cycles, IntT value) {
template <bool lsb_first, typename IntT> void Line<include_clock>::write(const HalfCycles cycles, const IntT value) {
write_internal<lsb_first, IntT>(cycles, 8 * sizeof(IntT), value);
}
@ -129,7 +133,10 @@ bool Line<include_clock>::read() const {
}
template <bool include_clock>
void Line<include_clock>::set_read_delegate(ReadDelegate *delegate, [[maybe_unused]] Storage::Time bit_length) {
void Line<include_clock>::set_read_delegate(
ReadDelegate *const delegate,
[[maybe_unused]] const Storage::Time bit_length
) {
read_delegate_ = delegate;
if constexpr (!include_clock) {
assert(bit_length > Storage::Time(0));
@ -140,7 +147,7 @@ void Line<include_clock>::set_read_delegate(ReadDelegate *delegate, [[maybe_unus
}
template <bool include_clock>
void Line<include_clock>::update_delegate(bool level) {
void Line<include_clock>::update_delegate(const bool level) {
// Exit early if there's no delegate, or if the delegate is waiting for
// zero and this isn't zero.
if(!read_delegate_) return;

View File

@ -41,97 +41,96 @@ namespace Serial {
all enqueued bits at appropriate times.
*/
template <bool include_clock> class Line {
public:
/// Sets the line to @c level instantaneously.
void write(bool level);
public:
/// Sets the line to @c level instantaneously.
void write(bool);
/// @returns The instantaneous level of this line.
bool read() const;
/// @returns The instantaneous level of this line.
bool read() const;
/// Sets the denominator for the between levels for any data enqueued
/// via an @c write.
void set_writer_clock_rate(HalfCycles clock_rate);
/// Sets the denominator for the between levels for any data enqueued
/// via an @c write.
void set_writer_clock_rate(HalfCycles);
/// Enqueues @c count level changes, the first occurring immediately
/// after the final event currently posted and each subsequent event
/// occurring @c cycles after the previous. An additional gap of @c cycles
/// is scheduled after the final output. The levels to output are
/// taken from @c levels which is read from lsb to msb. @c cycles is
/// relative to the writer's clock rate.
void write(HalfCycles cycles, int count, int levels);
/// Enqueues @c count level changes, the first occurring immediately
/// after the final event currently posted and each subsequent event
/// occurring @c cycles after the previous. An additional gap of @c cycles
/// is scheduled after the final output. The levels to output are
/// taken from @c levels which is read from lsb to msb. @c cycles is
/// relative to the writer's clock rate.
void write(HalfCycles cycles, int count, int levels);
/// Enqueus every bit from @c value as per the rules of write(HalfCycles, int, int),
/// either in LSB or MSB order as per the @c lsb_first template flag.
template <bool lsb_first, typename IntT> void write(HalfCycles cycles, IntT value);
/// Enqueus every bit from @c value as per the rules of write(HalfCycles, int, int),
/// either in LSB or MSB order as per the @c lsb_first template flag.
template <bool lsb_first, typename IntT> void write(HalfCycles cycles, IntT value);
/// @returns the number of cycles until currently enqueued write data is exhausted.
forceinline HalfCycles write_data_time_remaining() const {
return HalfCycles(remaining_delays_);
}
/// @returns the number of cycles until currently enqueued write data is exhausted.
forceinline HalfCycles write_data_time_remaining() const {
return HalfCycles(remaining_delays_);
}
/// @returns the number of cycles left until it is guaranteed that a passive reader
/// has received all currently-enqueued bits.
forceinline HalfCycles transmission_data_time_remaining() const {
return HalfCycles(remaining_delays_ + transmission_extra_);
}
/// @returns the number of cycles left until it is guaranteed that a passive reader
/// has received all currently-enqueued bits.
forceinline HalfCycles transmission_data_time_remaining() const {
return HalfCycles(remaining_delays_ + transmission_extra_);
}
/// Advances the read position by @c cycles relative to the writer's
/// clock rate.
void advance_writer(HalfCycles cycles);
/// Advances the read position relative to the writer's clock rate.
void advance_writer(HalfCycles);
/// Eliminates all future write states, leaving the output at whatever it is now.
void reset_writing();
/// Eliminates all future write states, leaving the output at whatever it is now.
void reset_writing();
struct ReadDelegate {
virtual bool serial_line_did_produce_bit(Line *line, int bit) = 0;
};
/*!
Sets a read delegate.
struct ReadDelegate {
virtual bool serial_line_did_produce_bit(Line *, int bit) = 0;
};
/*!
Sets a read delegate.
Single line serial connection:
Single line serial connection:
The delegate will receive samples of the output level every
@c bit_lengths of a second apart subject to a state machine:
The delegate will receive samples of the output level every
@c bit_lengths of a second apart subject to a state machine:
* initially no bits will be delivered;
* when a zero level is first detected, the line will wait half a bit's length, then start
sampling at single-bit intervals, passing each bit to the delegate while it returns @c true;
* as soon as the delegate returns @c false, the line will return to the initial state.
* initially no bits will be delivered;
* when a zero level is first detected, the line will wait half a bit's length, then start
sampling at single-bit intervals, passing each bit to the delegate while it returns @c true;
* as soon as the delegate returns @c false, the line will return to the initial state.
Two-line clock + data connection:
Two-line clock + data connection:
The delegate will receive every bit that has been enqueued, spaced as nominated
by the writer. @c bit_length is ignored, as is the return result of
@c ReadDelegate::serial_line_did_produce_bit.
*/
void set_read_delegate(ReadDelegate *delegate, Storage::Time bit_length = Storage::Time());
The delegate will receive every bit that has been enqueued, spaced as nominated
by the writer. @c bit_length is ignored, as is the return result of
@c ReadDelegate::serial_line_did_produce_bit.
*/
void set_read_delegate(ReadDelegate *, Storage::Time bit_length = Storage::Time());
private:
struct Event {
enum Type {
Delay, SetHigh, SetLow
} type;
int delay;
};
std::vector<Event> events_;
HalfCycles::IntType remaining_delays_ = 0;
HalfCycles::IntType transmission_extra_ = 0;
bool level_ = true;
HalfCycles clock_rate_ = 0;
private:
struct Event {
enum Type {
Delay, SetHigh, SetLow
} type;
int delay;
};
std::vector<Event> events_;
HalfCycles::IntType remaining_delays_ = 0;
HalfCycles::IntType transmission_extra_ = 0;
bool level_ = true;
HalfCycles clock_rate_ = 0;
ReadDelegate *read_delegate_ = nullptr;
Storage::Time read_delegate_bit_length_, time_left_in_bit_;
int write_cycles_since_delegate_call_ = 0;
enum class ReadDelegatePhase {
WaitingForZero,
Serialising
} read_delegate_phase_ = ReadDelegatePhase::WaitingForZero;
ReadDelegate *read_delegate_ = nullptr;
Storage::Time read_delegate_bit_length_, time_left_in_bit_;
int write_cycles_since_delegate_call_ = 0;
enum class ReadDelegatePhase {
WaitingForZero,
Serialising
} read_delegate_phase_ = ReadDelegatePhase::WaitingForZero;
void update_delegate(bool level);
HalfCycles::IntType minimum_write_cycles_for_read_delegate_bit();
void update_delegate(bool level);
HalfCycles::IntType minimum_write_cycles_for_read_delegate_bit();
template <bool lsb_first, typename IntT> void
write_internal(HalfCycles, int, IntT);
template <bool lsb_first, typename IntT> void
write_internal(HalfCycles, int, IntT);
};
/*!