From 4add48cffb4cd9720d323978badbc9005d9fc382 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 3 Jul 2021 22:43:20 -0400 Subject: [PATCH] Obey Dave's 8/12MHz programmable divider. --- Machines/Enterprise/Dave.cpp | 83 ++++++++++++++++++++---------- Machines/Enterprise/Dave.hpp | 8 +++ Machines/Enterprise/Enterprise.cpp | 35 ++++++------- 3 files changed, 80 insertions(+), 46 deletions(-) diff --git a/Machines/Enterprise/Dave.cpp b/Machines/Enterprise/Dave.cpp index 08450ff28..6bad6d676 100644 --- a/Machines/Enterprise/Dave.cpp +++ b/Machines/Enterprise/Dave.cpp @@ -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(&target[c << 1]) = *reinterpret_cast(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)) { diff --git a/Machines/Enterprise/Dave.hpp b/Machines/Enterprise/Dave.hpp index ab778aae4..760fd05a4 100644 --- a/Machines/Enterprise/Dave.hpp +++ b/Machines/Enterprise/Dave.hpp @@ -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; diff --git a/Machines/Enterprise/Enterprise.cpp b/Machines/Enterprise/Enterprise.cpp index 7153c1795..12bfc3192 100644 --- a/Machines/Enterprise/Enterprise.cpp +++ b/Machines/Enterprise/Enterprise.cpp @@ -95,8 +95,10 @@ template 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 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 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 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 class ConcreteMachine: Outputs::Speaker::LowpassSpeaker speaker_; HalfCycles time_since_audio_update_; - // The following two should both use the same divider. - JustInTimeActor 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_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.