mirror of
https://github.com/TomHarte/CLK.git
synced 2025-01-11 08:30:55 +00:00
Separates state transitions and tests.
This commit is contained in:
parent
a9971917f5
commit
0c5bb9626b
@ -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<Audio::Channel::State::Disabled>() {
|
||||
if(has_data && !dma_enabled && !interrupt_pending) {
|
||||
return transit<State::Disabled, State::PlayingHigh>();
|
||||
}
|
||||
|
||||
// Test for DMA-style transition.
|
||||
if(dma_enabled) {
|
||||
return transit<State::Disabled, State::WaitingForDummyDMA>();
|
||||
}
|
||||
|
||||
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<Audio::Channel::State::WaitingForDummyDMA>() {
|
||||
if(!dma_enabled) {
|
||||
return transit<State::WaitingForDummyDMA, State::Disabled>();
|
||||
}
|
||||
|
||||
if(dma_enabled && has_data) {
|
||||
return transit<State::WaitingForDummyDMA, State::WaitingForDMA>();
|
||||
}
|
||||
|
||||
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<Audio::Channel::State::WaitingForDMA>() {
|
||||
if(!dma_enabled) {
|
||||
return transit<State::WaitingForDummyDMA, State::Disabled>();
|
||||
}
|
||||
|
||||
if(dma_enabled && has_data) {
|
||||
return transit<State::WaitingForDummyDMA, State::PlayingHigh>();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//
|
||||
// Audio::Channel::State::PlayingHigh
|
||||
//
|
||||
|
||||
template <> bool Audio::Channel::output<Audio::Channel::State::PlayingHigh>() {
|
||||
-- period_counter;
|
||||
|
||||
// This is a reasonable guess as to the exit condition for this node;
|
||||
// Commodore doesn't document.
|
||||
if(!period_counter) {
|
||||
return transit<State::PlayingHigh, State::PlayingLow>();
|
||||
}
|
||||
|
||||
// TODO: penhi (i.e. output high byte).
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//
|
||||
// Audio::Channel::State::PlayingLow
|
||||
//
|
||||
|
||||
template <> bool Audio::Channel::output<Audio::Channel::State::PlayingLow>() {
|
||||
-- period_counter;
|
||||
|
||||
const bool dma_and_not_done = dma_enabled && !interrupt_pending;
|
||||
|
||||
if(!period_counter && !dma_and_not_done) {
|
||||
return transit<State::PlayingLow, State::Disabled>();
|
||||
}
|
||||
|
||||
if(!period_counter && dma_and_not_done) {
|
||||
return transit<State::PlayingLow, State::PlayingHigh>();
|
||||
}
|
||||
|
||||
// TODO: not penhi (i.e. output low byte).
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//
|
||||
// Dispatcher
|
||||
//
|
||||
|
||||
bool Audio::Channel::output() {
|
||||
switch(state) {
|
||||
case State::Disabled: return output<State::Disabled>();
|
||||
case State::WaitingForDummyDMA: return output<State::WaitingForDummyDMA>();
|
||||
case State::WaitingForDMA: return output<State::WaitingForDMA>();
|
||||
case State::PlayingHigh: return output<State::PlayingHigh>();
|
||||
case State::PlayingLow: return output<State::PlayingLow>();
|
||||
|
||||
default:
|
||||
assert(false);
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
@ -94,6 +94,8 @@ class Audio: public DMADevice<4> {
|
||||
} state = State::Disabled;
|
||||
|
||||
bool output();
|
||||
template <State state> bool output();
|
||||
template <State begin, State end> bool transit();
|
||||
} channels_[4];
|
||||
};
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user