#include "BaseExpansionAudio.h" class UnlDripGameAudio : public BaseExpansionAudio { private: uint8_t _buffer[256]; uint8_t _readPos; uint8_t _writePos; bool _bufferFull; bool _bufferEmpty; uint16_t _freq; uint16_t _timer; uint8_t _volume; int16_t _prevOutput; protected: void StreamState(bool saving) override { BaseExpansionAudio::StreamState(saving); ArrayInfo buffer { _buffer, 256 }; Stream(_readPos, _writePos, _bufferFull, _bufferEmpty, _freq, _timer, _volume, _prevOutput, buffer); } void ClockAudio() override { if(_bufferEmpty) { return; } _timer--; if(_timer == 0) { //Each time the timer reaches zero, it is reloaded and a byte is removed from the //channel's FIFO and is output (with 0x80 being the 'center' voltage) at the //channel's specified volume. _timer = _freq; if(_readPos == _writePos) { _bufferFull = false; } _readPos++; SetOutput(((int)_buffer[_readPos] - 0x80) * _volume); if(_readPos == _writePos) { _bufferEmpty = true; } } } void SetOutput(int16_t output) { _console->GetApu()->AddExpansionAudioDelta(AudioChannel::VRC7, (output - _prevOutput) * 3); _prevOutput = output; } void ResetBuffer() { memset(_buffer, 0, 256); _readPos = 0; _writePos = 0; _bufferFull = false; _bufferEmpty = true; } public: UnlDripGameAudio(shared_ptr console) : BaseExpansionAudio(console) { _freq = 0; _timer = 0; _volume = 0; _prevOutput = 0; ResetBuffer(); } uint8_t ReadRegister() { uint8_t result = 0; if(_bufferFull) { result |= 0x80; } if(_bufferEmpty) { result |= 0x40; } return result; } void WriteRegister(uint16_t addr, uint8_t value) { switch(addr & 0x03) { case 0: //Writing any value will silence the corresponding sound channel //When a channel's Clear FIFO register is written to, its timer is reset to the //last written frequency and it is silenced, outputting a 'center' voltage. ResetBuffer(); SetOutput(0); _timer = _freq; break; case 1: //Writing a value will insert it into the FIFO. if(_readPos == _writePos) { //When data is written to an empty channel's Data Port, the channel's timer is //reloaded from the Period registers and playback begins immediately. _bufferEmpty = false; SetOutput((value - 0x80) * _volume); _timer = _freq; } _buffer[_writePos++] = value; if(_readPos == _writePos) { _bufferFull = true; } break; case 2: //Specifies channel playback rate, in cycles per sample (lower 8 bits) _freq = (_freq & 0x0F00) | value; break; case 3: //Specifies channel playback rate, in cycles per sample (higher 8 bits) (bits 0-3) //Specifies channel playback volume (bits 4-7) _freq = (_freq & 0xFF) | ((value & 0x0F) << 8); _volume = (value & 0xF0) >> 4; if(!_bufferEmpty) { //Updates to a channel's Period do not take effect until the current //sample has finished playing, but updates to a channel's Volume take effect immediately. SetOutput(((int)_buffer[_readPos] - 0x80) * _volume); } break; } } };