mirror of
https://github.com/SourMesen/Mesen2.git
synced 2025-04-02 10:21:44 -04:00
193 lines
5.4 KiB
C++
193 lines
5.4 KiB
C++
#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);
|
|
}
|