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-11 14:24:15 +00:00
|
|
|
|
#include <cassert>
|
2021-12-02 17:53:20 +00:00
|
|
|
|
#include <tuple>
|
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-12-04 22:58:41 +00:00
|
|
|
|
speaker_.set_high_frequency_cutoff(7000.0f);
|
2021-12-01 10:37:58 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-12-04 12:20:17 +00:00
|
|
|
|
// MARK: - Exposed setters.
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-06 11:35:08 +00:00
|
|
|
|
template <bool is_external> void Audio::set_data(int channel, uint16_t data) {
|
2021-11-11 14:24:15 +00:00
|
|
|
|
assert(channel >= 0 && channel < 4);
|
2021-12-02 14:41:16 +00:00
|
|
|
|
channels_[channel].wants_data = false;
|
2021-11-11 14:24:15 +00:00
|
|
|
|
channels_[channel].data = data;
|
2021-12-06 11:35:08 +00:00
|
|
|
|
|
|
|
|
|
// TODO: "the [PWM] counter is reset when ... AUDxDAT is written", but
|
|
|
|
|
// does that just mean written by the CPU, or does it include DMA?
|
|
|
|
|
// My guess is the former. But TODO.
|
|
|
|
|
if constexpr (is_external) {
|
|
|
|
|
channels_[channel].reset_output_phase();
|
|
|
|
|
}
|
2021-11-09 12:11:23 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-12-06 11:35:08 +00:00
|
|
|
|
template void Audio::set_data<false>(int, uint16_t);
|
|
|
|
|
template void Audio::set_data<true>(int, uint16_t);
|
|
|
|
|
|
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-12-04 23:02:43 +00:00
|
|
|
|
void Audio::set_modulation_flags(uint16_t flags) {
|
|
|
|
|
channels_[3].attach_period = flags & 0x80;
|
|
|
|
|
channels_[2].attach_period = flags & 0x40;
|
|
|
|
|
channels_[1].attach_period = flags & 0x20;
|
|
|
|
|
channels_[0].attach_period = flags & 0x10;
|
|
|
|
|
|
|
|
|
|
channels_[3].attach_volume = flags & 0x08;
|
|
|
|
|
channels_[2].attach_volume = flags & 0x04;
|
|
|
|
|
channels_[1].attach_volume = flags & 0x02;
|
|
|
|
|
channels_[0].attach_volume = flags & 0x01;
|
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-12-04 12:20:17 +00:00
|
|
|
|
// MARK: - DMA and mixing.
|
|
|
|
|
|
|
|
|
|
bool Audio::advance_dma(int channel) {
|
|
|
|
|
if(!channels_[channel].wants_data) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-04 22:58:41 +00:00
|
|
|
|
if(channels_[channel].should_reload_address) {
|
|
|
|
|
channels_[channel].data_address = pointer_[size_t(channel)];
|
|
|
|
|
channels_[channel].should_reload_address = false;
|
2021-12-04 12:20:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-12-07 00:28:40 +00:00
|
|
|
|
set_data<false>(channel, ram_[channels_[channel].data_address & ram_mask_]);
|
|
|
|
|
|
|
|
|
|
if(channels_[channel].state != Channel::State::WaitingForDummyDMA) {
|
|
|
|
|
++channels_[channel].data_address;
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-04 12:20:17 +00:00
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-13 20:53:41 +00:00
|
|
|
|
void Audio::output() {
|
2022-07-26 13:22:05 +00:00
|
|
|
|
constexpr InterruptFlag::FlagT interrupts[] = {
|
2021-11-14 15:48:50 +00:00
|
|
|
|
InterruptFlag::AudioChannel0,
|
|
|
|
|
InterruptFlag::AudioChannel1,
|
|
|
|
|
InterruptFlag::AudioChannel2,
|
|
|
|
|
InterruptFlag::AudioChannel3,
|
|
|
|
|
};
|
2021-12-05 11:38:55 +00:00
|
|
|
|
Channel *const modulands[] = {
|
|
|
|
|
&channels_[1],
|
|
|
|
|
&channels_[2],
|
|
|
|
|
&channels_[3],
|
|
|
|
|
nullptr,
|
|
|
|
|
};
|
2021-11-13 16:05:39 +00:00
|
|
|
|
|
2021-11-14 15:48:50 +00:00
|
|
|
|
for(int c = 0; c < 4; c++) {
|
2021-12-05 11:38:55 +00:00
|
|
|
|
if(channels_[c].output(modulands[c])) {
|
2021-11-14 15:48:50 +00:00
|
|
|
|
posit_interrupt(interrupts[c]);
|
2021-11-13 16:05:39 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2021-12-01 10:37:58 +00:00
|
|
|
|
|
2021-12-02 16:15:29 +00:00
|
|
|
|
// Spin until the next buffer is available if just entering it for the first time.
|
|
|
|
|
// Contention here should be essentially non-existent.
|
2021-12-01 10:37:58 +00:00
|
|
|
|
if(!sample_pointer_) {
|
|
|
|
|
while(!buffer_available_[buffer_pointer_].load(std::memory_order::memory_order_relaxed));
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-02 16:15:29 +00:00
|
|
|
|
// Left.
|
2021-12-02 17:53:20 +00:00
|
|
|
|
static_assert(std::tuple_size<AudioBuffer>::value % 2 == 0);
|
2021-12-01 11:01:58 +00:00
|
|
|
|
buffer_[buffer_pointer_][sample_pointer_] = int16_t(
|
2021-12-01 23:34:54 +00:00
|
|
|
|
(
|
2021-12-02 16:15:29 +00:00
|
|
|
|
channels_[1].output_level * channels_[1].output_enabled +
|
|
|
|
|
channels_[2].output_level * channels_[2].output_enabled
|
2021-12-01 23:34:54 +00:00
|
|
|
|
) << 7
|
2021-12-01 11:01:58 +00:00
|
|
|
|
);
|
2021-12-02 16:15:29 +00:00
|
|
|
|
|
|
|
|
|
// Right.
|
2021-12-02 17:53:20 +00:00
|
|
|
|
buffer_[buffer_pointer_][sample_pointer_ + 1] = int16_t(
|
2021-12-01 23:34:54 +00:00
|
|
|
|
(
|
2021-12-02 16:15:29 +00:00
|
|
|
|
channels_[0].output_level * channels_[0].output_enabled +
|
|
|
|
|
channels_[3].output_level * channels_[3].output_enabled
|
2021-12-01 23:34:54 +00:00
|
|
|
|
) << 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-12-04 12:20:17 +00:00
|
|
|
|
// MARK: - Per-channel logic.
|
|
|
|
|
|
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-12-05 20:27:35 +00:00
|
|
|
|
action: percntrld, AUDxIR, volcntrld, pbudld1
|
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-05 20:27:35 +00:00
|
|
|
|
action: percntrld, AUDxDR, lencntrld, dmasen*
|
2021-12-02 14:30:52 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
* 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-12-05 20:27:35 +00:00
|
|
|
|
1. volcntrld, percntrld, pbufld1
|
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-12-05 20:27:35 +00:00
|
|
|
|
1. if AUDxAP, then pbufld2
|
2021-11-15 17:29:32 +00:00
|
|
|
|
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-12-04 13:24:41 +00:00
|
|
|
|
if: perfin and not (AUDxON or not AUDxIP)
|
2021-11-14 19:54:33 +00:00
|
|
|
|
action: None
|
|
|
|
|
|
2021-12-02 14:30:52 +00:00
|
|
|
|
-> State::PlayingHigh (010)
|
2021-12-04 13:24:41 +00:00
|
|
|
|
if: perfin and (AUDxON or not AUDxIP)
|
2021-11-14 19:54:33 +00:00
|
|
|
|
action:
|
2021-12-07 00:28:40 +00:00
|
|
|
|
1. pbufld1
|
2021-11-14 19:54:33 +00:00
|
|
|
|
2. percntrld
|
2021-12-05 20:27:35 +00:00
|
|
|
|
3. if napnav and AUDxON, then AUDxDR
|
|
|
|
|
4. if napnav and AUDxON and intreq2, AUDxIR
|
2021-11-15 17:29:32 +00:00
|
|
|
|
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-12-04 13:24:41 +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
|
|
|
|
|
|
|
|
|
//
|
2021-12-04 12:20:17 +00:00
|
|
|
|
// Non-action fallback transition and setter, plus specialised begin_state declarations.
|
2021-11-15 10:29:28 +00:00
|
|
|
|
//
|
|
|
|
|
|
2021-12-05 11:38:55 +00:00
|
|
|
|
template <Audio::Channel::State end> void Audio::Channel::begin_state(Channel *) {
|
2021-12-04 12:20:17 +00:00
|
|
|
|
state = end;
|
|
|
|
|
}
|
2021-12-05 11:38:55 +00:00
|
|
|
|
template <> void Audio::Channel::begin_state<Audio::Channel::State::PlayingHigh>(Channel *);
|
|
|
|
|
template <> void Audio::Channel::begin_state<Audio::Channel::State::PlayingLow>(Channel *);
|
2021-12-04 12:20:17 +00:00
|
|
|
|
|
2021-11-15 10:29:28 +00:00
|
|
|
|
template <
|
|
|
|
|
Audio::Channel::State begin,
|
2021-12-05 11:38:55 +00:00
|
|
|
|
Audio::Channel::State end> bool Audio::Channel::transit(Channel *moduland) {
|
|
|
|
|
begin_state<end>(moduland);
|
2021-11-15 10:29:28 +00:00
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// Audio::Channel::State::Disabled
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
template <> bool Audio::Channel::transit<
|
|
|
|
|
Audio::Channel::State::Disabled,
|
2021-12-05 20:27:35 +00:00
|
|
|
|
Audio::Channel::State::PlayingHigh>(Channel *moduland) {
|
|
|
|
|
begin_state<State::PlayingHigh>(moduland);
|
2021-11-15 10:29:28 +00:00
|
|
|
|
|
2021-12-05 20:27:35 +00:00
|
|
|
|
// percntrld
|
|
|
|
|
period_counter = period;
|
|
|
|
|
|
|
|
|
|
// [AUDxIR]: see return result.
|
|
|
|
|
|
2021-12-06 11:35:08 +00:00
|
|
|
|
// volcntrld
|
|
|
|
|
volume_latch = volume;
|
|
|
|
|
reset_output_phase();
|
2021-12-05 20:27:35 +00:00
|
|
|
|
|
|
|
|
|
// pbufld1
|
|
|
|
|
data_latch = data;
|
2021-12-05 00:17:40 +00:00
|
|
|
|
wants_data = true;
|
2021-12-07 00:28:40 +00:00
|
|
|
|
if(moduland && attach_volume) moduland->volume = uint8_t(data_latch);
|
2021-11-15 10:29:28 +00:00
|
|
|
|
|
2021-12-05 20:27:35 +00:00
|
|
|
|
// AUDxIR.
|
|
|
|
|
return true;
|
2021-11-15 10:29:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template <> bool Audio::Channel::transit<
|
|
|
|
|
Audio::Channel::State::Disabled,
|
2021-12-05 20:27:35 +00:00
|
|
|
|
Audio::Channel::State::WaitingForDummyDMA>(Channel *moduland) {
|
|
|
|
|
begin_state<State::WaitingForDummyDMA>(moduland);
|
2021-11-15 10:29:28 +00:00
|
|
|
|
|
2021-12-05 20:27:35 +00:00
|
|
|
|
// percntrld
|
|
|
|
|
period_counter = period;
|
|
|
|
|
|
|
|
|
|
// AUDxDR
|
2021-12-05 00:17:40 +00:00
|
|
|
|
wants_data = true;
|
2021-11-15 10:29:28 +00:00
|
|
|
|
|
2021-12-05 20:27:35 +00:00
|
|
|
|
// lencntrld
|
|
|
|
|
length_counter = length;
|
|
|
|
|
|
|
|
|
|
// dmasen / AUDxDSR
|
|
|
|
|
should_reload_address = true;
|
|
|
|
|
|
|
|
|
|
return false;
|
2021-11-15 10:29:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-12-05 11:38:55 +00:00
|
|
|
|
template <> bool Audio::Channel::output<Audio::Channel::State::Disabled>(Channel *moduland) {
|
2021-12-05 20:27:35 +00:00
|
|
|
|
// if AUDDAT, and not AUDxON, and not AUDxIP.
|
2021-12-02 14:41:16 +00:00
|
|
|
|
if(!wants_data && !dma_enabled && !interrupt_pending) {
|
2021-12-05 11:38:55 +00:00
|
|
|
|
return transit<State::Disabled, State::PlayingHigh>(moduland);
|
2021-11-15 10:29:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-12-05 20:27:35 +00:00
|
|
|
|
// if AUDxON.
|
2021-11-15 10:29:28 +00:00
|
|
|
|
if(dma_enabled) {
|
2021-12-05 11:38:55 +00:00
|
|
|
|
return transit<State::Disabled, State::WaitingForDummyDMA>(moduland);
|
2021-11-15 10:29:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// Audio::Channel::State::WaitingForDummyDMA
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
template <> bool Audio::Channel::transit<
|
|
|
|
|
Audio::Channel::State::WaitingForDummyDMA,
|
2021-12-05 11:38:55 +00:00
|
|
|
|
Audio::Channel::State::WaitingForDMA>(Channel *moduland) {
|
|
|
|
|
begin_state<State::WaitingForDMA>(moduland);
|
2021-11-15 10:29:28 +00:00
|
|
|
|
|
2021-12-05 20:27:35 +00:00
|
|
|
|
// AUDxDR
|
2021-12-02 14:41:16 +00:00
|
|
|
|
wants_data = true;
|
2021-12-05 20:27:35 +00:00
|
|
|
|
|
|
|
|
|
// if not lenfin, then lencount
|
|
|
|
|
if(length != 1) {
|
|
|
|
|
-- length_counter;
|
2021-11-15 10:29:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-12-05 20:27:35 +00:00
|
|
|
|
// AUDxIR
|
|
|
|
|
return true;
|
2021-11-15 10:29:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-12-05 11:38:55 +00:00
|
|
|
|
template <> bool Audio::Channel::output<Audio::Channel::State::WaitingForDummyDMA>(Channel *moduland) {
|
2021-12-05 20:27:35 +00:00
|
|
|
|
// if not AUDxON
|
2021-11-15 10:29:28 +00:00
|
|
|
|
if(!dma_enabled) {
|
2021-12-05 11:38:55 +00:00
|
|
|
|
return transit<State::WaitingForDummyDMA, State::Disabled>(moduland);
|
2021-11-15 10:29:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-12-05 20:27:35 +00:00
|
|
|
|
// if AUDxON and AUDxDAT
|
2021-12-02 14:41:16 +00:00
|
|
|
|
if(dma_enabled && !wants_data) {
|
2021-12-05 11:38:55 +00:00
|
|
|
|
return transit<State::WaitingForDummyDMA, State::WaitingForDMA>(moduland);
|
2021-11-15 10:29:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// Audio::Channel::State::WaitingForDMA
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
template <> bool Audio::Channel::transit<
|
|
|
|
|
Audio::Channel::State::WaitingForDMA,
|
2021-12-05 11:38:55 +00:00
|
|
|
|
Audio::Channel::State::PlayingHigh>(Channel *moduland) {
|
|
|
|
|
begin_state<State::PlayingHigh>(moduland);
|
2021-11-15 10:29:28 +00:00
|
|
|
|
|
2021-12-06 11:35:08 +00:00
|
|
|
|
// volcntrld
|
|
|
|
|
volume_latch = volume;
|
|
|
|
|
reset_output_phase();
|
2021-12-05 20:27:35 +00:00
|
|
|
|
|
|
|
|
|
// percntrld
|
|
|
|
|
period_counter = period;
|
|
|
|
|
|
|
|
|
|
// pbufld1
|
2021-11-15 10:29:28 +00:00
|
|
|
|
data_latch = data;
|
2021-12-07 00:28:40 +00:00
|
|
|
|
if(moduland && attach_volume) moduland->volume = uint8_t(data_latch);
|
2021-12-05 20:27:35 +00:00
|
|
|
|
|
|
|
|
|
// if napnav
|
|
|
|
|
if(attach_volume || !(attach_volume || attach_period)) {
|
|
|
|
|
// AUDxDR
|
|
|
|
|
wants_data = true;
|
|
|
|
|
}
|
2021-11-15 10:29:28 +00:00
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-05 11:38:55 +00:00
|
|
|
|
template <> bool Audio::Channel::output<Audio::Channel::State::WaitingForDMA>(Channel *moduland) {
|
2021-12-05 20:27:35 +00:00
|
|
|
|
// if: not AUDxON
|
2021-11-15 10:29:28 +00:00
|
|
|
|
if(!dma_enabled) {
|
2021-12-05 11:38:55 +00:00
|
|
|
|
return transit<State::WaitingForDummyDMA, State::Disabled>(moduland);
|
2021-11-15 10:29:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-12-05 20:27:35 +00:00
|
|
|
|
// if: AUDxON, and AUDxDAT
|
2021-12-02 14:41:16 +00:00
|
|
|
|
if(dma_enabled && !wants_data) {
|
2021-12-05 11:38:55 +00:00
|
|
|
|
return transit<State::WaitingForDummyDMA, State::PlayingHigh>(moduland);
|
2021-11-15 10:29:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// Audio::Channel::State::PlayingHigh
|
|
|
|
|
//
|
|
|
|
|
|
2021-12-05 20:27:35 +00:00
|
|
|
|
void Audio::Channel::decrement_length() {
|
|
|
|
|
// 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 && !wants_data) {
|
2021-12-05 22:47:12 +00:00
|
|
|
|
-- length_counter;
|
|
|
|
|
|
|
|
|
|
if(!length_counter) {
|
2021-12-05 20:27:35 +00:00
|
|
|
|
length_counter = length;
|
|
|
|
|
will_request_interrupt = true;
|
2021-12-05 22:47:12 +00:00
|
|
|
|
should_reload_address = true; // This feels logical to me; it's a bit
|
|
|
|
|
// of a stab in the dark though.
|
2021-12-05 20:27:35 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-15 17:29:32 +00:00
|
|
|
|
template <> bool Audio::Channel::transit<
|
|
|
|
|
Audio::Channel::State::PlayingHigh,
|
2021-12-05 11:38:55 +00:00
|
|
|
|
Audio::Channel::State::PlayingLow>(Channel *moduland) {
|
|
|
|
|
begin_state<State::PlayingLow>(moduland);
|
2021-11-15 17:29:32 +00:00
|
|
|
|
|
2021-12-05 20:27:35 +00:00
|
|
|
|
bool wants_interrupt = false;
|
2021-11-15 17:29:32 +00:00
|
|
|
|
|
2021-12-05 20:27:35 +00:00
|
|
|
|
// if AUDxAP
|
|
|
|
|
if(attach_period) {
|
|
|
|
|
// pbufld2
|
|
|
|
|
data_latch = data;
|
2021-12-07 00:28:40 +00:00
|
|
|
|
if(moduland) moduland->period = data_latch;
|
2021-11-15 17:29:32 +00:00
|
|
|
|
|
2021-12-05 20:27:35 +00:00
|
|
|
|
// [if AUDxAP] and AUDxON
|
|
|
|
|
if(dma_enabled) {
|
|
|
|
|
// AUDxDR
|
|
|
|
|
wants_data = true;
|
2021-11-15 17:29:32 +00:00
|
|
|
|
|
2021-12-05 20:27:35 +00:00
|
|
|
|
// [if AUDxAP and AUDxON] and intreq2
|
|
|
|
|
if(will_request_interrupt) {
|
|
|
|
|
will_request_interrupt = false;
|
|
|
|
|
|
|
|
|
|
// AUDxIR
|
|
|
|
|
wants_interrupt = true;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// i.e. if AUDxAP and AUDxON, then AUDxIR
|
|
|
|
|
wants_interrupt = true;
|
2021-11-15 17:29:32 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-05 20:27:35 +00:00
|
|
|
|
// percntrld
|
|
|
|
|
period_counter = period;
|
|
|
|
|
|
|
|
|
|
decrement_length();
|
|
|
|
|
|
|
|
|
|
return wants_interrupt;
|
2021-11-15 17:29:32 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-12-05 11:38:55 +00:00
|
|
|
|
template <> void Audio::Channel::begin_state<Audio::Channel::State::PlayingHigh>(Channel *) {
|
2021-12-04 12:20:17 +00:00
|
|
|
|
state = Audio::Channel::State::PlayingHigh;
|
|
|
|
|
|
2021-12-05 20:27:35 +00:00
|
|
|
|
// penhi.
|
2021-12-04 12:20:17 +00:00
|
|
|
|
output_level = int8_t(data_latch >> 8);
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-05 11:38:55 +00:00
|
|
|
|
template <> bool Audio::Channel::output<Audio::Channel::State::PlayingHigh>(Channel *moduland) {
|
2021-11-15 10:29:28 +00:00
|
|
|
|
// This is a reasonable guess as to the exit condition for this node;
|
|
|
|
|
// Commodore doesn't document.
|
2021-12-05 20:27:35 +00:00
|
|
|
|
if(period_counter == 1) {
|
2021-12-05 11:38:55 +00:00
|
|
|
|
return transit<State::PlayingHigh, State::PlayingLow>(moduland);
|
2021-11-15 10:29:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-12-05 20:27:35 +00:00
|
|
|
|
// percount.
|
|
|
|
|
-- period_counter;
|
|
|
|
|
|
2021-11-15 10:29:28 +00:00
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// Audio::Channel::State::PlayingLow
|
|
|
|
|
//
|
|
|
|
|
|
2021-12-05 20:27:35 +00:00
|
|
|
|
template <> bool Audio::Channel::transit<
|
|
|
|
|
Audio::Channel::State::PlayingLow,
|
|
|
|
|
Audio::Channel::State::Disabled>(Channel *moduland) {
|
|
|
|
|
begin_state<State::Disabled>(moduland);
|
|
|
|
|
|
|
|
|
|
// Clear the slightly nebulous 'if intreq2 occurred' state.
|
|
|
|
|
will_request_interrupt = false;
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-15 17:29:32 +00:00
|
|
|
|
template <> bool Audio::Channel::transit<
|
|
|
|
|
Audio::Channel::State::PlayingLow,
|
2021-12-05 11:38:55 +00:00
|
|
|
|
Audio::Channel::State::PlayingHigh>(Channel *moduland) {
|
|
|
|
|
begin_state<State::PlayingHigh>(moduland);
|
2021-11-15 17:29:32 +00:00
|
|
|
|
|
2021-12-05 20:27:35 +00:00
|
|
|
|
bool wants_interrupt = false;
|
2021-12-06 11:35:08 +00:00
|
|
|
|
|
|
|
|
|
// volcntrld
|
|
|
|
|
volume_latch = volume;
|
|
|
|
|
reset_output_phase(); // Is this correct?
|
2021-11-15 17:29:32 +00:00
|
|
|
|
|
2021-12-05 20:27:35 +00:00
|
|
|
|
// percntrld
|
|
|
|
|
period_counter = period;
|
2021-12-02 23:43:02 +00:00
|
|
|
|
|
2021-12-05 20:27:35 +00:00
|
|
|
|
// pbufld1
|
|
|
|
|
data_latch = data;
|
2021-12-07 00:28:40 +00:00
|
|
|
|
if(moduland && attach_volume) moduland->volume = uint8_t(data_latch);
|
2021-12-05 00:17:40 +00:00
|
|
|
|
|
2021-12-05 20:27:35 +00:00
|
|
|
|
// if napnav
|
|
|
|
|
if(attach_volume || !(attach_volume || attach_period)) {
|
|
|
|
|
// [if napnav] and AUDxON
|
|
|
|
|
if(dma_enabled) {
|
|
|
|
|
// AUDxDR
|
|
|
|
|
wants_data = true;
|
|
|
|
|
|
|
|
|
|
// [if napnav and AUDxON] and intreq2
|
|
|
|
|
if(will_request_interrupt) {
|
|
|
|
|
will_request_interrupt = false;
|
|
|
|
|
wants_interrupt = true;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// AUDxIR
|
|
|
|
|
wants_interrupt = true;
|
2021-12-05 00:17:40 +00:00
|
|
|
|
}
|
2021-11-15 17:29:32 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-12-05 20:27:35 +00:00
|
|
|
|
decrement_length();
|
2021-11-15 17:29:32 +00:00
|
|
|
|
|
2021-12-05 20:27:35 +00:00
|
|
|
|
return wants_interrupt;
|
2021-11-15 17:29:32 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-12-05 11:38:55 +00:00
|
|
|
|
template <> void Audio::Channel::begin_state<Audio::Channel::State::PlayingLow>(Channel *) {
|
2021-12-04 12:20:17 +00:00
|
|
|
|
state = Audio::Channel::State::PlayingLow;
|
|
|
|
|
|
|
|
|
|
// Output low byte.
|
|
|
|
|
output_level = int8_t(data_latch & 0xff);
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-05 11:38:55 +00:00
|
|
|
|
template <> bool Audio::Channel::output<Audio::Channel::State::PlayingLow>(Channel *moduland) {
|
2021-11-15 10:29:28 +00:00
|
|
|
|
-- period_counter;
|
|
|
|
|
|
2021-12-04 13:24:41 +00:00
|
|
|
|
if(!period_counter) {
|
|
|
|
|
const bool dma_or_no_interrupt = dma_enabled || !interrupt_pending;
|
|
|
|
|
if(dma_or_no_interrupt) {
|
2021-12-05 11:38:55 +00:00
|
|
|
|
return transit<State::PlayingLow, State::PlayingHigh>(moduland);
|
2021-12-04 13:24:41 +00:00
|
|
|
|
} else {
|
2021-12-05 11:38:55 +00:00
|
|
|
|
return transit<State::PlayingLow, State::Disabled>(moduland);
|
2021-12-04 13:24:41 +00:00
|
|
|
|
}
|
2021-11-15 10:29:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// Dispatcher
|
|
|
|
|
//
|
|
|
|
|
|
2021-12-05 11:38:55 +00:00
|
|
|
|
bool Audio::Channel::output(Channel *moduland) {
|
2021-12-01 11:01:58 +00:00
|
|
|
|
// Update pulse-width modulation.
|
2021-12-06 11:35:08 +00:00
|
|
|
|
output_phase = output_phase + 1;
|
|
|
|
|
if(output_phase == 64) {
|
|
|
|
|
reset_output_phase();
|
|
|
|
|
} else {
|
|
|
|
|
output_enabled &= output_phase != volume_latch;
|
|
|
|
|
}
|
2021-12-01 11:01:58 +00:00
|
|
|
|
|
2021-11-15 10:29:28 +00:00
|
|
|
|
switch(state) {
|
2021-12-05 11:38:55 +00:00
|
|
|
|
case State::Disabled: return output<State::Disabled>(moduland);
|
|
|
|
|
case State::WaitingForDummyDMA: return output<State::WaitingForDummyDMA>(moduland);
|
|
|
|
|
case State::WaitingForDMA: return output<State::WaitingForDMA>(moduland);
|
|
|
|
|
case State::PlayingHigh: return output<State::PlayingHigh>(moduland);
|
|
|
|
|
case State::PlayingLow: return output<State::PlayingLow>(moduland);
|
2021-11-15 10:29:28 +00:00
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
assert(false);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|