mirror of
https://github.com/TomHarte/CLK.git
synced 2026-01-24 23:35:12 +00:00
159 lines
4.2 KiB
C++
159 lines
4.2 KiB
C++
//
|
|
// SID.cpp
|
|
// Clock Signal
|
|
//
|
|
// Created by Thomas Harte on 07/11/2025.
|
|
// Copyright © 2025 Thomas Harte. All rights reserved.
|
|
//
|
|
|
|
#include "SID.hpp"
|
|
|
|
using namespace MOS::SID;
|
|
|
|
SID::SID(Concurrency::AsyncTaskQueue<false> &audio_queue) : audio_queue_(audio_queue) {}
|
|
|
|
void SID::write(const Numeric::SizedInt<5> address, const uint8_t value) {
|
|
last_write_ = value;
|
|
audio_queue_.enqueue([=, this] {
|
|
const auto voice = [&]() -> Voice & {
|
|
return voices_[address.get() / 7];
|
|
};
|
|
const auto oscillator = [&]() -> Voice::Oscillator & {
|
|
return voice().oscillator;
|
|
};
|
|
const auto adsr = [&]() -> Voice::ADSR & {
|
|
return voice().adsr;
|
|
};
|
|
|
|
switch(address.get()) {
|
|
case 0x00: case 0x07: case 0x0e:
|
|
oscillator().pitch = (oscillator().pitch & 0xff'00'00) | uint32_t(value << 8);
|
|
break;
|
|
case 0x01: case 0x08: case 0x0f:
|
|
oscillator().pitch = (oscillator().pitch & 0x00'ff'00) | uint32_t(value << 16);
|
|
break;
|
|
case 0x02: case 0x09: case 0x10:
|
|
oscillator().pulse_width = (oscillator().pitch & 0xf0'00'00'00) | uint32_t(value << 20);
|
|
break;
|
|
case 0x03: case 0x0a: case 0x11:
|
|
// The top bit of the phase counter is inverted; since it'll be compared directly with the
|
|
// pulse width, invert that bit too.
|
|
oscillator().pulse_width =
|
|
(
|
|
(oscillator().pitch & 0x0f'f0'00'00) |
|
|
uint32_t(value << 28)
|
|
);
|
|
break;
|
|
case 0x04: case 0x0b: case 0x12:
|
|
voice().set_control(value);
|
|
break;
|
|
case 0x05: case 0x0c: case 0x13:
|
|
adsr().attack = value >> 4;
|
|
adsr().decay = value;
|
|
adsr().set_phase(adsr().phase);
|
|
break;
|
|
case 0x06: case 0x0d: case 0x14:
|
|
adsr().sustain = (value >> 4) | (value & 0xf0);
|
|
adsr().release = value;
|
|
adsr().set_phase(adsr().phase);
|
|
break;
|
|
|
|
case 0x15:
|
|
filter_cutoff_.load<0, 3>(value);
|
|
update_filter();
|
|
break;
|
|
case 0x16:
|
|
filter_cutoff_.load<3>(value);
|
|
update_filter();
|
|
break;
|
|
case 0x17:
|
|
filter_channels_ = value;
|
|
filter_resonance_ = value >> 4;
|
|
update_filter();
|
|
break;
|
|
case 0x18:
|
|
volume_ = value & 0x0f;
|
|
filter_mode_ = value >> 4;
|
|
update_filter();
|
|
break;
|
|
}
|
|
});
|
|
}
|
|
|
|
void SID::update_filter() {
|
|
// TODO!
|
|
}
|
|
|
|
uint8_t SID::read(const Numeric::SizedInt<5> address) {
|
|
(void)address;
|
|
return last_write_;
|
|
}
|
|
|
|
void SID::set_sample_volume_range(const std::int16_t range) {
|
|
range_ = range;
|
|
}
|
|
|
|
bool SID::is_zero_level() const {
|
|
return false;
|
|
}
|
|
|
|
template <Outputs::Speaker::Action action>
|
|
void SID::apply_samples(const std::size_t number_of_samples, Outputs::Speaker::MonoSample *const target) {
|
|
for(std::size_t c = 0; c < number_of_samples; c++) {
|
|
// Advance phase.
|
|
voices_[0].update();
|
|
voices_[1].update();
|
|
voices_[2].update();
|
|
|
|
// Apply hard synchronisations.
|
|
voices_[0].synchronise(voices_[2]);
|
|
voices_[1].synchronise(voices_[0]);
|
|
voices_[2].synchronise(voices_[1]);
|
|
|
|
// Construct filtered and unfiltered output.
|
|
const uint16_t outputs[3] = {
|
|
voices_[0].output(voices_[2]),
|
|
voices_[1].output(voices_[0]),
|
|
voices_[2].output(voices_[1]),
|
|
};
|
|
|
|
const uint16_t direct_sample =
|
|
(filter_channels_.bit<0>() ? 0 : outputs[0]) +
|
|
(filter_channels_.bit<1>() ? 0 : outputs[1]) +
|
|
(filter_channels_.bit<2>() ? 0 : outputs[2]);
|
|
|
|
const int16_t filtered_sample =
|
|
filter_.apply(
|
|
(filter_channels_.bit<0>() ? outputs[0] : 0) +
|
|
(filter_channels_.bit<1>() ? outputs[1] : 0) +
|
|
(filter_channels_.bit<2>() ? outputs[2] : 0)
|
|
);
|
|
|
|
// Sum, apply volume and output.
|
|
const auto sample =
|
|
(
|
|
volume_ * (
|
|
direct_sample +
|
|
filtered_sample
|
|
- 227 // DC offset.
|
|
)
|
|
- 88732
|
|
) / 3;
|
|
// Maximum range of above: 15 * (4095 * 3 - 227) = [-3405, 180870]
|
|
// So subtracting 88732 will move to the centre of the range, and 3 is the smallest
|
|
// integer that avoids clipping.
|
|
|
|
Outputs::Speaker::apply<action>(
|
|
target[c],
|
|
Outputs::Speaker::MonoSample((sample * range_) >> 16)
|
|
);
|
|
}
|
|
}
|
|
|
|
template void SID::apply_samples<Outputs::Speaker::Action::Mix>(
|
|
std::size_t, Outputs::Speaker::MonoSample *);
|
|
template void SID::apply_samples<Outputs::Speaker::Action::Store>(
|
|
std::size_t, Outputs::Speaker::MonoSample *);
|
|
template void SID::apply_samples<Outputs::Speaker::Action::Ignore>(
|
|
std::size_t, Outputs::Speaker::MonoSample *);
|