#include "pch.h" #include "PCE/PcePsgChannel.h" #include "PCE/PcePsg.h" #include "Utilities/Serializer.h" PcePsgChannel::PcePsgChannel() { _state.NoiseLfsr = 1; } void PcePsgChannel::Init(uint8_t index, PcePsg* psg) { _chIndex = index; _psg = psg; } void PcePsgChannel::SetOutputOffset(uint8_t offset) { _outputOffset = offset; } uint32_t PcePsgChannel::GetNoisePeriod() { if(_state.NoiseFrequency == 0x1F) { return 32; } else { return ((~_state.NoiseFrequency) & 0x1F) * 64; } } uint32_t PcePsgChannel::GetPeriod() { if(_state.DdaEnabled) { return 0; } else { uint32_t period = _state.Frequency; if(_chIndex == 0 && _psg->IsLfoEnabled()) { //When enabled, LFO alters channel 1's frequency/period period = (period + _psg->GetLfoCh1PeriodOffset()) & 0xFFF; } period = period ? period : 0x1000; if(_chIndex == 1 && _psg->IsLfoEnabled()) { //When enabled, LFO acts as a clock divider for channel 2 //which reduces its frequency (increases its period) period *= _psg->GetLfoFrequency(); } return period; } } void PcePsgChannel::Run(uint32_t clocks) { if(_chIndex >= 4) { //Source: https://web.archive.org/web/20080311065543/http://cgfm2.emuviews.com:80/blog/index.php //"I wanted to see which registers affected the LFSR, but as it turns out none of them do except for the LFSR frequency control. //If the channel is turned on or off, noise is turned on or off, or if any other setting is adjusted, the LFSR always runs at //the specified noise frequency. Apart from a reset, the LFSR state can't be changed." if(_state.NoiseTimer <= clocks) { _state.NoiseTimer = GetNoisePeriod(); //Clock noise LSFR //"The LFSR was 18 bits, with bits (LSB) 0, 1, 11, 12, and 17 (MSB) tapped. Assuming a right-shift on each clock pulse, //the bit shifted out is the noise sample. The tapped bits are XOR'd together and inserted into bit 17. After /RESET is //asserted the LFSR is initialized with bit 0 set and all other bits reset. It has a maximum sequence of 131071 bits before repeating." uint32_t v = _state.NoiseLfsr; uint32_t bit = ((v >> 0) ^ (v >> 1) ^ (v >> 11) ^ (v >> 12) ^ (v >> 17)) & 0x01; _state.NoiseOutput = (int8_t)((_state.NoiseLfsr & 0x01) ? 0x1F : 0); _state.NoiseLfsr >>= 1; _state.NoiseLfsr |= (bit << 17); } else { _state.NoiseTimer -= clocks; } } if(_state.Enabled) { if(_state.DdaEnabled) { _state.CurrentOutput = (int8_t)_state.DdaOutputValue - _outputOffset; } else if(!_state.NoiseEnabled) { _state.Timer -= clocks; if(_state.Timer == 0) { _state.Timer = GetPeriod(); _state.ReadAddr = (_state.ReadAddr + 1) & 0x1F; } _state.CurrentOutput = (int8_t)_state.WaveData[_state.ReadAddr] - _outputOffset; } else { _state.CurrentOutput = _state.NoiseOutput - _outputOffset; } } else { _state.CurrentOutput = 0; } } int16_t PcePsgChannel::GetOutput(bool forLeftChannel, uint8_t masterVolume) { //Sound reduction constants (in -1.5dB steps) constexpr uint8_t volumeReduction[30] = { 255,214,180,151,127,107,90,76,64,53,45,38,32,27,22,19,16,13,11,9,8,6,5,4,4,3,2,2,2,1 }; uint8_t reductionFactor = (0xF - masterVolume) * 2 + (0x1F - _state.Amplitude) + (0xF - (forLeftChannel ? _state.LeftVolume : _state.RightVolume)) * 2; if(reductionFactor >= 30) { //45+dB of reduction, channel is muted return 0; } return (int16_t)_state.CurrentOutput * volumeReduction[reductionFactor]; } uint16_t PcePsgChannel::GetTimer() { uint16_t minTimer = _chIndex >= 4 ? _state.NoiseTimer : 0; if(_state.Enabled && !_state.DdaEnabled && (minTimer == 0 || _state.Timer < minTimer)) { return _state.Timer; } return minTimer; } void PcePsgChannel::Write(uint16_t addr, uint8_t value) { switch(addr & 0x0F) { case 2: _state.Frequency = (_state.Frequency & 0xF00) | value; break; case 3: _state.Frequency = (_state.Frequency & 0xFF) | ((value & 0x0F) << 8); break; case 4: if(_state.Enabled != ((value & 0x80) != 0)) { _state.Timer = GetPeriod(); _state.Enabled = (value & 0x80) != 0; } _state.DdaEnabled = (value & 0x40) != 0; _state.Amplitude = (value & 0x1F); if(_state.DdaEnabled) { if(_state.Enabled) { //Update channel output immediately when DDA is enabled _state.CurrentOutput = (int8_t)_state.DdaOutputValue - _outputOffset; } else { _state.WriteAddr = 0; } } break; case 5: _state.RightVolume = value & 0x0F; _state.LeftVolume = (value >> 4) & 0x0F; break; case 6: if(_state.DdaEnabled) { _state.DdaOutputValue = value & 0x1F; if(_state.Enabled) { //Update channel output immediately with the new value when DDA is enabled _state.CurrentOutput = (int8_t)_state.DdaOutputValue - _outputOffset; } } else if(!_state.Enabled) { _state.WaveData[_state.WriteAddr] = value & 0x1F; _state.WriteAddr = (_state.WriteAddr + 1) & 0x1F; } break; case 7: _state.NoiseEnabled = (value & 0x80) != 0; _state.NoiseFrequency = (value & 0x1F); break; } } void PcePsgChannel::Serialize(Serializer& s) { SV(_state.Amplitude); SV(_state.CurrentOutput); SV(_state.DdaEnabled); SV(_state.DdaOutputValue); SV(_state.Enabled); SV(_state.Frequency); SV(_state.LeftVolume); SV(_state.NoiseEnabled); SV(_state.NoiseFrequency); SV(_state.NoiseLfsr); SV(_state.NoiseOutput); SV(_state.NoiseTimer); SV(_state.ReadAddr); SV(_state.RightVolume); SV(_state.Timer); SV(_state.WriteAddr); SVArray(_state.WaveData, 0x20); }