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:
parent
5545906063
commit
3addb8d72b
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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];
|
||||
|
@ -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];
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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 0–15.
|
||||
*/
|
||||
void set_attack_rate(int rate) {
|
||||
attack_rate_ = rate << 2;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the decay rate, which should be in the range 0–15.
|
||||
*/
|
||||
void set_decay_rate(int rate) {
|
||||
decay_rate_ = rate << 2;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the release rate, which should be in the range 0–15.
|
||||
*/
|
||||
void set_release_rate(int rate) {
|
||||
release_rate_ = rate << 2;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the sustain level, which should be in the range 0–15.
|
||||
*/
|
||||
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 0–15.
|
||||
*/
|
||||
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 0–15.
|
||||
*/
|
||||
void set_decay_rate(const int rate) {
|
||||
decay_rate_ = rate << 2;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the release rate, which should be in the range 0–15.
|
||||
*/
|
||||
void set_release_rate(const int rate) {
|
||||
release_rate_ = rate << 2;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the sustain level, which should be in the range 0–15.
|
||||
*/
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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_);
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
};
|
||||
|
||||
/*!
|
||||
|
Loading…
Reference in New Issue
Block a user