// // Audio.hpp // Clock Signal // // Created by Thomas Harte on 09/11/2021. // Copyright © 2021 Thomas Harte. All rights reserved. // #ifndef Audio_hpp #define Audio_hpp #include #include #include "DMADevice.hpp" #include "../../ClockReceiver/ClockReceiver.hpp" #include "../../Concurrency/AsyncTaskQueue.hpp" #include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp" namespace Amiga { class Audio: public DMADevice<4> { public: Audio(Chipset &chipset, uint16_t *ram, size_t word_size, float output_rate); /// Idiomatic call-in for DMA scheduling; indicates that this class may /// perform a DMA access for the stated channel now. bool advance_dma(int channel); /// Advances output by one DMA window, which is implicitly two cycles /// at the output rate that was specified to the constructor. void output(); /// 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. template 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); /// Sets which interrupt requests are currently active. void set_interrupt_requests(uint16_t); /// Obtains the output source. Outputs::Speaker::Speaker *get_speaker() { return &speaker_; } 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; bool wants_data = false; uint16_t data_latch = 0x0000; // The DMA address; unlike most of the Amiga Chipset, // the user posts a value to feed a pointer, rather // than having access to the pointer itself. bool should_reload_address = false; uint32_t data_address = 0x0000'0000; // Number of words remaining in DMA data. uint16_t length = 0; uint16_t length_counter = 0; // Number of ticks between each sample, plus the // current counter, which counts downward. uint16_t period = 0; uint16_t period_counter = 0; // Modulation / attach flags. bool attach_period = false; bool attach_volume = false; // Output volume, [0, 64]. uint8_t volume = 0; uint8_t volume_latch = 0; // Indicates whether DMA is enabled for this channel. bool dma_enabled = false; // Records whether this audio interrupt is pending. bool interrupt_pending = false; bool will_request_interrupt = false; // Replicates the Hardware Reference Manual state machine; // comments indicate which of the documented states each // label refers to. enum class State { Disabled, // 000 WaitingForDummyDMA, // 001 WaitingForDMA, // 101 PlayingHigh, // 010 PlayingLow, // 011 } state = State::Disabled; /// Dispatches to the appropriate templatised output for the current state. /// @param moduland The channel to modulate, if modulation is enabled. /// @returns @c true if an interrupt should be posted; @c false otherwise. bool output(Channel *moduland); /// Applies dynamic logic for @c state, mostly testing for potential state transitions. /// @param moduland The channel to modulate, if modulation is enabled. /// @returns @c true if an interrupt should be posted; @c false otherwise. template bool output(Channel *moduland); /// Transitions from @c begin to @c end, calling the appropriate @c begin_state /// and taking any steps specific to that particular transition. /// @param moduland The channel to modulate, if modulation is enabled. /// @returns @c true if an interrupt should be posted; @c false otherwise. template bool transit(Channel *moduland); /// Begins @c state, performing all fixed logic that would otherwise have to be /// repeated endlessly in the relevant @c output. /// @param moduland The channel to modulate, if modulation is enabled. template void begin_state(Channel *moduland); /// Provides the common length-decrementing logic used when transitioning /// between PlayingHigh and PlayingLow in either direction. void decrement_length(); // Output state. int8_t output_level = 0; uint8_t output_phase = 0; bool output_enabled = false; void reset_output_phase() { output_phase = 0; output_enabled = (volume_latch > 0) && !attach_period && !attach_volume; } } channels_[4]; // Transient output state, and its destination. Outputs::Speaker::PushLowpass speaker_; Concurrency::AsyncTaskQueue queue_; using AudioBuffer = std::array; static constexpr int BufferCount = 3; AudioBuffer buffer_[BufferCount]; std::atomic buffer_available_[BufferCount]; size_t buffer_pointer_ = 0, sample_pointer_ = 0; }; } #endif /* Audio_hpp */