1
0
mirror of https://github.com/TomHarte/CLK.git synced 2025-01-14 13:33: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); set_clock_data(clock_, pulled);
} }
bool Bus::data() { bool Bus::data() const {
bool result = data_; bool result = data_;
if(peripheral_bits_) { if(peripheral_bits_) {
result |= !(peripheral_response_ & 0x80); result |= !(peripheral_response_ & 0x80);
@ -29,14 +29,14 @@ bool Bus::data() {
return result; return result;
} }
void Bus::set_clock(bool pulled) { void Bus::set_clock(const bool pulled) {
set_clock_data(pulled, data_); set_clock_data(pulled, data_);
} }
bool Bus::clock() { bool Bus::clock() const {
return clock_; 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. // Proceed only if changes are evidenced.
if(clock_pulled == clock_ && data_pulled == data_) { if(clock_pulled == clock_ && data_pulled == data_) {
return; 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 = [&]() { const auto capture_bit = [&]() {
input_ = uint16_t((input_ << 1) | (event == Event::Zero ? 0 : 1)); input_ = uint16_t((input_ << 1) | (event == Event::Zero ? 0 : 1));
++input_count_; ++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; peripherals_[address] = peripheral;
} }

View File

@ -41,10 +41,10 @@ struct Peripheral {
class Bus { class Bus {
public: public:
void set_data(bool pulled); void set_data(bool pulled);
bool data(); bool data() const;
void set_clock(bool pulled); void set_clock(bool pulled);
bool clock(); bool clock() const;
void set_clock_data(bool clock_pulled, bool data_pulled); 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> 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()) { if(is_zero_level()) {
Outputs::Speaker::fill<action>(target, target + number_of_samples, Outputs::Speaker::MonoSample()); Outputs::Speaker::fill<action>(target, target + number_of_samples, Outputs::Speaker::MonoSample());
return; 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::Store>(std::size_t, Outputs::Speaker::MonoSample *);
template void SCC::apply_samples<Outputs::Speaker::Action::Ignore>(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; address &= 0xff;
if(address < 0x80) ram_[address] = value; if(address < 0x80) ram_[address] = value;
@ -108,7 +108,7 @@ void SCC::set_sample_volume_range(std::int16_t range) {
evaluate_output_volume(); evaluate_output_volume();
} }
uint8_t SCC::read(uint16_t address) { uint8_t SCC::read(uint16_t address) const {
address &= 0xff; address &= 0xff;
if(address < 0x80) { if(address < 0x80) {
return ram_[address]; return ram_[address];

View File

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

View File

@ -31,229 +31,229 @@ namespace Yamaha::OPL {
TODO: use envelope_precision. TODO: use envelope_precision.
*/ */
template <int envelope_precision, int period_precision> class EnvelopeGenerator { template <int envelope_precision, int period_precision> class EnvelopeGenerator {
public: public:
/*! /*!
Advances the envelope generator a single step, given the current state of the low-frequency oscillator, @c oscillator. Advances the envelope generator a single step, given the current state of the low-frequency oscillator, @c oscillator.
*/ */
void update(const LowFrequencyOscillator &oscillator) { void update(const LowFrequencyOscillator &oscillator) {
// Apply tremolo, which is fairly easy. // Apply tremolo, which is fairly easy.
tremolo_ = tremolo_enable_ * oscillator.tremolo << 4; tremolo_ = tremolo_enable_ * oscillator.tremolo << 4;
// Something something something... // Something something something...
const int key_scaling_rate = key_scale_rate_ >> key_scale_rate_shift_; const int key_scaling_rate = key_scale_rate_ >> key_scale_rate_shift_;
switch(phase_) { switch(phase_) {
case Phase::Damp: case Phase::Damp:
update_decay(oscillator, 12 << 2); update_decay(oscillator, 12 << 2);
if(attenuation_ == 511) { if(attenuation_ == 511) {
(*will_attack_)(); (*will_attack_)();
phase_ = Phase::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;
} }
break;
(*will_attack_)(); case Phase::Attack:
} update_attack(oscillator, attack_rate_ + key_scaling_rate);
phase_ = Phase::Attack;
// 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;
} }
/*! // On key on: if this is an envelope generator with damping, and damping is required,
Sets the attack rate, which should be in the range 015. // schedule that. If damping is not required, announce a pending attack now and
*/ // transition to attack.
void set_attack_rate(int rate) { if(will_attack_) {
attack_rate_ = rate << 2; if(attenuation_ != 511) {
} phase_ = Phase::Damp;
/*!
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) {
return; return;
} }
// Special case: instant attack. (*will_attack_)();
if(rate >= 60) { }
attenuation_ = 0; phase_ = Phase::Attack;
return; }
}
// Work out the number of cycles between each adjustment tick, and stop now /*!
// if not at the next adjustment boundary. Sets the attack rate, which should be in the range 015.
const int shift_size = 13 - (std::min(rate, 52) >> 2); */
if(oscillator.counter & ((1 << shift_size) - 1)) { void set_attack_rate(const int rate) {
return; attack_rate_ = rate << 2;
} }
// Apply dithered adjustment. /*!
const int rate_shift = (rate > 55); Sets the decay rate, which should be in the range 015.
const int step = dithering_patterns[rate & 3][(oscillator.counter >> shift_size) & 7]; */
attenuation_ -= ((attenuation_ >> (3 - rate_shift)) + 1) * step; 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: instant attack.
// Special case: no decay. if(rate >= 60) {
if(rate < 4) { attenuation_ = 0;
return; 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);
} }
// 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 { namespace Yamaha::OPL {
template <int frequency_precision> class KeyLevelScaler { template <int frequency_precision> class KeyLevelScaler {
public: public:
/*! /*!
Sets the current period associated with the channel that owns this envelope generator; 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. this is used to select a key scaling rate if key-rate scaling is enabled.
*/ */
void set_period(int period, int octave) { 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 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}; constexpr int masks[2] = {~0, 0};
// A two's complement assumption is embedded below; the use of masks relies // A two's complement assumption is embedded below; the use of masks relies
// on the sign bit to clamp to zero. // on the sign bit to clamp to zero.
level_ = key_level_scales[period >> (frequency_precision - 4)]; level_ = key_level_scales[period >> (frequency_precision - 4)];
level_ -= 16 * (octave ^ 7); level_ -= 16 * (octave ^ 7);
level_ &= masks[(level_ >> ((sizeof(int) * 8) - 1)) & 1]; level_ &= masks[(level_ >> ((sizeof(int) * 8) - 1)) & 1];
} }
/*! /*!
Enables or disables key-rate scaling. Enables or disables key-rate scaling.
*/ */
void set_key_scaling_level(int level) { void set_key_scaling_level(const int level) {
// '7' is just a number large enough to render all possible scaling coefficients as 0. // '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}; constexpr int key_level_scale_shifts[4] = {7, 1, 2, 0};
shift_ = key_level_scale_shifts[level]; shift_ = key_level_scale_shifts[level];
} }
/*! /*!
@returns The current attenuation level due to key-level scaling. @returns The current attenuation level due to key-level scaling.
*/ */
int attenuation() const { int attenuation() const {
return level_ >> shift_; return level_ >> shift_;
} }
private: private:
int level_ = 0; int level_ = 0;
int shift_ = 0; int shift_ = 0;
}; };
} }

View File

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

View File

@ -14,22 +14,22 @@
namespace Yamaha::OPL { namespace Yamaha::OPL {
template <typename Child, bool stereo> class OPLBase: public ::Outputs::Speaker::BufferSource<Child, stereo> { template <typename Child, bool stereo> class OPLBase: public ::Outputs::Speaker::BufferSource<Child, stereo> {
public: public:
void write(uint16_t address, uint8_t value) { void write(const uint16_t address, const uint8_t value) {
if(address & 1) { if(address & 1) {
static_cast<Child *>(this)->write_register(selected_register_, value); static_cast<Child *>(this)->write_register(selected_register_, value);
} else { } else {
selected_register_ = value; selected_register_ = value;
}
} }
}
protected: protected:
OPLBase(Concurrency::AsyncTaskQueue<false> &task_queue) : task_queue_(task_queue) {} OPLBase(Concurrency::AsyncTaskQueue<false> &task_queue) : task_queue_(task_queue) {}
Concurrency::AsyncTaskQueue<false> &task_queue_; Concurrency::AsyncTaskQueue<false> &task_queue_;
private: private:
uint8_t selected_register_ = 0; 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. multiple, and whether to apply vibrato, this will then appropriately update and return phase.
*/ */
template <int precision> class PhaseGenerator { template <int precision> class PhaseGenerator {
public: public:
/*! /*!
Advances the phase generator a single step, given the current state of the low-frequency oscillator, @c oscillator. Advances the phase generator a single step, given the current state of the low-frequency oscillator, @c oscillator.
*/ */
void update(const LowFrequencyOscillator &oscillator) { void update(const LowFrequencyOscillator &oscillator) {
constexpr int vibrato_shifts[4] = {3, 1, 0, 1}; constexpr int vibrato_shifts[4] = {3, 1, 0, 1};
constexpr int vibrato_signs[2] = {1, -1}; constexpr int vibrato_signs[2] = {1, -1};
// Get just the top three bits of the period_. // Get just the top three bits of the period_.
const int top_freq = period_ >> (precision - 3); const int top_freq = period_ >> (precision - 3);
// Cacluaute applicable vibrato as a function of (i) the top three bits of the // 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 // oscillator period; (ii) the current low-frequency oscillator vibrato output; and
// (iii) whether vibrato is enabled. // (iii) whether vibrato is enabled.
const int vibrato = (top_freq >> vibrato_shifts[oscillator.vibrato & 3]) * vibrato_signs[oscillator.vibrato >> 2] * enable_vibrato_; 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. // Apply phase update with vibrato from the low-frequency oscillator.
phase_ += (multiple_ * ((period_ << 1) + vibrato) << octave_) >> 1; phase_ += (multiple_ * ((period_ << 1) + vibrato) << octave_) >> 1;
} }
/*! /*!
@returns Current phase; real hardware provides only the low ten bits of this result. @returns Current phase; real hardware provides only the low ten bits of this result.
*/ */
int phase() const { int phase() const {
// My table if multipliers is multiplied by two, so shift by one more // My table if multipliers is multiplied by two, so shift by one more
// than the stated precision. // than the stated precision.
return phase_ >> precision_shift; return phase_ >> precision_shift;
} }
/*! /*!
@returns Current phase, scaled up by (1 << precision). @returns Current phase, scaled up by (1 << precision).
*/ */
int scaled_phase() const { int scaled_phase() const {
return phase_ >> 1; return phase_ >> 1;
} }
/*! /*!
Applies feedback based on two historic samples of a total output level, Applies feedback based on two historic samples of a total output level,
plus the degree of feedback to apply plus the degree of feedback to apply
*/ */
void apply_feedback(LogSign first, LogSign second, int level) { void apply_feedback(const LogSign first, const LogSign second, const int level) {
constexpr int masks[] = {0, ~0, ~0, ~0, ~0, ~0, ~0, ~0}; constexpr int masks[] = {0, ~0, ~0, ~0, ~0, ~0, ~0, ~0};
phase_ += ((second.level(precision) + first.level(precision)) >> (8 - level)) & masks[level]; 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, 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. i.e. a 4-bit number that is used as a lookup into the internal multiples table.
*/ */
void set_multiple(int multiple) { void set_multiple(const int multiple) {
// This encodes the MUL -> multiple table given on page 12, // This encodes the MUL -> multiple table given on page 12,
// multiplied by two. // multiplied by two.
constexpr int multipliers[] = { constexpr int multipliers[] = {
1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 20, 24, 24, 30, 30 1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 20, 24, 24, 30, 30
}; };
assert(multiple < 16); assert(multiple < 16);
multiple_ = multipliers[multiple]; 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. 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) { void set_period(const int period, const int octave) {
period_ = period; period_ = period;
octave_ = octave; octave_ = octave;
assert(octave_ < 8); assert(octave_ < 8);
assert(period_ < (1 << precision)); assert(period_ < (1 << precision));
} }
/*! /*!
Enables or disables vibrato. Enables or disables vibrato.
*/ */
void set_vibrato_enabled(bool enabled) { void set_vibrato_enabled(const bool enabled) {
enable_vibrato_ = int(enabled); enable_vibrato_ = int(enabled);
} }
/*! /*!
Resets the current phase. Resets the current phase.
*/ */
void reset() { void reset() {
phase_ = 0; phase_ = 0;
} }
private: private:
static constexpr int precision_shift = 1 + precision; static constexpr int precision_shift = 1 + precision;
int phase_ = 0; int phase_ = 0;
int multiple_ = 0; int multiple_ = 0;
int period_ = 0; int period_ = 0;
int octave_ = 0; int octave_ = 0;
int enable_vibrato_ = 0; int enable_vibrato_ = 0;
}; };
} }

View File

@ -32,12 +32,12 @@ struct LogSign {
sign = 1; sign = 1;
} }
LogSign &operator +=(int attenuation) { LogSign &operator +=(const int attenuation) {
log += attenuation; log += attenuation;
return *this; return *this;
} }
LogSign &operator +=(LogSign log_sign) { LogSign &operator +=(const LogSign log_sign) {
log += log_sign.log; log += log_sign.log;
sign *= log_sign.sign; sign *= log_sign.sign;
return *this; return *this;
@ -49,7 +49,7 @@ struct LogSign {
/*! /*!
@returns Negative log sin of x, assuming a 1024-unit circle. @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)). /// 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: /// 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 Computes the linear value represented by the log-sign @c ls, shifted left by @c fractional prior
to loss of precision. 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. /// 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 /// 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); return power_two(*this, fractional);
} }

View File

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

View File

@ -12,7 +12,7 @@
using namespace Yamaha::OPL; 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) { 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 // Due to the way that sound mixing works on the OPLL, the audio divider may not
// be larger than 4. // be larger than 4.
@ -71,7 +71,7 @@ OPLL::OPLL(Concurrency::AsyncTaskQueue<false> &task_queue, int audio_divider, bo
// MARK: - Machine-facing programmatic input. // 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 // The OPLL doesn't have timers or other non-audio functions, so all writes
// go to the audio queue. // go to the audio queue.
task_queue_.enqueue([this, address, value] { 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 + 0].set_period(channels_[channel].period, channels_[channel].octave);
phase_generators_[channel + 9].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); 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. // Divert to the appropriate rhythm instrument if in rhythm mode.
if(channel >= 6 && rhythm_mode_enabled_) { if(channel >= 6 && rhythm_mode_enabled_) {
return &percussion_patch_set[(channel - 6) * 8]; 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]; 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_envelope = envelope_generators_[channel + 0];
auto &carrier_phase = phase_generators_[channel + 0]; auto &carrier_phase = phase_generators_[channel + 0];
auto &carrier_scaler = key_level_scalers_[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); 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); 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 + 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); 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. // 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; total_volume_ = range;
} }
@ -387,7 +387,7 @@ void OPLL::update_all_channels() {
#define ATTENUATION(x) ((x) << 7) #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. // 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); 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(); 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(); 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. // 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()); auto modulation = WaveformGenerator<period_precision>::wave(Waveform::Sine, phase_generators_[6 + 9].phase());
modulation += rhythm_envelope_generators_[RhythmIndices::BassModulator].attenuation(); modulation += rhythm_envelope_generators_[RhythmIndices::BassModulator].attenuation();
@ -413,7 +413,7 @@ int OPLL::bass_drum() {
return carrier.level(); 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. // 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()); auto tom_tom = WaveformGenerator<period_precision>::wave(Waveform::Sine, phase_generators_[8 + 9].phase());
tom_tom += rhythm_envelope_generators_[RhythmIndices::TomTom].attenuation(); tom_tom += rhythm_envelope_generators_[RhythmIndices::TomTom].attenuation();
@ -421,7 +421,7 @@ int OPLL::tom_tom() {
return tom_tom.level(); return tom_tom.level();
} }
int OPLL::snare_drum() { int OPLL::snare_drum() const {
// Use modulator 7 and the carrier attenuation level for channel 7. // Use modulator 7 and the carrier attenuation level for channel 7.
LogSign snare = WaveformGenerator<period_precision>::snare(oscillator_, phase_generators_[7 + 9].phase()); LogSign snare = WaveformGenerator<period_precision>::snare(oscillator_, phase_generators_[7 + 9].phase());
snare += rhythm_envelope_generators_[RhythmIndices::Snare].attenuation(); snare += rhythm_envelope_generators_[RhythmIndices::Snare].attenuation();
@ -429,7 +429,7 @@ int OPLL::snare_drum() {
return snare.level(); return snare.level();
} }
int OPLL::cymbal() { int OPLL::cymbal() const {
// Use modulator 7, carrier 8 and the attenuation level for channel 8. // 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()); LogSign cymbal = WaveformGenerator<period_precision>::cymbal(phase_generators_[8].phase(), phase_generators_[7 + 9].phase());
cymbal += rhythm_envelope_generators_[RhythmIndices::Cymbal].attenuation(); cymbal += rhythm_envelope_generators_[RhythmIndices::Cymbal].attenuation();
@ -437,7 +437,7 @@ int OPLL::cymbal() {
return cymbal.level(); 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. // 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()); 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(); 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. /// As per ::SampleSource; provides audio output.
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 *);
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. bool is_zero_level() const { return false; } // TODO.
@ -49,11 +49,11 @@ class OPLL: public OPLBase<OPLL, false> {
void update_all_channels(); void update_all_channels();
int melodic_output(int channel); int melodic_output(int channel);
int bass_drum(); int bass_drum() const;
int tom_tom(); int tom_tom() const;
int snare_drum(); int snare_drum() const;
int cymbal(); int cymbal() const;
int high_hat(); int high_hat() const;
static constexpr int period_precision = 9; static constexpr int period_precision = 9;
static constexpr int envelope_precision = 7; static constexpr int envelope_precision = 7;
@ -122,7 +122,7 @@ class OPLL: public OPLBase<OPLL, false> {
void set_use_sustain(int channel); void set_use_sustain(int channel);
/// @returns The 8-byte definition of instrument @c instrument. /// @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; leap_year_ = time_date->tm_year % 4;
} }
void RP5C01::run_for(HalfCycles cycles) { void RP5C01::run_for(const HalfCycles cycles) {
sub_seconds_ += cycles; sub_seconds_ += cycles;
// Guess: this happens so rarely (i.e. once a second, ordinarily) that // Guess: this happens so rarely (i.e. once a second, ordinarily) that
@ -96,13 +96,13 @@ void RP5C01::run_for(HalfCycles cycles) {
namespace { namespace {
constexpr int Reg(int mode, int address) { constexpr int Reg(const int mode, const int address) {
return address | mode << 4; return address | mode << 4;
} }
constexpr int PM = 1 << 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) { switch(hours) {
default: return (hours % 12) + (hours > 12 ? PM : 0); default: return (hours % 12) + (hours > 12 ? PM : 0);
case 0: return 12; case 0: return 12;

View File

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

View File

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

View File

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

View File

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

View File

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