mirror of
https://github.com/TomHarte/CLK.git
synced 2025-04-09 15:39:08 +00:00
Merge pull request #1449 from TomHarte/FullerAudio
Switch to full-clock PWM audio implementation, with noise generator.
This commit is contained in:
commit
58ef91a7b1
@ -13,12 +13,6 @@
|
||||
|
||||
namespace Commodore::Plus4 {
|
||||
|
||||
// PAL: / 160 i.e. 5*32
|
||||
// NTSC: / 128 i.e. 4*32
|
||||
|
||||
// 111860.78125 = NTSC
|
||||
// 110840.46875 = PAL
|
||||
|
||||
class Audio: public Outputs::Speaker::BufferSource<Audio, false> {
|
||||
public:
|
||||
Audio(Concurrency::AsyncTaskQueue<false> &audio_queue) :
|
||||
@ -26,88 +20,126 @@ public:
|
||||
|
||||
template <Outputs::Speaker::Action action>
|
||||
void apply_samples(std::size_t size, Outputs::Speaker::MonoSample *const target) {
|
||||
const auto count_frequency = [&](int index) {
|
||||
++counts_[index];
|
||||
if(counts_[index] == (frequencies_[index] ^ 1023) * frequency_multiplier_) {
|
||||
states_[index] ^= 1;
|
||||
counts_[index] = 0;
|
||||
} else if(counts_[index] == 1024 * frequency_multiplier_) {
|
||||
counts_[index] = 0;
|
||||
// Divide by frequency_multiplier_ and multiply by 8 to get the "8Mhz clock".
|
||||
// Each 32-window cycle of that clock is a single complete tick of the audio engine.
|
||||
|
||||
const auto update_channel = [&](int channel) {
|
||||
if(sound_dc_ || channels_[channel].count == 0x3ff) {
|
||||
channels_[channel].count = (channels_[channel].frequency + 1) & 0x3ff;
|
||||
} else {
|
||||
++channels_[channel].count;
|
||||
if(channels_[channel].count == 0x3ff) {
|
||||
channels_[channel].state ^= 1;
|
||||
}
|
||||
}
|
||||
|
||||
channels_[channel].pwm_count = 0;
|
||||
};
|
||||
|
||||
if(sound_dc_) {
|
||||
Outputs::Speaker::fill<action>(target, target + size, Outputs::Speaker::MonoSample(volume_ * 2));
|
||||
} else {
|
||||
// TODO: noise generation.
|
||||
const auto update_noise = [&] {
|
||||
noise_ <<= 1;
|
||||
noise_ |= 1 ^ (noise_ >> 7) ^ (noise_ >> 5) ^ (noise_ >> 4) ^ (noise_ >> 1);
|
||||
};
|
||||
|
||||
for(size_t c = 0; c < size; c++) {
|
||||
count_frequency(0);
|
||||
count_frequency(1);
|
||||
for(size_t c = 0; c < size; c++) {
|
||||
subcycle_ = (subcycle_ + 1) & 31;
|
||||
|
||||
Outputs::Speaker::apply<action>(
|
||||
target[c],
|
||||
Outputs::Speaker::MonoSample(
|
||||
(
|
||||
((states_[0] & masks_[0]) * external_volume_) +
|
||||
((states_[1] & masks_[1]) * external_volume_)
|
||||
) * volume_
|
||||
));
|
||||
if(subcycle_ == 0) update_channel(0);
|
||||
if(subcycle_ == 16) {
|
||||
update_channel(1);
|
||||
update_noise();
|
||||
}
|
||||
|
||||
++ channels_[0].pwm_count;
|
||||
++ channels_[1].pwm_count;
|
||||
|
||||
if(sound_dc_) {
|
||||
channels_[0].state = 0;
|
||||
channels_[1].state = 0;
|
||||
noise_ = 0;
|
||||
}
|
||||
|
||||
target[c] =
|
||||
external_volume_ * (
|
||||
((channels_[0].pwm_count < volume_) & channels_[0].enabled & ~channels_[0].state) |
|
||||
((channels_[1].pwm_count < volume_) &
|
||||
(
|
||||
(channels_[1].enabled & ~channels_[1].state) |
|
||||
(sound2_noise_on_ & noise_ & 1)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
r_ += size;
|
||||
}
|
||||
|
||||
void set_sample_volume_range(const std::int16_t range) {
|
||||
external_volume_ = range / (2 * 9); // Two channels and nine output levels.
|
||||
external_volume_ = range; // / (2 * 9); // Two channels and nine output levels.
|
||||
}
|
||||
|
||||
bool is_zero_level() const {
|
||||
return !(masks_[0] || masks_[1] || sound2_noise_on_) || !volume_;
|
||||
return !(channels_[0].enabled || channels_[1].enabled || sound2_noise_on_) || !volume_;
|
||||
}
|
||||
|
||||
template <int channel> void set_frequency_low(uint8_t value) {
|
||||
audio_queue_.enqueue([this, value] {
|
||||
frequencies_[channel] = (frequencies_[channel] & 0xff00) | value;
|
||||
channels_[channel].frequency = (channels_[channel].frequency & 0xff00) | value;
|
||||
});
|
||||
}
|
||||
|
||||
template <int channel> void set_frequency_high(uint8_t value) {
|
||||
audio_queue_.enqueue([this, value] {
|
||||
frequencies_[channel] = (frequencies_[channel] & 0x00ff) | ((value&3) << 8);
|
||||
channels_[channel].frequency = (channels_[channel].frequency & 0x00ff) | ((value&3) << 8);
|
||||
});
|
||||
}
|
||||
|
||||
void set_constrol(uint8_t value) {
|
||||
void set_control(const uint8_t value) {
|
||||
audio_queue_.enqueue([this, value] {
|
||||
switch(value & 0xf) {
|
||||
case 0: volume_ = 31; break;
|
||||
case 1: volume_ = 30; break;
|
||||
case 2: volume_ = 28; break;
|
||||
case 3: volume_ = 26; break;
|
||||
case 4: volume_ = 24; break;
|
||||
case 5: volume_ = 22; break;
|
||||
case 6: volume_ = 20; break;
|
||||
case 7: volume_ = 18; break;
|
||||
default: volume_ = 16; break;
|
||||
}
|
||||
volume_ = std::min(value & 0xf, 8); // Only nine volumes are available.
|
||||
masks_[0] = (value & 0x10) ? 1 : 0;
|
||||
|
||||
masks_[1] = (value & 0x20) ? 1 : 0;
|
||||
channels_[0].enabled = (value & 0x10) ? 1 : 0;
|
||||
channels_[1].enabled = (value & 0x20) ? 1 : 0;
|
||||
sound2_noise_on_ = (value & 0x40) && !(value & 0x20);
|
||||
|
||||
sound_dc_ = value & 0x80;
|
||||
});
|
||||
}
|
||||
|
||||
void set_divider(const uint8_t value) {
|
||||
audio_queue_.enqueue([this, value] {
|
||||
frequency_multiplier_ = 32 * (value & 0x40 ? 4 : 5);
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
// Calling-thread state.
|
||||
Concurrency::AsyncTaskQueue<false> &audio_queue_;
|
||||
|
||||
// Audio-thread state.
|
||||
int16_t external_volume_ = 0;
|
||||
int frequencies_[2]{};
|
||||
int frequency_multiplier_ = 5;
|
||||
int counts_[2]{};
|
||||
|
||||
int states_[2]{};
|
||||
int masks_[2]{};
|
||||
int subcycle_ = 0;
|
||||
struct Channel {
|
||||
int frequency = 0;
|
||||
int count = 0;
|
||||
int pwm_count = 0;
|
||||
int state = 0;
|
||||
int enabled = 0;
|
||||
} channels_[2];
|
||||
uint8_t noise_ = 0;
|
||||
|
||||
bool sound2_noise_on_ = false;
|
||||
bool sound_dc_ = false;
|
||||
int volume_ = 0;
|
||||
|
||||
int r_ = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -146,7 +146,7 @@ public:
|
||||
const auto clock = clock_rate(false);
|
||||
media_divider_ = Cycles(clock);
|
||||
set_clock_rate(clock);
|
||||
speaker_.set_input_rate(float(clock) / 32.0f);
|
||||
speaker_.set_input_rate(float(clock));
|
||||
|
||||
// TODO: decide whether to attach a 1541 for real.
|
||||
const bool has_c1541 = true;
|
||||
@ -347,7 +347,11 @@ public:
|
||||
case 0xff04: timers_.write<4>(*value); break;
|
||||
case 0xff05: timers_.write<5>(*value); break;
|
||||
case 0xff06: video_.write<0xff06>(*value); break;
|
||||
case 0xff07: video_.write<0xff07>(*value); break;
|
||||
case 0xff07:
|
||||
video_.write<0xff07>(*value);
|
||||
update_audio();
|
||||
audio_.set_divider(*value);
|
||||
break;
|
||||
case 0xff08:
|
||||
// Observation here: the kernel posts a 0 to this
|
||||
// address upon completing each keyboard scan cycle,
|
||||
@ -398,7 +402,7 @@ public:
|
||||
case 0xff11:
|
||||
ff11_ = *value;
|
||||
update_audio();
|
||||
audio_.set_constrol(*value);
|
||||
audio_.set_control(*value);
|
||||
break;
|
||||
case 0xff12:
|
||||
ff12_ = *value & 0x3f;
|
||||
@ -540,7 +544,7 @@ private:
|
||||
Cycles time_since_audio_update_;
|
||||
Outputs::Speaker::PullLowpass<Audio> speaker_;
|
||||
void update_audio() {
|
||||
speaker_.run_for(audio_queue_, time_since_audio_update_.divide(Cycles(32)));
|
||||
speaker_.run_for(audio_queue_, time_since_audio_update_.flush<Cycles>());
|
||||
}
|
||||
|
||||
// MARK: - MappedKeyboardMachine.
|
||||
|
Loading…
x
Reference in New Issue
Block a user