diff --git a/Machines/Amiga/Audio.cpp b/Machines/Amiga/Audio.cpp index 2a0a2ea02..4a9ece054 100644 --- a/Machines/Amiga/Audio.cpp +++ b/Machines/Amiga/Audio.cpp @@ -7,21 +7,60 @@ // #include "Audio.hpp" +#include using namespace Amiga; -bool Audio::advance(int) { +bool Audio::advance(int channel) { + if(channels_[channel].samples_remaining || !channels_[channel].length) { + return false; + } + + set_data(channel, ram_[pointer_[size_t(channel)]]); + ++pointer_[size_t(channel)]; + --channels_[channel].length; + return false; } -void Audio::set_length(int, uint16_t) { +void Audio::set_length(int channel, uint16_t length) { + assert(channel >= 0 && channel < 4); + channels_[channel].length = length; } -void Audio::set_period(int, uint16_t) { +void Audio::set_period(int channel, uint16_t period) { + assert(channel >= 0 && channel < 4); + channels_[channel].period = period; } -void Audio::set_volume(int, uint16_t) { +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, uint16_t) { +void Audio::set_data(int channel, uint16_t data) { + assert(channel >= 0 && channel < 4); + if(!channels_[channel].samples_remaining) { + channels_[channel].period_counter = channels_[channel].period; + } + channels_[channel].samples_remaining = 2; + channels_[channel].data = data; +} + +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; +} + +void Audio::set_modulation_flags(uint16_t) { +} + +void Audio::run_for(HalfCycles) { + // TODO: + // + // Check whether any channel's period counter is exhausted and, if + // so, attempt to consume another sample. If there are no more samples + // and length is 0, trigger an interrupt. } diff --git a/Machines/Amiga/Audio.hpp b/Machines/Amiga/Audio.hpp index 86931ebe9..84c5aafb7 100644 --- a/Machines/Amiga/Audio.hpp +++ b/Machines/Amiga/Audio.hpp @@ -10,19 +10,70 @@ #define Audio_hpp #include "DMADevice.hpp" +#include "../../ClockReceiver/ClockReceiver.hpp" +#include "../../Concurrency/AsyncTaskQueue.hpp" namespace Amiga { class Audio: public DMADevice<4> { public: - using DMADevice::DMADevice; + Audio( + Chipset &chipset, uint16_t *ram, size_t word_size, + [[maybe_unused]] double output_rate) : + DMADevice<4>(chipset, ram, word_size) {} + /// Idiomatic call-in for DMA scheduling; indicates that this class may + /// perform a DMA access for the stated channel now. bool advance(int channel); - void set_length(int, uint16_t); - void set_period(int, uint16_t); - void set_volume(int, uint16_t); - void set_data(int, uint16_t); + /// Standard JustInTimeActor item; allows this class to track the + /// amount of time between other events. + void run_for(HalfCycles); + + /// Sets the total number of words to fetch for the given channel. + void set_length(int channel, uint16_t); + + /// Sets the number of DMA windows between each 8-bit output, + /// in the same time base as @c ticks_per_line. + void set_period(int channel, uint16_t); + + /// Sets the output volume for the given channel; if bit 6 is set + /// then output is maximal; otherwise bits 0–5 select + /// a volume of [0–63]/64, on a logarithmic scale. + void set_volume(int channel, uint16_t); + + /// Sets the next two samples of audio to output. + void set_data(int channel, uint16_t); + + /// Provides a copy of the DMA enable flags, for the purpose of + /// determining which channels are enabled for DMA. + void set_channel_enables(uint16_t); + + /// Sets which channels, if any, modulate period or volume of + /// their neighbours. + void set_modulation_flags(uint16_t); + + private: + struct Channel { + // The data latch plus a count of unused samples + // in the latch, which will always be 0, 1 or 2. + uint16_t data = 0x0000; + int samples_remaining = 0; + + // Number of words remaining in DMA data. + uint16_t length = 0; + + // Number of ticks between each sample, plus the + // current counter, which counts downward. + uint16_t period = 0; + uint16_t period_counter = 0; + + // Output volume, [0, 64]. + uint8_t volume; + + // Indicates whether DMA is enabled for this channel. + bool dma_enabled = false; + } channels_[4]; }; } diff --git a/Machines/Amiga/Chipset.cpp b/Machines/Amiga/Chipset.cpp index f071002d4..083aa0e49 100644 --- a/Machines/Amiga/Chipset.cpp +++ b/Machines/Amiga/Chipset.cpp @@ -73,7 +73,7 @@ Chipset::Chipset(MemoryMap &map, int input_clock_rate) : }, bitplanes_(DMA_CONSTRUCT), copper_(DMA_CONSTRUCT), - audio_(DMA_CONSTRUCT), + audio_(DMA_CONSTRUCT, input_clock_rate), crt_(908, 4, Outputs::Display::Type::PAL50, Outputs::Display::InputDataType::Red4Green4Blue4), cia_a_handler_(map, disk_controller_, mouse_), cia_b_handler_(disk_controller_), @@ -368,7 +368,7 @@ template bool Chipset::perform_cycle() { if constexpr (cycle >= 0xd && cycle < 0x14) { constexpr auto channel = (cycle - 0xd) >> 1; if((dma_control_ & AudioFlags[channel]) == AudioFlags[channel]) { - if(audio_.advance(channel)) { + if(audio_->advance(channel)) { return false; } } @@ -444,6 +444,8 @@ template int Chipset::advance_slots(int first_slot, int last_ template Chipset::Changes Chipset::run(HalfCycles length) { Changes changes; + // TODO: incorporate audio timing here or deeper. + // This code uses 'pixels' as a measure, which is equivalent to one pixel clock time, // or half a cycle. auto pixels_remaining = length.as(); @@ -693,7 +695,7 @@ void Chipset::perform(const CPU::MC68000::Microcycle &cycle) { disk_controller_.set_control(paula_disk_control_); disk_.set_control(paula_disk_control_); - // TODO: should also post to Paula. + audio_->set_modulation_flags(paula_disk_control_); break; case Read(0x010): // ADKCONR LOG("Read disk control"); @@ -732,6 +734,7 @@ void Chipset::perform(const CPU::MC68000::Microcycle &cycle) { break; case Write(0x096): // DMACON ApplySetClear(dma_control_, 0x1fff); + audio_->set_channel_enables(dma_control_); break; // Interrupts. @@ -868,12 +871,12 @@ void Chipset::perform(const CPU::MC68000::Microcycle &cycle) { // Audio. #define Audio(index, pointer) \ - case Write(pointer + 0): audio_.set_pointer(cycle.value16()); break; \ - case Write(pointer + 2): audio_.set_pointer(cycle.value16()); break; \ - case Write(pointer + 4): audio_.set_length(index, cycle.value16()); break; \ - case Write(pointer + 6): audio_.set_period(index, cycle.value16()); break; \ - case Write(pointer + 8): audio_.set_volume(index, cycle.value16()); break; \ - case Write(pointer + 10): audio_.set_data(index, cycle.value16()); break; \ + case Write(pointer + 0): audio_->set_pointer(cycle.value16()); break; \ + case Write(pointer + 2): audio_->set_pointer(cycle.value16()); break; \ + case Write(pointer + 4): audio_->set_length(index, cycle.value16()); break; \ + case Write(pointer + 6): audio_->set_period(index, cycle.value16()); break; \ + case Write(pointer + 8): audio_->set_volume(index, cycle.value16()); break; \ + case Write(pointer + 10): audio_->set_data(index, cycle.value16()); break; \ Audio(0, 0x0a0); Audio(1, 0x0b0); diff --git a/Machines/Amiga/Chipset.hpp b/Machines/Amiga/Chipset.hpp index a2b15f2d0..67cd477f8 100644 --- a/Machines/Amiga/Chipset.hpp +++ b/Machines/Amiga/Chipset.hpp @@ -17,6 +17,7 @@ #include "../../Activity/Source.hpp" #include "../../ClockReceiver/ClockingHintSource.hpp" +#include "../../ClockReceiver/JustInTime.hpp" #include "../../Components/6526/6526.hpp" #include "../../Inputs/Mouse.hpp" #include "../../Outputs/CRT/CRT.hpp" @@ -308,7 +309,7 @@ class Chipset: private ClockingHint::Observer { // MARK: - Audio. - Audio audio_; + JustInTimeActor