1
0
mirror of https://github.com/TomHarte/CLK.git synced 2025-12-20 06:16:41 +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

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;
}