1
0
mirror of https://github.com/TomHarte/CLK.git synced 2026-04-21 17:16:44 +00:00

Clean up: pull out noise generation, remove code from header.

This commit is contained in:
Thomas Harte
2025-11-13 13:44:53 -05:00
parent dbbb1d60fc
commit fd32e63459
2 changed files with 249 additions and 197 deletions
+218 -1
View File
@@ -18,6 +18,8 @@ SID::SID(Concurrency::AsyncTaskQueue<false> &audio_queue) :
15000.0f
) {}
// MARK: - Programmer interface.
void SID::write(const Numeric::SizedInt<5> address, const uint8_t value) {
last_write_ = value;
audio_queue_.enqueue([=, this] {
@@ -111,10 +113,23 @@ void SID::update_filter() {
type,
1'000'000.0f,
30.0f + float(filter_cutoff_.get()) * 5.8f,
0.707f + float(filter_resonance_.get()) * 0.125f,
0.707f + float(filter_resonance_.get()) * 0.2862f,
6.0f,
true
);
// Filter cutoff: the data sheet provides that it is linear, and "approximate Cutoff Frequency
// ranges between 30Hz and 12KHz [with recommended externally-supplied capacitors]."
//
// It's an 11-bit number, so the above is "approximate"ly right.
// Resonance: a complete from-thin-air guess. The data sheet says merely:
//
// "There are 16 Resonance settings ranging from about 0.707 (Critical Damping) for a count of 0
// to a maximum for a count of 15"
//
// i.e. no information is given on the maximum. I've taken it to be 5-ish per commentary on more general sites
// that 5 is a typical ceiling for the resonance factor.
}
uint8_t SID::read(const Numeric::SizedInt<5> address) {
@@ -122,6 +137,208 @@ uint8_t SID::read(const Numeric::SizedInt<5> address) {
return last_write_;
}
// MARK: - Oscillators.
void Voice::Oscillator::reset_phase() {
phase = PhaseReload;
}
bool Voice::Oscillator::did_raise_b23() const {
return previous_phase > phase;
}
bool Voice::Oscillator::did_raise_b19() const {
static constexpr int NoiseBit = 1 << (19 + 8);
return (previous_phase ^ phase) & phase & NoiseBit;
}
uint16_t Voice::Oscillator::sawtooth_output() const {
return (phase >> 20) ^ 0x800;
}
// MARK: - Noise generator.
uint16_t Voice::NoiseGenerator::output() const {
// Uses bits: 20, 18, 14, 11, 9, 5, 2 and 0, plus four more zero bits.
return
((noise >> 9) & 0b1000'0000'0000) | // b20 -> b11
((noise >> 8) & 0b0100'0000'0000) | // b18 -> b10
((noise >> 5) & 0b0010'0000'0000) | // b14 -> b9
((noise >> 3) & 0b0001'0000'0000) | // b11 -> b8
((noise >> 2) & 0b0000'1000'0000) | // b9 -> b7
((noise << 1) & 0b0000'0100'0000) | // b5 -> b6
((noise << 3) & 0b0000'0010'0000) | // b2 -> b5
((noise << 4) & 0b0000'0001'0000); // b0 -> b4
}
void Voice::NoiseGenerator::update(const bool test) {
noise =
(noise << 1) |
(((noise >> 17) ^ ((noise >> 22) | test)) & 1);
}
// MARK: - ADSR.
void Voice::ADSR::set_phase(const Phase new_phase) {
static constexpr uint16_t rate_prescaler[] = {
9, 32, 63, 95, 149, 220, 267, 313, 392, 977, 1954, 3126, 3907, 11720, 19532, 31251
};
static_assert(sizeof(rate_prescaler) / sizeof(*rate_prescaler) == 16);
phase = new_phase;
switch(phase) {
case Phase::Attack: rate_counter_target = rate_prescaler[attack.get()]; break;
case Phase::DecayAndHold: rate_counter_target = rate_prescaler[decay.get()]; break;
case Phase::Release: rate_counter_target = rate_prescaler[release.get()]; break;
}
}
// MARK: - Voices.
void Voice::set_control(const uint8_t new_control) {
const bool old_gate = gate();
control = new_control;
if(gate() && !old_gate) {
adsr.set_phase(ADSR::Phase::Attack);
} else if(!gate() && old_gate) {
adsr.set_phase(ADSR::Phase::Release);
}
}
bool Voice::noise() const { return control.bit<7>(); }
bool Voice::pulse() const { return control.bit<6>(); }
bool Voice::sawtooth() const { return control.bit<5>(); }
bool Voice::triangle() const { return control.bit<4>(); }
bool Voice::test() const { return control.bit<3>(); }
bool Voice::ring_mod() const { return control.bit<2>(); }
bool Voice::sync() const { return control.bit<1>(); }
bool Voice::gate() const { return control.bit<0>(); }
void Voice::update() {
// Oscillator.
oscillator.previous_phase = oscillator.phase;
if(test()) {
oscillator.phase = 0;
} else {
oscillator.phase += oscillator.pitch;
if(oscillator.did_raise_b19()) {
noise_generator.update(test());
}
}
// ADSR.
// First prescalar, which is a function of the programmer-set rate.
++ adsr.rate_counter;
if(adsr.rate_counter == adsr.rate_counter_target) {
adsr.rate_counter = 0;
// Second prescalar, which approximates an exponential.
static constexpr uint8_t exponential_prescaler[] = {
1, // 0
30, 30, 30, 30, 30, 30, // 16
16, 16, 16, 16, 16, 16, 16, 16, // 714
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, // 1526
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, // 2754
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 5594
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1,
};
static_assert(sizeof(exponential_prescaler) == 256);
static_assert(exponential_prescaler[0] == 1);
static_assert(exponential_prescaler[1] == 30);
static_assert(exponential_prescaler[6] == 30);
static_assert(exponential_prescaler[7] == 16);
static_assert(exponential_prescaler[14] == 16);
static_assert(exponential_prescaler[15] == 8);
static_assert(exponential_prescaler[26] == 8);
static_assert(exponential_prescaler[27] == 4);
static_assert(exponential_prescaler[54] == 4);
static_assert(exponential_prescaler[55] == 2);
static_assert(exponential_prescaler[94] == 2);
static_assert(exponential_prescaler[95] == 1);
static_assert(exponential_prescaler[255] == 1);
if(adsr.phase == ADSR::Phase::Attack) {
++adsr.envelope;
// TODO: what really resets the exponential counter? If anything?
adsr.exponential_counter = 0;
if(adsr.envelope == 0xff) {
adsr.set_phase(ADSR::Phase::DecayAndHold);
}
} else {
++adsr.exponential_counter;
if(adsr.exponential_counter == exponential_prescaler[adsr.envelope]) {
adsr.exponential_counter = 0;
if(adsr.envelope && (adsr.envelope != adsr.sustain || adsr.phase != ADSR::Phase::DecayAndHold)) {
--adsr.envelope;
}
}
}
}
}
void Voice::synchronise(const Voice &prior) {
// Only oscillator work to do here.
if(
sync() &&
prior.oscillator.did_raise_b23()
) {
oscillator.phase = Oscillator::PhaseReload;
}
}
uint16_t Voice::pulse_output() const {
return (
(oscillator.phase ^ 0x8000'0000) < oscillator.pulse_width
) ? 0 : MaxWaveformValue;
}
uint16_t Voice::triangle_output(const Voice &prior) const {
const uint16_t sawtooth = oscillator.sawtooth_output();
const uint16_t xor_mask1 = sawtooth;
const uint16_t xor_mask2 = ring_mod() ? prior.sawtooth() : 0;
const uint16_t xor_mask = ((xor_mask1 ^ xor_mask2) & 0x800) ? 0xfff : 0x000;
return ((sawtooth << 1) ^ xor_mask) & 0xfff;
}
uint16_t Voice::output(const Voice &prior) const {
// TODO: true composite waves.
//
// My current understanding on this: if multiple waveforms are enabled, the pull to zero beats the
// pull to one on any line where the two compete. But the twist is that the lines are not necessarily
// one per bit since they lead to a common ground. Ummm, I think.
//
// Anyway, first pass: logical AND. It's not right. It will temporarily do.
uint16_t output = MaxWaveformValue;
if(pulse()) output &= pulse_output();
if(sawtooth()) output &= oscillator.sawtooth_output();
if(triangle()) output &= triangle_output(prior);
if(noise()) output &= noise_generator.output();
return (output * adsr.envelope) / 255;
}
// MARK: - Wave generation
void SID::set_sample_volume_range(const std::int16_t range) {
range_ = range;
}
+31 -196
View File
@@ -32,40 +32,11 @@ struct Voice {
static constexpr uint32_t PhaseReload = 0x8000'0000;
uint32_t phase = PhaseReload;
uint32_t previous_phase = PhaseReload;
void reset_phase() {
phase = PhaseReload;
}
static constexpr uint32_t NoiseReload = 0x7'ffff;
uint32_t noise = NoiseReload;
bool did_raise_b23() const {
return previous_phase > phase;
}
bool did_raise_b19() const {
static constexpr int NoiseBit = 1 << (19 + 8);
return (previous_phase ^ phase) & phase & NoiseBit;
}
uint16_t sawtooth_output() const {
return (phase >> 20) ^ 0x800;
}
uint16_t noise_output() const {
// Uses bits: 20, 18, 14, 11, 9, 5, 2 and 0, plus four more zero bits.
return
((noise >> 9) & 0b1000'0000'0000) | // b20 -> b11
((noise >> 8) & 0b0100'0000'0000) | // b18 -> b10
((noise >> 5) & 0b0010'0000'0000) | // b14 -> b9
((noise >> 3) & 0b0001'0000'0000) | // b11 -> b8
((noise >> 2) & 0b0000'1000'0000) | // b9 -> b7
((noise << 1) & 0b0000'0100'0000) | // b5 -> b6
((noise << 3) & 0b0000'0010'0000) | // b2 -> b5
((noise << 4) & 0b0000'0001'0000); // b0 -> b4
}
void update_noise(const bool test) {
noise =
(noise << 1) |
(((noise >> 17) ^ ((noise >> 22) | test)) & 1);
}
void reset_phase();
bool did_raise_b23() const;
bool did_raise_b19() const;
uint16_t sawtooth_output() const;
} oscillator;
struct ADSR {
// Programmer inputs.
@@ -87,173 +58,36 @@ struct Voice {
uint8_t exponential_counter;
uint8_t envelope;
void set_phase(const Phase new_phase) {
static constexpr uint16_t rate_prescaler[] = {
9, 32, 63, 95, 149, 220, 267, 313, 392, 977, 1954, 3126, 3907, 11720, 19532, 31251
};
static_assert(sizeof(rate_prescaler) / sizeof(*rate_prescaler) == 16);
phase = new_phase;
switch(phase) {
case Phase::Attack: rate_counter_target = rate_prescaler[attack.get()]; break;
case Phase::DecayAndHold: rate_counter_target = rate_prescaler[decay.get()]; break;
case Phase::Release: rate_counter_target = rate_prescaler[release.get()]; break;
}
}
void set_phase(const Phase);
} adsr;
struct NoiseGenerator {
static constexpr uint32_t NoiseReload = 0x7'ffff;
uint32_t noise = NoiseReload;
uint16_t output() const;
void update(const bool test);
} noise_generator;
void set_control(const uint8_t);
void update();
void synchronise(const Voice &prior);
uint16_t output(const Voice &prior) const;
private:
Numeric::SizedInt<8> control;
void set_control(const uint8_t new_control) {
const bool old_gate = gate();
control = new_control;
if(gate() && !old_gate) {
adsr.set_phase(ADSR::Phase::Attack);
} else if(!gate() && old_gate) {
adsr.set_phase(ADSR::Phase::Release);
}
}
bool noise() const { return control.bit<7>(); }
bool pulse() const { return control.bit<6>(); }
bool sawtooth() const { return control.bit<5>(); }
bool triangle() const { return control.bit<4>(); }
bool test() const { return control.bit<3>(); }
bool ring_mod() const { return control.bit<2>(); }
bool sync() const { return control.bit<1>(); }
bool gate() const { return control.bit<0>(); }
void update() {
// Oscillator.
oscillator.previous_phase = oscillator.phase;
if(test()) {
oscillator.phase = 0;
} else {
oscillator.phase += oscillator.pitch;
if(oscillator.did_raise_b19()) {
oscillator.update_noise(test());
}
}
// ADSR.
// First prescalar, which is a function of the programmer-set rate.
++ adsr.rate_counter;
if(adsr.rate_counter == adsr.rate_counter_target) {
adsr.rate_counter = 0;
// Second prescalar, which approximates an exponential.
static constexpr uint8_t exponential_prescaler[] = {
1, // 0
30, 30, 30, 30, 30, 30, // 16
16, 16, 16, 16, 16, 16, 16, 16, // 714
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, // 1526
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, // 2754
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 5594
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1,
};
static_assert(sizeof(exponential_prescaler) == 256);
static_assert(exponential_prescaler[0] == 1);
static_assert(exponential_prescaler[1] == 30);
static_assert(exponential_prescaler[6] == 30);
static_assert(exponential_prescaler[7] == 16);
static_assert(exponential_prescaler[14] == 16);
static_assert(exponential_prescaler[15] == 8);
static_assert(exponential_prescaler[26] == 8);
static_assert(exponential_prescaler[27] == 4);
static_assert(exponential_prescaler[54] == 4);
static_assert(exponential_prescaler[55] == 2);
static_assert(exponential_prescaler[94] == 2);
static_assert(exponential_prescaler[95] == 1);
static_assert(exponential_prescaler[255] == 1);
if(adsr.phase == ADSR::Phase::Attack) {
++adsr.envelope;
// TODO: what really resets the exponential counter? If anything?
adsr.exponential_counter = 0;
if(adsr.envelope == 0xff) {
adsr.set_phase(ADSR::Phase::DecayAndHold);
}
} else {
++adsr.exponential_counter;
if(adsr.exponential_counter == exponential_prescaler[adsr.envelope]) {
adsr.exponential_counter = 0;
if(adsr.envelope && (adsr.envelope != adsr.sustain || adsr.phase != ADSR::Phase::DecayAndHold)) {
--adsr.envelope;
}
}
}
}
}
void synchronise(const Voice &prior) {
// Only oscillator work to do here.
if(
sync() &&
prior.oscillator.did_raise_b23()
) {
oscillator.phase = Oscillator::PhaseReload;
}
}
bool noise() const;
bool pulse() const;
bool sawtooth() const;
bool triangle() const;
bool test() const;
bool ring_mod() const;
bool sync() const;
bool gate() const;
static constexpr uint16_t MaxWaveformValue = (1 << 12) - 1;
uint16_t sawtooth_output() const {
return oscillator.sawtooth_output();
}
uint16_t pulse_output() const {
return (
(oscillator.phase ^ 0x8000'0000) < oscillator.pulse_width
) ? 0 : MaxWaveformValue;
}
uint16_t triangle_output(const Voice &prior) const {
const uint16_t sawtooth = oscillator.sawtooth_output();
const uint16_t xor_mask1 = sawtooth;
const uint16_t xor_mask2 = ring_mod() ? prior.sawtooth() : 0;
const uint16_t xor_mask = ((xor_mask1 ^ xor_mask2) & 0x800) ? 0xfff : 0x000;
return ((sawtooth << 1) ^ xor_mask) & 0xfff;
}
uint16_t noise_output() const {
return oscillator.noise_output();
}
uint16_t output(const Voice &prior) const {
// TODO: true composite waves.
//
// My current understanding on this: if multiple waveforms are enabled, the pull to zero beats the
// pull to one on any line where the two compete. But the twist is that the lines are not necessarily
// one per bit since they lead to a common ground. Ummm, I think.
//
// Anyway, first pass: logical AND. It's not right. It will temporarily do.
uint16_t output = MaxWaveformValue;
if(pulse()) output &= pulse_output();
if(sawtooth()) output &= sawtooth_output();
if(triangle()) output &= triangle_output(prior);
if(noise()) output &= noise_output();
return (output * adsr.envelope) / 255;
}
uint16_t pulse_output() const;
uint16_t triangle_output(const Voice &prior) const;
};
class SID: public Outputs::Speaker::BufferSource<SID, false> {
@@ -271,12 +105,13 @@ public:
private:
Concurrency::AsyncTaskQueue<false> &audio_queue_;
Voice voices_[3];
// TODO: an emulator thread copy of voices needs to be kept too, to do the digital stuff, as
// the current output of voice 3 can be read. Probably best if the audio thread posts its most
// recent copy atomically and the emulator thread just catches up from whatever it has? I don't
// think that spinning on voice 3 is common.
Voice voices_[3];
uint8_t last_write_;
int16_t range_ = 0;