mirror of
https://github.com/TomHarte/CLK.git
synced 2024-11-21 21:33:54 +00:00
Obey Dave's 8/12MHz programmable divider.
This commit is contained in:
parent
adbfb009f8
commit
4add48cffb
@ -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)) {
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user