mirror of
https://github.com/TomHarte/CLK.git
synced 2025-01-15 05:31:30 +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) {}
|
audio_queue_(audio_queue) {}
|
||||||
|
|
||||||
void Audio::write(uint16_t address, uint8_t value) {
|
void Audio::write(uint16_t address, uint8_t value) {
|
||||||
address &= 0xf;
|
address &= 0x1f;
|
||||||
audio_queue_.defer([address, value, this] {
|
audio_queue_.defer([address, value, this] {
|
||||||
switch(address) {
|
switch(address) {
|
||||||
case 0: case 2: case 4:
|
case 0: case 2: case 4:
|
||||||
@ -54,6 +54,10 @@ void Audio::write(uint16_t address, uint8_t value) {
|
|||||||
break;
|
break;
|
||||||
case 11: noise_.amplitude[0] = value & 0x3f; break;
|
case 11: noise_.amplitude[0] = value & 0x3f; break;
|
||||||
case 15: noise_.amplitude[1] = 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) {
|
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::FourBit)] = poly4_.next();
|
||||||
poly_state_[int(Channel::Distortion::FiveBit)] = poly5_.next();
|
poly_state_[int(Channel::Distortion::FiveBit)] = poly5_.next();
|
||||||
poly_state_[int(Channel::Distortion::SevenBit)] = poly7_.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 {
|
} else {
|
||||||
noise_.final_output = noise_.output & 1;
|
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) {
|
void TimedInterruptSource::write(uint16_t address, uint8_t value) {
|
||||||
address &= 15;
|
address &= 0x1f;
|
||||||
switch(address) {
|
switch(address) {
|
||||||
default: break;
|
default: break;
|
||||||
|
|
||||||
@ -224,6 +240,10 @@ void TimedInterruptSource::write(uint16_t address, uint8_t value) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} break;
|
} 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.
|
// Update the 1Hz interrupt.
|
||||||
one_hz_offset_ -= cycles;
|
one_hz_offset_ -= cycles;
|
||||||
if(one_hz_offset_ <= Cycles(0)) {
|
if(one_hz_offset_ <= Cycles(0)) {
|
||||||
|
@ -45,6 +45,10 @@ class Audio: public Outputs::Speaker::SampleSource {
|
|||||||
private:
|
private:
|
||||||
Concurrency::DeferringAsyncTaskQueue &audio_queue_;
|
Concurrency::DeferringAsyncTaskQueue &audio_queue_;
|
||||||
|
|
||||||
|
// Global divider (i.e. 8MHz/12Mhz switch).
|
||||||
|
uint8_t global_divider_;
|
||||||
|
uint8_t global_divider_reload_ = 2;
|
||||||
|
|
||||||
// Tone channels.
|
// Tone channels.
|
||||||
struct Channel {
|
struct Channel {
|
||||||
// User-set values.
|
// User-set values.
|
||||||
@ -147,6 +151,10 @@ class TimedInterruptSource {
|
|||||||
static constexpr Cycles clock_rate{250000};
|
static constexpr Cycles clock_rate{250000};
|
||||||
static constexpr Cycles half_clock_rate{125000};
|
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()
|
// Interrupts that have fired since get_new_interrupts()
|
||||||
// was last called.
|
// was last called.
|
||||||
uint8_t interrupts_ = 0;
|
uint8_t interrupts_ = 0;
|
||||||
|
@ -95,8 +95,10 @@ template <bool has_disk_controller> class ConcreteMachine:
|
|||||||
nick_(ram_.end() - 65536),
|
nick_(ram_.end() - 65536),
|
||||||
dave_audio_(audio_queue_),
|
dave_audio_(audio_queue_),
|
||||||
speaker_(dave_audio_) {
|
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;
|
ROM::Request request;
|
||||||
using Target = Analyser::Static::Enterprise::Target;
|
using Target = Analyser::Static::Enterprise::Target;
|
||||||
@ -216,9 +218,6 @@ template <bool has_disk_controller> class ConcreteMachine:
|
|||||||
page<2>(0x00);
|
page<2>(0x00);
|
||||||
page<3>(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.
|
// Pass on any media.
|
||||||
insert_media(target.media);
|
insert_media(target.media);
|
||||||
if(!target.loading_command.empty()) {
|
if(!target.loading_command.empty()) {
|
||||||
@ -430,6 +429,14 @@ template <bool has_disk_controller> class ConcreteMachine:
|
|||||||
case 0xb2: page<2>(*cycle.value); break;
|
case 0xb2: page<2>(*cycle.value); break;
|
||||||
case 0xb3: page<3>(*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 0xa0: case 0xa1: case 0xa2: case 0xa3:
|
||||||
case 0xa4: case 0xa5: case 0xa6: case 0xa7:
|
case 0xa4: case 0xa5: case 0xa6: case 0xa7:
|
||||||
case 0xa8: case 0xa9: case 0xaa: case 0xab:
|
case 0xa8: case 0xa9: case 0xaa: case 0xab:
|
||||||
@ -495,14 +502,6 @@ template <bool has_disk_controller> class ConcreteMachine:
|
|||||||
// b1 = serial status out
|
// b1 = serial status out
|
||||||
LOG("TODO: serial output " << PADHEX(2) << *cycle.value);
|
LOG("TODO: serial output " << PADHEX(2) << *cycle.value);
|
||||||
break;
|
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;
|
break;
|
||||||
|
|
||||||
@ -692,12 +691,12 @@ template <bool has_disk_controller> class ConcreteMachine:
|
|||||||
Outputs::Speaker::LowpassSpeaker<Dave::Audio> speaker_;
|
Outputs::Speaker::LowpassSpeaker<Dave::Audio> speaker_;
|
||||||
HalfCycles time_since_audio_update_;
|
HalfCycles time_since_audio_update_;
|
||||||
|
|
||||||
// The following two should both use the same divider.
|
// The divider supplied to the JustInTimeActor and the manual divider used in
|
||||||
JustInTimeActor<Dave::TimedInterruptSource, HalfCycles, 1, 16> dave_timer_;
|
// update_audio() should match.
|
||||||
|
static constexpr int dave_divider = 8;
|
||||||
|
JustInTimeActor<Dave::TimedInterruptSource, HalfCycles, 1, dave_divider> dave_timer_;
|
||||||
inline void update_audio() {
|
inline void update_audio() {
|
||||||
// TODO: divide by only 8, letting Dave divide itself by a further 2 or 3
|
speaker_.run_for(audio_queue_, time_since_audio_update_.divide_cycles(Cycles(dave_divider)));
|
||||||
// as per its own register.
|
|
||||||
speaker_.run_for(audio_queue_, time_since_audio_update_.divide_cycles(Cycles(16)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - EXDos card.
|
// MARK: - EXDos card.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user