2021-11-09 12:11:23 +00:00
|
|
|
|
//
|
|
|
|
|
// Audio.cpp
|
|
|
|
|
// Clock Signal
|
|
|
|
|
//
|
|
|
|
|
// Created by Thomas Harte on 09/11/2021.
|
|
|
|
|
// Copyright © 2021 Thomas Harte. All rights reserved.
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
#include "Audio.hpp"
|
2021-11-12 20:30:52 +00:00
|
|
|
|
|
2021-11-13 16:05:39 +00:00
|
|
|
|
#include "Flags.hpp"
|
|
|
|
|
|
2021-11-12 20:30:52 +00:00
|
|
|
|
#define LOG_PREFIX "[Audio] "
|
|
|
|
|
#include "../../Outputs/Log.hpp"
|
|
|
|
|
|
2021-11-11 14:24:15 +00:00
|
|
|
|
#include <cassert>
|
2021-11-09 12:11:23 +00:00
|
|
|
|
|
|
|
|
|
using namespace Amiga;
|
|
|
|
|
|
2021-12-01 10:37:58 +00:00
|
|
|
|
Audio::Audio(Chipset &chipset, uint16_t *ram, size_t word_size, float output_rate) :
|
|
|
|
|
DMADevice<4>(chipset, ram, word_size) {
|
|
|
|
|
|
|
|
|
|
// Mark all buffers as available.
|
|
|
|
|
for(auto &flag: buffer_available_) {
|
|
|
|
|
flag.store(true, std::memory_order::memory_order_relaxed);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
speaker_.set_input_rate(output_rate);
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-13 20:53:41 +00:00
|
|
|
|
bool Audio::advance_dma(int channel) {
|
2021-12-02 14:33:02 +00:00
|
|
|
|
if(channels_[channel].has_data) {
|
|
|
|
|
return false;
|
2021-11-11 14:24:15 +00:00
|
|
|
|
}
|
2021-11-15 17:31:15 +00:00
|
|
|
|
|
2021-12-02 14:33:02 +00:00
|
|
|
|
set_data(channel, ram_[pointer_[size_t(channel)]]);
|
|
|
|
|
if(channels_[channel].state != Channel::State::WaitingForDummyDMA) {
|
|
|
|
|
++pointer_[size_t(channel)];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
2021-11-09 12:11:23 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-11-11 14:24:15 +00:00
|
|
|
|
void Audio::set_length(int channel, uint16_t length) {
|
|
|
|
|
assert(channel >= 0 && channel < 4);
|
|
|
|
|
channels_[channel].length = length;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Audio::set_period(int channel, uint16_t period) {
|
|
|
|
|
assert(channel >= 0 && channel < 4);
|
|
|
|
|
channels_[channel].period = period;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Audio::set_volume(int channel, uint16_t volume) {
|
|
|
|
|
assert(channel >= 0 && channel < 4);
|
|
|
|
|
channels_[channel].volume = (volume & 0x40) ? 64 : (volume & 0x3f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Audio::set_data(int channel, uint16_t data) {
|
|
|
|
|
assert(channel >= 0 && channel < 4);
|
2021-11-12 20:30:52 +00:00
|
|
|
|
channels_[channel].has_data = true;
|
2021-11-11 14:24:15 +00:00
|
|
|
|
channels_[channel].data = data;
|
2021-11-09 12:11:23 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-11-11 14:24:15 +00:00
|
|
|
|
void Audio::set_channel_enables(uint16_t enables) {
|
|
|
|
|
channels_[0].dma_enabled = enables & 1;
|
|
|
|
|
channels_[1].dma_enabled = enables & 2;
|
|
|
|
|
channels_[2].dma_enabled = enables & 4;
|
|
|
|
|
channels_[3].dma_enabled = enables & 8;
|
2021-11-09 12:11:23 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-11-11 14:24:15 +00:00
|
|
|
|
void Audio::set_modulation_flags(uint16_t) {
|
2021-11-09 12:11:23 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-11-13 16:05:39 +00:00
|
|
|
|
void Audio::set_interrupt_requests(uint16_t requests) {
|
|
|
|
|
channels_[0].interrupt_pending = requests & uint16_t(InterruptFlag::AudioChannel0);
|
|
|
|
|
channels_[1].interrupt_pending = requests & uint16_t(InterruptFlag::AudioChannel1);
|
|
|
|
|
channels_[2].interrupt_pending = requests & uint16_t(InterruptFlag::AudioChannel2);
|
|
|
|
|
channels_[3].interrupt_pending = requests & uint16_t(InterruptFlag::AudioChannel3);
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-13 20:53:41 +00:00
|
|
|
|
void Audio::output() {
|
2021-11-14 15:48:50 +00:00
|
|
|
|
constexpr InterruptFlag interrupts[] = {
|
|
|
|
|
InterruptFlag::AudioChannel0,
|
|
|
|
|
InterruptFlag::AudioChannel1,
|
|
|
|
|
InterruptFlag::AudioChannel2,
|
|
|
|
|
InterruptFlag::AudioChannel3,
|
|
|
|
|
};
|
2021-11-13 16:05:39 +00:00
|
|
|
|
|
2021-11-14 15:48:50 +00:00
|
|
|
|
for(int c = 0; c < 4; c++) {
|
|
|
|
|
if(channels_[c].output()) {
|
|
|
|
|
posit_interrupt(interrupts[c]);
|
2021-11-13 16:05:39 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2021-12-01 10:37:58 +00:00
|
|
|
|
|
|
|
|
|
// TEMPORARY: just fill the audio buffer with silence.
|
|
|
|
|
if(!sample_pointer_) {
|
|
|
|
|
while(!buffer_available_[buffer_pointer_].load(std::memory_order::memory_order_relaxed));
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-01 11:01:58 +00:00
|
|
|
|
buffer_[buffer_pointer_][sample_pointer_] = int16_t(
|
2021-12-01 23:34:54 +00:00
|
|
|
|
(
|
|
|
|
|
int8_t(channels_[0].output_level) * channels_[0].output_enabled +
|
|
|
|
|
int8_t(channels_[2].output_level) * channels_[2].output_enabled
|
|
|
|
|
) << 7
|
2021-12-01 11:01:58 +00:00
|
|
|
|
);
|
|
|
|
|
buffer_[buffer_pointer_][sample_pointer_+1] = int16_t(
|
2021-12-01 23:34:54 +00:00
|
|
|
|
(
|
|
|
|
|
int8_t(channels_[1].output_level) * channels_[1].output_enabled +
|
|
|
|
|
int8_t(channels_[3].output_level) * channels_[3].output_enabled
|
|
|
|
|
) << 7
|
2021-12-01 11:01:58 +00:00
|
|
|
|
);
|
|
|
|
|
sample_pointer_ += 2;
|
2021-12-01 10:37:58 +00:00
|
|
|
|
|
|
|
|
|
if(sample_pointer_ == buffer_[buffer_pointer_].size()) {
|
|
|
|
|
const auto &buffer = buffer_[buffer_pointer_];
|
|
|
|
|
auto &flag = buffer_available_[buffer_pointer_];
|
|
|
|
|
|
|
|
|
|
flag.store(false, std::memory_order::memory_order_release);
|
|
|
|
|
queue_.enqueue([this, &buffer, &flag] {
|
2021-12-01 11:01:58 +00:00
|
|
|
|
speaker_.push(buffer.data(), buffer.size() >> 1);
|
2021-12-01 10:37:58 +00:00
|
|
|
|
flag.store(true, std::memory_order::memory_order_relaxed);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
buffer_pointer_ = (buffer_pointer_ + 1) % BufferCount;
|
|
|
|
|
sample_pointer_ = 0;
|
|
|
|
|
}
|
2021-11-09 12:11:23 +00:00
|
|
|
|
}
|
2021-11-14 15:48:50 +00:00
|
|
|
|
|
2021-11-14 19:54:33 +00:00
|
|
|
|
/*
|
2021-11-15 10:29:28 +00:00
|
|
|
|
Big spiel on the state machine:
|
2021-11-14 19:54:33 +00:00
|
|
|
|
|
|
|
|
|
Commodore's Hardware Rerefence Manual provides the audio subsystem's state
|
|
|
|
|
machine, so I've just tried to reimplement it verbatim. It's depicted
|
|
|
|
|
diagrammatically in the original source as a finite state automata, the
|
|
|
|
|
below is my attempt to translate that into text.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
000 State::Disabled:
|
|
|
|
|
|
2021-12-02 14:30:52 +00:00
|
|
|
|
-> State::Disabled (000)
|
2021-11-14 19:54:33 +00:00
|
|
|
|
if: N/A
|
2021-11-15 17:29:32 +00:00
|
|
|
|
action: percntrld
|
2021-11-14 19:54:33 +00:00
|
|
|
|
|
2021-12-02 14:30:52 +00:00
|
|
|
|
-> State::PlayingHigh (010)
|
2021-11-14 19:54:33 +00:00
|
|
|
|
if: AUDDAT, and not AUDxON, and not AUDxIP
|
2021-11-15 17:29:32 +00:00
|
|
|
|
action: volcntrld, percntrld, pbudld1, AUDxIR
|
2021-11-14 19:54:33 +00:00
|
|
|
|
|
2021-12-02 14:30:52 +00:00
|
|
|
|
-> State::WaitingForDummyDMA (001)
|
2021-11-14 19:54:33 +00:00
|
|
|
|
if: AUDxON
|
2021-12-02 14:30:52 +00:00
|
|
|
|
action: lencntrld, AUDxDR, dmasen*, percntrld
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
* NOTE: except for this case, dmasen is true only when
|
|
|
|
|
LENFIN = 1. Also, AUDxDSR = (AUDxDR and dmasen).
|
2021-11-14 19:54:33 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
001 State::WaitingForDummyDMA:
|
|
|
|
|
|
2021-12-02 14:30:52 +00:00
|
|
|
|
-> State::WaitingForDummyDMA (001)
|
2021-11-14 19:54:33 +00:00
|
|
|
|
if: N/A
|
|
|
|
|
action: None
|
|
|
|
|
|
2021-12-02 14:30:52 +00:00
|
|
|
|
-> State::Disabled (000)
|
2021-11-14 19:54:33 +00:00
|
|
|
|
if: not AUDxON
|
|
|
|
|
action: None
|
|
|
|
|
|
2021-12-02 14:30:52 +00:00
|
|
|
|
-> State::WaitingForDMA (101)
|
2021-11-14 19:54:33 +00:00
|
|
|
|
if: AUDxON, and AUDxDAT
|
|
|
|
|
action:
|
|
|
|
|
1. AUDxIR
|
|
|
|
|
2. if not lenfin, then lencount
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
101 State::WaitingForDMA:
|
|
|
|
|
|
2021-12-02 14:30:52 +00:00
|
|
|
|
-> State::WaitingForDMA (101)
|
2021-11-14 19:54:33 +00:00
|
|
|
|
if: N/A
|
|
|
|
|
action: None
|
|
|
|
|
|
2021-12-02 14:30:52 +00:00
|
|
|
|
-> State:Disabled (000)
|
2021-11-14 19:54:33 +00:00
|
|
|
|
if: not AUDxON
|
|
|
|
|
action: None
|
|
|
|
|
|
2021-12-02 14:30:52 +00:00
|
|
|
|
-> State::PlayingHigh (010)
|
2021-11-14 19:54:33 +00:00
|
|
|
|
if: AUDxON, and AUDxDAT
|
|
|
|
|
action:
|
2021-11-15 17:29:32 +00:00
|
|
|
|
1. volcntrld, percntrld, pbufid1
|
2021-11-15 10:29:28 +00:00
|
|
|
|
2. if napnav, then AUDxDR
|
2021-11-14 19:54:33 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
010 State::PlayingHigh
|
|
|
|
|
|
2021-12-02 14:30:52 +00:00
|
|
|
|
-> State::PlayingHigh (010)
|
2021-11-14 19:54:33 +00:00
|
|
|
|
if: N/A
|
|
|
|
|
action: percount, and penhi
|
|
|
|
|
|
2021-12-02 14:30:52 +00:00
|
|
|
|
-> State::PlayingLow (011)
|
|
|
|
|
if: perfin
|
2021-11-14 19:54:33 +00:00
|
|
|
|
action:
|
2021-11-15 17:29:32 +00:00
|
|
|
|
1. if AUDxAP, then pubfid2
|
|
|
|
|
2. if AUDxAP and AUDxON, then AUDxDR
|
|
|
|
|
3. percntrld
|
2021-11-14 19:54:33 +00:00
|
|
|
|
4. if intreq2 and AUDxON and AUDxAP, then AUDxIR
|
|
|
|
|
5. if AUDxAP and AUDxON, then AUDxIR
|
2021-11-15 17:29:32 +00:00
|
|
|
|
6. if lenfin and AUDxON and AUDxDAT, then lencntrld
|
2021-11-14 19:54:33 +00:00
|
|
|
|
7. if (not lenfin) and AUDxON and AUDxDAT, then lencount
|
|
|
|
|
8. if lenfin and AUDxON and AUDxDAT, then intreq2
|
|
|
|
|
|
|
|
|
|
[note that 6–8 are shared with the Low -> High transition]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
011 State::PlayingLow
|
|
|
|
|
|
2021-12-02 14:30:52 +00:00
|
|
|
|
-> State::PlayingLow (011)
|
2021-11-14 19:54:33 +00:00
|
|
|
|
if: N/A
|
|
|
|
|
action: percount, and not penhi
|
|
|
|
|
|
2021-12-02 14:30:52 +00:00
|
|
|
|
-> State::Disabled (000)
|
2021-11-14 19:54:33 +00:00
|
|
|
|
if: perfin and not (AUDxON and not AUDxIP)
|
|
|
|
|
action: None
|
|
|
|
|
|
2021-12-02 14:30:52 +00:00
|
|
|
|
-> State::PlayingHigh (010)
|
2021-11-14 19:54:33 +00:00
|
|
|
|
if: perfin and AUDxON and not AUDxIP
|
|
|
|
|
action:
|
|
|
|
|
1. pbufld
|
|
|
|
|
2. percntrld
|
|
|
|
|
3. if AUDxON and napnav, then AUDxDR
|
2021-11-15 17:29:32 +00:00
|
|
|
|
4. if intreq2 and napnav and AUDxON, AUDxIR
|
|
|
|
|
5. if napnav and not AUDxON, AUDxIR
|
|
|
|
|
6. if lenfin and AUDxON and AUDxDAT, then lencntrld
|
|
|
|
|
7. if (not lenfin) and AUDxON and AUDxDAT, then lencount
|
|
|
|
|
8. if lenfin and AUDxON and AUDxDAT, then intreq2
|
2021-11-14 19:54:33 +00:00
|
|
|
|
|
2021-11-15 17:29:32 +00:00
|
|
|
|
[note that 6-8 are shared with the High -> Low transition]
|
2021-11-14 19:54:33 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Definitions:
|
|
|
|
|
|
2021-11-15 10:29:28 +00:00
|
|
|
|
AUDxON DMA on "x" indicates channel number (signal from DMACON).
|
2021-11-14 19:54:33 +00:00
|
|
|
|
|
2021-11-15 10:29:28 +00:00
|
|
|
|
AUDxIP Audio interrupt pending (input to channel from interrupt circuitry).
|
2021-11-14 19:54:33 +00:00
|
|
|
|
|
2021-11-15 10:29:28 +00:00
|
|
|
|
AUDxIR Audio interrupt request (output from channel to interrupt circuitry)
|
2021-11-14 19:54:33 +00:00
|
|
|
|
|
|
|
|
|
intreq1 Interrupt request that combines with intreq2 to form AUDxIR.
|
|
|
|
|
|
|
|
|
|
intreq2 Prepare for interrupt request. Request comes out after the
|
|
|
|
|
next 011->010 transition in normal operation.
|
|
|
|
|
|
|
|
|
|
AUDxDAT Audio data load signal. Loads 16 bits of data to audio channel.
|
|
|
|
|
|
|
|
|
|
AUDxDR Audio DMA request to Agnus for one word of data.
|
|
|
|
|
|
|
|
|
|
AUDxDSR Audio DMA request to Agnus to reset pointer to start of block.
|
|
|
|
|
|
|
|
|
|
dmasen Restart request enable.
|
|
|
|
|
|
|
|
|
|
percntrld Reload period counter from back-up latch typically written
|
2021-11-15 10:29:28 +00:00
|
|
|
|
by processor with AUDxPER (can also be written by attach mode).
|
2021-11-14 19:54:33 +00:00
|
|
|
|
|
2021-11-15 10:29:28 +00:00
|
|
|
|
percount Count period counter down one latch.
|
2021-11-14 19:54:33 +00:00
|
|
|
|
|
2021-11-15 10:29:28 +00:00
|
|
|
|
perfin Period counter finished (value = 1).
|
2021-11-14 19:54:33 +00:00
|
|
|
|
|
2021-11-15 10:29:28 +00:00
|
|
|
|
lencntrld Reload length counter from back-up latch.
|
2021-11-14 19:54:33 +00:00
|
|
|
|
|
2021-11-15 10:29:28 +00:00
|
|
|
|
lencount Count length counter down one notch.
|
2021-11-14 19:54:33 +00:00
|
|
|
|
|
2021-11-15 10:29:28 +00:00
|
|
|
|
lenfin Length counter finished (value = 1).
|
2021-11-14 19:54:33 +00:00
|
|
|
|
|
2021-11-15 10:29:28 +00:00
|
|
|
|
volcntrld Reload volume counter from back-up latch.
|
2021-11-14 19:54:33 +00:00
|
|
|
|
|
2021-11-15 10:29:28 +00:00
|
|
|
|
pbufld1 Load output buffer from holding latch written to by AUDxDAT.
|
2021-11-14 19:54:33 +00:00
|
|
|
|
|
2021-11-15 10:29:28 +00:00
|
|
|
|
pbufld2 Like pbufld1, but only during 010->011 with attach period.
|
2021-11-14 19:54:33 +00:00
|
|
|
|
|
2021-11-15 10:29:28 +00:00
|
|
|
|
AUDxAV Attach volume. Send data to volume latch of next channel
|
2021-11-14 19:54:33 +00:00
|
|
|
|
instead of to D->A converter.
|
|
|
|
|
|
2021-11-15 10:29:28 +00:00
|
|
|
|
AUDxAP Attach period. Send data to period latch of next channel
|
2021-11-14 19:54:33 +00:00
|
|
|
|
instead of to the D->A converter.
|
|
|
|
|
|
2021-11-15 10:29:28 +00:00
|
|
|
|
penhi Enable the high 8 bits of data to go to the D->A converter.
|
2021-11-14 19:54:33 +00:00
|
|
|
|
|
2021-11-15 10:29:28 +00:00
|
|
|
|
napnav /AUDxAV * /AUDxAP + AUDxAV -- no attach stuff or else attach
|
2021-11-14 19:54:33 +00:00
|
|
|
|
volume. Condition for normal DMA and interrupt requests.
|
|
|
|
|
*/
|
2021-11-15 10:29:28 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// 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
|
2021-11-15 17:29:32 +00:00
|
|
|
|
length_counter = length; // i.e. lencntrld
|
2021-11-15 10:29:28 +00:00
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
//
|
|
|
|
|
|
2021-11-15 17:29:32 +00:00
|
|
|
|
template <> bool Audio::Channel::transit<
|
|
|
|
|
Audio::Channel::State::PlayingHigh,
|
|
|
|
|
Audio::Channel::State::PlayingLow>() {
|
|
|
|
|
state = State::PlayingLow;
|
|
|
|
|
|
|
|
|
|
// TODO: if AUDxAP, then pubfid2
|
|
|
|
|
// TODO: if AUDxAP and AUDxON, then AUDxDR
|
|
|
|
|
|
|
|
|
|
period_counter = period; // i.e. percntrld
|
|
|
|
|
|
|
|
|
|
// TODO: if intreq2 and AUDxON and AUDxAP, then AUDxIR
|
|
|
|
|
// TODO: if AUDxAP and AUDxON, then AUDxIR
|
|
|
|
|
|
|
|
|
|
// if lenfin and AUDxON and AUDxDAT, then lencntrld
|
|
|
|
|
// if (not lenfin) and AUDxON and AUDxDAT, then lencount
|
|
|
|
|
// if lenfin and AUDxON and AUDxDAT, then intreq2
|
|
|
|
|
if(dma_enabled && has_data) {
|
|
|
|
|
--length_counter;
|
|
|
|
|
if(!length_counter) {
|
|
|
|
|
length_counter = length;
|
|
|
|
|
will_request_interrupt = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-15 10:29:28 +00:00
|
|
|
|
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>();
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-01 11:01:58 +00:00
|
|
|
|
// Output high byte.
|
|
|
|
|
output_level = data_latch >> 8;
|
2021-11-15 10:29:28 +00:00
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// Audio::Channel::State::PlayingLow
|
|
|
|
|
//
|
|
|
|
|
|
2021-11-15 17:29:32 +00:00
|
|
|
|
template <> bool Audio::Channel::transit<
|
|
|
|
|
Audio::Channel::State::PlayingLow,
|
|
|
|
|
Audio::Channel::State::PlayingHigh>() {
|
|
|
|
|
state = State::PlayingHigh;
|
|
|
|
|
|
|
|
|
|
// TODO: include napnav in tests
|
|
|
|
|
|
|
|
|
|
if(!dma_enabled) {
|
|
|
|
|
return true;
|
2021-12-01 23:34:54 +00:00
|
|
|
|
} else {
|
|
|
|
|
data_latch = data; // i.e. pbufld2
|
|
|
|
|
has_data = false; // AUDxDR
|
2021-11-15 17:29:32 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(dma_enabled && will_request_interrupt) {
|
2021-11-15 21:00:35 +00:00
|
|
|
|
will_request_interrupt = false;
|
2021-11-15 17:29:32 +00:00
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-15 10:29:28 +00:00
|
|
|
|
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>();
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-01 11:01:58 +00:00
|
|
|
|
// Output low byte.
|
|
|
|
|
output_level = data_latch & 0xff;
|
2021-11-15 10:29:28 +00:00
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// Dispatcher
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
bool Audio::Channel::output() {
|
2021-12-01 11:01:58 +00:00
|
|
|
|
// Update pulse-width modulation.
|
|
|
|
|
output_phase = (output_phase + 1) & 63;
|
|
|
|
|
output_enabled |= !output_phase;
|
|
|
|
|
output_enabled &= output_phase != volume;
|
|
|
|
|
|
2021-11-15 10:29:28 +00:00
|
|
|
|
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;
|
|
|
|
|
}
|