1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-06-25 18:30:07 +00:00

Obey Dave's 8/12MHz programmable divider.

This commit is contained in:
Thomas Harte 2021-07-03 22:43:20 -04:00
parent adbfb009f8
commit 4add48cffb
3 changed files with 80 additions and 46 deletions

View File

@ -16,7 +16,7 @@ Audio::Audio(Concurrency::DeferringAsyncTaskQueue &audio_queue) :
audio_queue_(audio_queue) {}
void Audio::write(uint16_t address, uint8_t value) {
address &= 0xf;
address &= 0x1f;
audio_queue_.defer([address, value, this] {
switch(address) {
case 0: case 2: case 4:
@ -54,6 +54,10 @@ void Audio::write(uint16_t address, uint8_t value) {
break;
case 11: noise_.amplitude[0] = value & 0x3f; break;
case 15: noise_.amplitude[1] = value & 0x3f; break;
case 31:
global_divider_reload_ = 2 + ((value >> 1)&1);
break;
}
});
}
@ -95,7 +99,43 @@ void Audio::update_channel(int c) {
}
void Audio::get_samples(std::size_t number_of_samples, int16_t *target) {
for(size_t c = 0; c < number_of_samples; c++) {
int16_t output_level[2];
size_t c = 0;
while(c < number_of_samples) {
// I'm unclear on the details of the time division multiplexing so,
// for now, just sum the outputs.
output_level[0] =
volume_ *
(use_direct_output_[0] ?
channels_[0].amplitude[0]
: (
channels_[0].amplitude[0] * (channels_[0].output & 1) +
channels_[1].amplitude[0] * (channels_[1].output & 1) +
channels_[2].amplitude[0] * (channels_[2].output & 1) +
noise_.amplitude[0] * noise_.final_output
));
output_level[1] =
volume_ *
(use_direct_output_[1] ?
channels_[0].amplitude[1]
: (
channels_[0].amplitude[1] * (channels_[0].output & 1) +
channels_[1].amplitude[1] * (channels_[1].output & 1) +
channels_[2].amplitude[1] * (channels_[2].output & 1) +
noise_.amplitude[1] * noise_.final_output
));
while(global_divider_ && c < number_of_samples) {
--global_divider_;
*reinterpret_cast<uint32_t *>(&target[c << 1]) = *reinterpret_cast<uint32_t *>(output_level);
++c;
}
global_divider_ = global_divider_reload_;
if(!global_divider_) {
global_divider_ = global_divider_reload_;
}
poly_state_[int(Channel::Distortion::FourBit)] = poly4_.next();
poly_state_[int(Channel::Distortion::FiveBit)] = poly5_.next();
poly_state_[int(Channel::Distortion::SevenBit)] = poly7_.next();
@ -162,30 +202,6 @@ void Audio::get_samples(std::size_t number_of_samples, int16_t *target) {
} else {
noise_.final_output = noise_.output & 1;
}
// I'm unclear on the details of the time division multiplexing so,
// for now, just sum the outputs.
target[(c << 1) + 0] =
volume_ *
(use_direct_output_[0] ?
channels_[0].amplitude[0]
: (
channels_[0].amplitude[0] * (channels_[0].output & 1) +
channels_[1].amplitude[0] * (channels_[1].output & 1) +
channels_[2].amplitude[0] * (channels_[2].output & 1) +
noise_.amplitude[0] * noise_.final_output
));
target[(c << 1) + 1] =
volume_ *
(use_direct_output_[1] ?
channels_[0].amplitude[1]
: (
channels_[0].amplitude[1] * (channels_[0].output & 1) +
channels_[1].amplitude[1] * (channels_[1].output & 1) +
channels_[2].amplitude[1] * (channels_[2].output & 1) +
noise_.amplitude[1] * noise_.final_output
));
}
}
@ -198,7 +214,7 @@ uint8_t TimedInterruptSource::get_new_interrupts() {
}
void TimedInterruptSource::write(uint16_t address, uint8_t value) {
address &= 15;
address &= 0x1f;
switch(address) {
default: break;
@ -224,6 +240,10 @@ void TimedInterruptSource::write(uint16_t address, uint8_t value) {
}
}
} break;
case 31:
global_divider_ = Cycles(2 + ((value >> 1)&1));
break;
}
}
@ -259,7 +279,14 @@ void TimedInterruptSource::update_channel(int c, bool is_linked, int decrement)
}
}
void TimedInterruptSource::run_for(Cycles cycles) {
void TimedInterruptSource::run_for(Cycles duration) {
// Determine total number of ticks.
run_length_ += duration;
const Cycles cycles = run_length_.divide(global_divider_);
if(cycles == Cycles(0)) {
return;
}
// Update the 1Hz interrupt.
one_hz_offset_ -= cycles;
if(one_hz_offset_ <= Cycles(0)) {

View File

@ -45,6 +45,10 @@ class Audio: public Outputs::Speaker::SampleSource {
private:
Concurrency::DeferringAsyncTaskQueue &audio_queue_;
// Global divider (i.e. 8MHz/12Mhz switch).
uint8_t global_divider_;
uint8_t global_divider_reload_ = 2;
// Tone channels.
struct Channel {
// User-set values.
@ -147,6 +151,10 @@ class TimedInterruptSource {
static constexpr Cycles clock_rate{250000};
static constexpr Cycles half_clock_rate{125000};
// Global divider (i.e. 8MHz/12Mhz switch).
Cycles global_divider_;
Cycles run_length_;
// Interrupts that have fired since get_new_interrupts()
// was last called.
uint8_t interrupts_ = 0;

View File

@ -95,8 +95,10 @@ template <bool has_disk_controller> class ConcreteMachine:
nick_(ram_.end() - 65536),
dave_audio_(audio_queue_),
speaker_(dave_audio_) {
// Request a clock of 4Mhz; this'll be mapped upwards for Nick and Dave elsewhere.
set_clock_rate(4'000'000);
// Request a clock of 4Mhz; this'll be mapped upwards for Nick and downwards for Dave elsewhere.
set_clock_rate(4'000'000.0);
speaker_.set_input_rate(float(get_clock_rate()) / float(dave_divider));
ROM::Request request;
using Target = Analyser::Static::Enterprise::Target;
@ -216,9 +218,6 @@ template <bool has_disk_controller> class ConcreteMachine:
page<2>(0x00);
page<3>(0x00);
// Set up audio.
speaker_.set_input_rate(250000.0f); // TODO: a bigger number, and respect the programmable divider.
// Pass on any media.
insert_media(target.media);
if(!target.loading_command.empty()) {
@ -430,6 +429,14 @@ template <bool has_disk_controller> class ConcreteMachine:
case 0xb2: page<2>(*cycle.value); break;
case 0xb3: page<3>(*cycle.value); break;
case 0xbf:
switch((*cycle.value >> 2)&3) {
default: wait_mode_ = WaitMode::None; break;
case 0: wait_mode_ = WaitMode::OnAllAccesses; break;
case 1: wait_mode_ = WaitMode::OnM1; break;
}
[[fallthrough]];
case 0xa0: case 0xa1: case 0xa2: case 0xa3:
case 0xa4: case 0xa5: case 0xa6: case 0xa7:
case 0xa8: case 0xa9: case 0xaa: case 0xab:
@ -495,14 +502,6 @@ template <bool has_disk_controller> class ConcreteMachine:
// b1 = serial status out
LOG("TODO: serial output " << PADHEX(2) << *cycle.value);
break;
case 0xbf:
// TODO: onboard RAM, Dave 8/12Mhz select.
switch((*cycle.value >> 2)&3) {
default: wait_mode_ = WaitMode::None; break;
case 0: wait_mode_ = WaitMode::OnAllAccesses; break;
case 1: wait_mode_ = WaitMode::OnM1; break;
}
break;
}
break;
@ -692,12 +691,12 @@ template <bool has_disk_controller> class ConcreteMachine:
Outputs::Speaker::LowpassSpeaker<Dave::Audio> speaker_;
HalfCycles time_since_audio_update_;
// The following two should both use the same divider.
JustInTimeActor<Dave::TimedInterruptSource, HalfCycles, 1, 16> dave_timer_;
// The divider supplied to the JustInTimeActor and the manual divider used in
// update_audio() should match.
static constexpr int dave_divider = 8;
JustInTimeActor<Dave::TimedInterruptSource, HalfCycles, 1, dave_divider> dave_timer_;
inline void update_audio() {
// TODO: divide by only 8, letting Dave divide itself by a further 2 or 3
// as per its own register.
speaker_.run_for(audio_queue_, time_since_audio_update_.divide_cycles(Cycles(16)));
speaker_.run_for(audio_queue_, time_since_audio_update_.divide_cycles(Cycles(dave_divider)));
}
// MARK: - EXDos card.