diff --git a/Machines/Amiga/Audio.cpp b/Machines/Amiga/Audio.cpp index 02ce43e8a..f6c5d2b27 100644 --- a/Machines/Amiga/Audio.cpp +++ b/Machines/Amiga/Audio.cpp @@ -83,93 +83,8 @@ void Audio::output() { } } -bool Audio::Channel::output() { - // The following attempts to reproduce the audio state diagram provided in - // Commodore's Hardware Reference Manual. - // - // See big comment at the foot of this file. - - switch(state) { - case State::Disabled: - // Test for top loop of Commodore's state diagram, - // which permits CPU-driven audio output. - if(has_data && !dma_enabled && !interrupt_pending) { - state = State::PlayingHigh; - - data_latch = data; // i.e. pbufld1 - has_data = false; - period_counter = period; // i.e. percntrld - // TODO: volcntrld (see above). - - // Request an interrupt. - return true; - } - - // Test for DMA-style transition. - if(dma_enabled) { - state = State::WaitingForDummyDMA; - - period_counter = period; // i.e. percntrld - length_counter = length; // i.e. lenctrld - - break; - } - break; - - case State::WaitingForDummyDMA: - if(!dma_enabled) { - state = State::Disabled; - break; - } - - if(dma_enabled && has_data) { - has_data = false; - state = State::WaitingForDMA; - if(length == 1) { - length_counter = length; - return true; - } - break; - } - break; - - case State::WaitingForDMA: - if(!dma_enabled) { - state = State::Disabled; - break; - } - - if(dma_enabled && has_data) { - data_latch = data; - has_data = false; - period_counter = period; // i.e. percntrld - - state = State::PlayingHigh; - break; - } - break; - - case State::PlayingHigh: - // TODO: penhi (i.e. output high byte). - -- period_counter; - - // This is a reasonable guess as to the exit condition for this node; - // Commodore doesn't document. - if(period_counter == 1) { - state = State::PlayingLow; - - // TODO: if attach period, reload output buffer. - } - break; - - default: break; - } - - return false; -} - /* - Big spiel on the state machine implemented above: + Big spiel on the state machine: Commodore's Hardware Rerefence Manual provides the audio subsystem's state machine, so I've just tried to reimplement it verbatim. It's depicted @@ -225,7 +140,7 @@ bool Audio::Channel::output() { if: AUDxON, and AUDxDAT action: 1. volcntrld, percentrld, pbufid1 - 2. if napnav, AUDxDR + 2. if napnav, then AUDxDR @@ -277,11 +192,11 @@ bool Audio::Channel::output() { Definitions: - AUDxON DMA on "x" indicates channel number (signal from DMACON). + AUDxON DMA on "x" indicates channel number (signal from DMACON). - AUDxIP Audio interrupt pending (input to channel from interrupt circuitry). + AUDxIP Audio interrupt pending (input to channel from interrupt circuitry). - AUDxIR Audio interrupt request (output from channel to interrupt circuitry) + AUDxIR Audio interrupt request (output from channel to interrupt circuitry) intreq1 Interrupt request that combines with intreq2 to form AUDxIR. @@ -297,32 +212,204 @@ bool Audio::Channel::output() { dmasen Restart request enable. percntrld Reload period counter from back-up latch typically written - by processor with AUDxPER (can also be written by attach mode). + by processor with AUDxPER (can also be written by attach mode). - percount Count period counter down one latch. + percount Count period counter down one latch. - perfin Period counter finished (value = 1). + perfin Period counter finished (value = 1). - lencntrld Reload length counter from back-up latch. + lencntrld Reload length counter from back-up latch. - lencount Count length counter down one notch. + lencount Count length counter down one notch. - lenfin Length counter finished (value = 1). + lenfin Length counter finished (value = 1). - volcntrld Reload volume counter from back-up latch. + volcntrld Reload volume counter from back-up latch. - pbufld1 Load output buffer from holding latch written to by AUDxDAT. + pbufld1 Load output buffer from holding latch written to by AUDxDAT. - pbufld2 Like pbufld1, but only during 010->011 with attach period. + pbufld2 Like pbufld1, but only during 010->011 with attach period. - AUDxAV Attach volume. Send data to volume latch of next channel + AUDxAV Attach volume. Send data to volume latch of next channel instead of to D->A converter. - AUDxAP Attach period. Send data to period latch of next channel + AUDxAP Attach period. Send data to period latch of next channel instead of to the D->A converter. - penhi Enable the high 8 bits of data to go to the D->A converter. + penhi Enable the high 8 bits of data to go to the D->A converter. - napnav /AUDxAV * /AUDxAP + AUDxAV -- no attach stuff or else attach + napnav /AUDxAV * /AUDxAP + AUDxAV -- no attach stuff or else attach volume. Condition for normal DMA and interrupt requests. */ + + +// +// Non-action fallback transition. +// + +template < + Audio::Channel::State begin, + Audio::Channel::State end> bool Audio::Channel::transit() { + state = end; + return false; +} + +// +// Audio::Channel::State::Disabled +// + +template <> bool Audio::Channel::transit< + Audio::Channel::State::Disabled, + Audio::Channel::State::WaitingForDummyDMA>() { + state = State::WaitingForDummyDMA; + + period_counter = period; // i.e. percntrld + length_counter = length; // i.e. lenctrld + + return false; +} + +template <> bool Audio::Channel::transit< + Audio::Channel::State::Disabled, + Audio::Channel::State::PlayingHigh>() { + state = State::PlayingHigh; + + data_latch = data; // i.e. pbufld1 + has_data = false; + period_counter = period; // i.e. percntrld + // TODO: volcntrld (see above). + + // Request an interrupt. + return true; +} + +template <> bool Audio::Channel::output() { + if(has_data && !dma_enabled && !interrupt_pending) { + return transit(); + } + + // Test for DMA-style transition. + if(dma_enabled) { + return transit(); + } + + return false; +} + +// +// Audio::Channel::State::WaitingForDummyDMA +// + +template <> bool Audio::Channel::transit< + Audio::Channel::State::WaitingForDummyDMA, + Audio::Channel::State::WaitingForDMA>() { + state = State::WaitingForDMA; + + has_data = false; + if(length == 1) { + length_counter = length; + return true; + } + + return false; +} + +template <> bool Audio::Channel::output() { + if(!dma_enabled) { + return transit(); + } + + if(dma_enabled && has_data) { + return transit(); + } + + return false; +} + +// +// Audio::Channel::State::WaitingForDMA +// + +template <> bool Audio::Channel::transit< + Audio::Channel::State::WaitingForDMA, + Audio::Channel::State::PlayingHigh>() { + state = State::PlayingHigh; + + data_latch = data; + has_data = false; + period_counter = period; // i.e. percntrld + + return false; +} + +template <> bool Audio::Channel::output() { + if(!dma_enabled) { + return transit(); + } + + if(dma_enabled && has_data) { + return transit(); + } + + return false; +} + +// +// Audio::Channel::State::PlayingHigh +// + +template <> bool Audio::Channel::output() { + -- period_counter; + + // This is a reasonable guess as to the exit condition for this node; + // Commodore doesn't document. + if(!period_counter) { + return transit(); + } + + // TODO: penhi (i.e. output high byte). + + return false; +} + +// +// Audio::Channel::State::PlayingLow +// + +template <> bool Audio::Channel::output() { + -- period_counter; + + const bool dma_and_not_done = dma_enabled && !interrupt_pending; + + if(!period_counter && !dma_and_not_done) { + return transit(); + } + + if(!period_counter && dma_and_not_done) { + return transit(); + } + + // TODO: not penhi (i.e. output low byte). + + return false; +} + +// +// Dispatcher +// + +bool Audio::Channel::output() { + switch(state) { + case State::Disabled: return output(); + case State::WaitingForDummyDMA: return output(); + case State::WaitingForDMA: return output(); + case State::PlayingHigh: return output(); + case State::PlayingLow: return output(); + + default: + assert(false); + break; + } + + return false; +} diff --git a/Machines/Amiga/Audio.hpp b/Machines/Amiga/Audio.hpp index 2dd3f6874..99967abf5 100644 --- a/Machines/Amiga/Audio.hpp +++ b/Machines/Amiga/Audio.hpp @@ -94,6 +94,8 @@ class Audio: public DMADevice<4> { } state = State::Disabled; bool output(); + template bool output(); + template bool transit(); } channels_[4]; };