Emulator/GbaAudio.Channels.cs
namespace sGBA;
public partial class GbaAudio
{
private void RunPsg( long targetCycles )
{
if ( _ch1Playing )
{
int period = 4 * (2048 - _ch1Frequency) * TimingFactor;
if ( period > 0 )
{
long diff = targetCycles - _ch1LastUpdate;
if ( diff >= period )
{
int steps = (int)(diff / period);
_ch1DutyIndex = (_ch1DutyIndex + steps) & 7;
_ch1LastUpdate += steps * period;
_ch1Sample = DutyTable[_ch1Duty * 8 + _ch1DutyIndex] * _ch1EnvVolume;
}
}
}
if ( _ch2Playing )
{
int period = 4 * (2048 - _ch2Frequency) * TimingFactor;
if ( period > 0 )
{
long diff = targetCycles - _ch2LastUpdate;
if ( diff >= period )
{
int steps = (int)(diff / period);
_ch2DutyIndex = (_ch2DutyIndex + steps) & 7;
_ch2LastUpdate += steps * period;
_ch2Sample = DutyTable[_ch2Duty * 8 + _ch2DutyIndex] * _ch2EnvVolume;
}
}
}
if ( _ch3Playing )
{
int cycles = 2 * (2048 - _ch3Rate) * TimingFactor;
if ( cycles > 0 )
{
long diff = targetCycles - _ch3NextUpdate;
if ( diff >= 0 )
{
int steps = (int)(diff / cycles) + 1;
int mask = _ch3Size ? 63 : 31;
int bankOffset = 0;
if ( !_ch3Size )
bankOffset = _ch3Bank ? 16 : 0;
for ( int i = 0; i < steps; i++ )
{
_ch3Window = (_ch3Window + 1) & mask;
}
int sampleIdx = _ch3Window;
int byteIdx = bankOffset + sampleIdx / 2;
if ( byteIdx >= WaveRam.Length ) byteIdx &= (WaveRam.Length - 1);
byte dataByte = WaveRam[byteIdx];
int nibble = (sampleIdx & 1) == 0 ? (dataByte >> 4) : (dataByte & 0xF);
int volumeShift;
switch ( _ch3Volume )
{
case 0: volumeShift = 4; break;
case 1: volumeShift = 0; break;
case 2: volumeShift = 1; break;
default: volumeShift = 2; break;
}
if ( _ch3Volume > 3 )
{
nibble += nibble << 1;
}
nibble >>= volumeShift;
_ch3Sample = nibble;
_ch3NextUpdate += (long)steps * cycles;
}
}
}
if ( _ch4Playing )
{
int noiseCycles = _ch4Ratio != 0 ? 2 * _ch4Ratio : 1;
noiseCycles <<= _ch4Frequency;
noiseCycles *= 8 * TimingFactor;
if ( noiseCycles > 0 )
{
long diff = targetCycles - _ch4LastEvent;
if ( diff >= noiseCycles )
{
long last = 0;
int lsb = 0;
int coeff = _ch4Power ? 0x4040 : 0x4000;
for ( ; last + noiseCycles <= diff; last += noiseCycles )
{
lsb = (int)((_ch4Lfsr ^ (_ch4Lfsr >> 1) ^ 1) & 1);
_ch4Lfsr >>= 1;
if ( lsb != 0 )
_ch4Lfsr |= (uint)coeff;
else
_ch4Lfsr &= ~(uint)coeff;
}
_ch4Sample = lsb * _ch4EnvVolume;
_ch4LastEvent += last;
}
}
}
}
private void ClockFrameSequencer()
{
if ( !Enable ) return;
RunPsg( _totalCycles );
_frameSeqStep = (_frameSeqStep + 1) & 7;
switch ( _frameSeqStep )
{
case 2:
case 6:
ClockSweep();
ClockLengths();
break;
case 0:
case 4:
ClockLengths();
break;
case 7:
ClockEnvelopes();
break;
}
}
private void ClockLengths()
{
if ( _ch1Length > 0 && _ch1Stop )
{
_ch1Length--;
if ( _ch1Length == 0 )
_ch1Playing = false;
}
if ( _ch2Length > 0 && _ch2Stop )
{
_ch2Length--;
if ( _ch2Length == 0 )
_ch2Playing = false;
}
if ( _ch3Length > 0 && _ch3Stop )
{
_ch3Length--;
if ( _ch3Length == 0 )
_ch3Playing = false;
}
if ( _ch4Length > 0 && _ch4Stop )
{
_ch4Length--;
if ( _ch4Length == 0 )
_ch4Playing = false;
}
}
private void ClockSweep()
{
if ( !_ch1SweepEnable ) return;
_ch1SweepStep--;
if ( _ch1SweepStep == 0 )
{
if ( !UpdateSweep( false ) )
{
_ch1Playing = false;
}
}
}
private void ClockEnvelopes()
{
if ( _ch1Playing && _ch1EnvDead == 0 )
{
TickEnvelope( ref _ch1EnvVolume, ref _ch1EnvStepTime,
ref _ch1EnvDirection, ref _ch1EnvDead, ref _ch1EnvNextStep );
_ch1Sample = DutyTable[_ch1Duty * 8 + _ch1DutyIndex] * _ch1EnvVolume;
}
if ( _ch2Playing && _ch2EnvDead == 0 )
{
TickEnvelope( ref _ch2EnvVolume, ref _ch2EnvStepTime,
ref _ch2EnvDirection, ref _ch2EnvDead, ref _ch2EnvNextStep );
_ch2Sample = DutyTable[_ch2Duty * 8 + _ch2DutyIndex] * _ch2EnvVolume;
}
if ( _ch4Playing && _ch4EnvDead == 0 )
{
int oldSample = _ch4Sample;
TickEnvelope( ref _ch4EnvVolume, ref _ch4EnvStepTime,
ref _ch4EnvDirection, ref _ch4EnvDead, ref _ch4EnvNextStep );
_ch4Sample = (oldSample > 0 ? 1 : 0) * _ch4EnvVolume;
}
}
private void SamplePsg( out int left, out int right )
{
int sampleLeft = 0;
int sampleRight = 0;
if ( _psgCh1Left ) sampleLeft += _ch1Sample;
if ( _psgCh1Right ) sampleRight += _ch1Sample;
if ( _psgCh2Left ) sampleLeft += _ch2Sample;
if ( _psgCh2Right ) sampleRight += _ch2Sample;
if ( _psgCh3Left ) sampleLeft += _ch3Sample;
if ( _psgCh3Right ) sampleRight += _ch3Sample;
sampleLeft <<= 3;
sampleRight <<= 3;
int ch4Out = _ch4Sample << 3;
if ( _psgCh4Left ) sampleLeft += ch4Out;
if ( _psgCh4Right ) sampleRight += ch4Out;
left = sampleLeft * (1 + _volumeLeft);
right = sampleRight * (1 + _volumeRight);
}
private void MixSample( out short left, out short right )
{
RunPsg( _totalCycles );
SamplePsg( out int psgLeft, out int psgRight );
int psgShift = 4 - _psgVolume;
psgLeft >>= psgShift;
psgRight >>= psgShift;
int mixLeft = psgLeft;
int mixRight = psgRight;
int dmaA = _fifoA.Sample << 2;
if ( !_volumeChA ) dmaA >>= 1;
if ( _chALeft ) mixLeft += dmaA;
if ( _chARight ) mixRight += dmaA;
int dmaB = _fifoB.Sample << 2;
if ( !_volumeChB ) dmaB >>= 1;
if ( _chBLeft ) mixLeft += dmaB;
if ( _chBRight ) mixRight += dmaB;
int bias = SoundBias & 0x3FF;
mixLeft = ApplyBias( mixLeft, bias );
mixRight = ApplyBias( mixRight, bias );
left = (short)Math.Clamp( mixLeft, -32768, 32767 );
right = (short)Math.Clamp( mixRight, -32768, 32767 );
}
private static int ApplyBias( int sample, int bias )
{
sample += bias;
if ( sample >= 0x400 ) sample = 0x3FF;
else if ( sample < 0 ) sample = 0;
sample -= bias;
return sample * 48;
}
}