Essgee.Unity/Assets/Plugins/Essgee/Emulation/Audio/SN76489.cs
2025-01-02 17:55:16 +08:00

388 lines
11 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Essgee.Exceptions;
using Essgee.EventArguments;
using Essgee.Utilities;
using static Essgee.Emulation.Utilities;
namespace Essgee.Emulation.Audio
{
public class SN76489 : IAudio
{
/* http://www.smspower.org/Development/SN76489 */
/* Differences in various system's PSGs: http://forums.nesdev.com/viewtopic.php?p=190216#p190216 */
protected const int numChannels = 4, numToneChannels = 3, noiseChannelIndex = 3;
protected const string channel1OptionName = "AudioEnableCh1Square";
protected const string channel2OptionName = "AudioEnableCh2Square";
protected const string channel3OptionName = "AudioEnableCh3Square";
protected const string channel4OptionName = "AudioEnableCh4Noise";
/* Noise generation constants */
protected virtual ushort noiseLfsrMask => 0x7FFF;
protected virtual ushort noiseTappedBits => 0x0003; /* Bits 0 and 1 */
protected virtual int noiseBitShift => 14;
/* Sample generation & event handling */
protected List<short>[] channelSampleBuffer;
protected List<short> mixedSampleBuffer;
public virtual event EventHandler<EnqueueSamplesEventArgs> EnqueueSamples;
public virtual void OnEnqueueSamples(EnqueueSamplesEventArgs e) { EnqueueSamples?.Invoke(this, e); }
/* Audio output variables */
protected int sampleRate, numOutputChannels;
/* Channel registers */
[StateRequired]
protected ushort[] volumeRegisters; /* Channels 0-3: 4 bits */
[StateRequired]
protected ushort[] toneRegisters; /* Channels 0-2 (tone): 10 bits; channel 3 (noise): 3 bits */
/* Channel counters */
[StateRequired]
protected ushort[] channelCounters; /* 10-bit counters */
[StateRequired]
protected bool[] channelOutput;
/* Volume attenuation table */
protected short[] volumeTable; /* 2dB change per volume register step */
/* Latched channel/type */
[StateRequired]
byte latchedChannel, latchedType;
/* Linear-feedback shift register, for noise generation */
[StateRequired]
protected ushort noiseLfsr; /* 15-bit */
/* Timing variables */
double clockRate, refreshRate;
int samplesPerFrame, cyclesPerFrame, cyclesPerSample;
[StateRequired]
int sampleCycleCount, frameCycleCount, dividerCount;
/* User-facing channel toggles */
protected bool channel1ForceEnable, channel2ForceEnable, channel3ForceEnable, channel4ForceEnable;
public (string Name, string Description)[] RuntimeOptions => new (string name, string description)[]
{
(channel1OptionName, "Channel 1 (Square)"),
(channel2OptionName, "Channel 2 (Square)"),
(channel3OptionName, "Channel 3 (Square)"),
(channel4OptionName, "Channel 4 (Noise)")
};
public SN76489()
{
channelSampleBuffer = new List<short>[numChannels];
for (int i = 0; i < numChannels; i++) channelSampleBuffer[i] = new List<short>();
mixedSampleBuffer = new List<short>();
volumeRegisters = new ushort[numChannels];
toneRegisters = new ushort[numChannels];
channelCounters = new ushort[numChannels];
channelOutput = new bool[numChannels];
volumeTable = new short[16];
for (int i = 0; i < volumeTable.Length; i++)
volumeTable[i] = (short)(short.MaxValue * Math.Pow(2.0, i * -2.0 / 6.0));
volumeTable[15] = 0;
samplesPerFrame = cyclesPerFrame = cyclesPerSample = -1;
channel1ForceEnable = true;
channel2ForceEnable = true;
channel3ForceEnable = true;
channel4ForceEnable = true;
}
public object GetRuntimeOption(string name)
{
switch (name)
{
case channel1OptionName: return channel1ForceEnable;
case channel2OptionName: return channel2ForceEnable;
case channel3OptionName: return channel3ForceEnable;
case channel4OptionName: return channel4ForceEnable;
default: return null;
}
}
public void SetRuntimeOption(string name, object value)
{
switch (name)
{
case channel1OptionName: channel1ForceEnable = (bool)value; break;
case channel2OptionName: channel2ForceEnable = (bool)value; break;
case channel3OptionName: channel3ForceEnable = (bool)value; break;
case channel4OptionName: channel4ForceEnable = (bool)value; break;
}
}
public void SetSampleRate(int rate)
{
sampleRate = rate;
ConfigureTimings();
}
public void SetOutputChannels(int channels)
{
numOutputChannels = channels;
ConfigureTimings();
}
public void SetClockRate(double clock)
{
clockRate = clock;
ConfigureTimings();
}
public void SetRefreshRate(double refresh)
{
refreshRate = refresh;
ConfigureTimings();
}
private void ConfigureTimings()
{
samplesPerFrame = (int)(sampleRate / refreshRate);
cyclesPerFrame = (int)(clockRate / refreshRate);
cyclesPerSample = (cyclesPerFrame / samplesPerFrame);
FlushSamples();
}
public virtual void Startup()
{
Reset();
if (samplesPerFrame == -1) throw new EmulationException("SN76489: Timings not configured, invalid samples per frame");
if (cyclesPerFrame == -1) throw new EmulationException("SN76489: Timings not configured, invalid cycles per frame");
if (cyclesPerSample == -1) throw new EmulationException("SN76489: Timings not configured, invalid cycles per sample");
}
public virtual void Shutdown()
{
//
}
public virtual void Reset()
{
FlushSamples();
latchedChannel = latchedType = 0x00;
noiseLfsr = 0x4000;
for (int i = 0; i < numChannels; i++)
{
volumeRegisters[i] = 0x000F;
toneRegisters[i] = 0x0000;
}
sampleCycleCount = frameCycleCount = dividerCount = 0;
}
public void Step(int clockCyclesInStep)
{
sampleCycleCount += clockCyclesInStep;
frameCycleCount += clockCyclesInStep;
for (int i = 0; i < clockCyclesInStep; i++)
{
dividerCount++;
if (dividerCount == 16)
{
for (int ch = 0; ch < numToneChannels; ch++)
StepToneChannel(ch);
StepNoiseChannel();
dividerCount = 0;
}
}
if (sampleCycleCount >= cyclesPerSample)
{
GenerateSample();
sampleCycleCount -= cyclesPerSample;
}
if (mixedSampleBuffer.Count >= (samplesPerFrame * numOutputChannels))
{
OnEnqueueSamples(new EnqueueSamplesEventArgs(
numChannels,
channelSampleBuffer.Select(x => x.ToArray()).ToArray(),
new bool[] { !channel1ForceEnable, !channel2ForceEnable, !channel3ForceEnable, !channel4ForceEnable },
mixedSampleBuffer.ToArray()));
FlushSamples();
}
if (frameCycleCount >= cyclesPerFrame)
{
frameCycleCount -= cyclesPerFrame;
sampleCycleCount = frameCycleCount;
}
}
private void StepToneChannel(int ch)
{
/* Check for counter underflow */
if ((channelCounters[ch] & 0x03FF) > 0)
channelCounters[ch]--;
/* Counter underflowed, reload and flip output bit, then generate sample */
if ((channelCounters[ch] & 0x03FF) == 0)
{
channelCounters[ch] = (ushort)(toneRegisters[ch] & 0x03FF);
channelOutput[ch] = !channelOutput[ch];
}
}
private void StepNoiseChannel()
{
int chN = noiseChannelIndex;
{
/* Check for counter underflow */
if ((channelCounters[chN] & 0x03FF) > 0)
channelCounters[chN]--;
/* Counter underflowed, reload and flip output bit */
if ((channelCounters[chN] & 0x03FF) == 0)
{
switch (toneRegisters[chN] & 0x3)
{
case 0x0: channelCounters[chN] = 0x10; break;
case 0x1: channelCounters[chN] = 0x20; break;
case 0x2: channelCounters[chN] = 0x40; break;
case 0x3: channelCounters[chN] = (ushort)(toneRegisters[2] & 0x03FF); break;
}
channelOutput[chN] = !channelOutput[chN];
if (channelOutput[chN])
{
/* Check noise type, then generate sample */
bool isWhiteNoise = (((toneRegisters[chN] >> 2) & 0x1) == 0x1);
ushort newLfsrBit = (ushort)((isWhiteNoise ? CheckParity((ushort)(noiseLfsr & noiseTappedBits)) : (noiseLfsr & 0x01)) << noiseBitShift);
noiseLfsr = (ushort)((newLfsrBit | (noiseLfsr >> 1)) & noiseLfsrMask);
}
}
}
}
protected virtual void GenerateSample()
{
for (int i = 0; i < numOutputChannels; i++)
{
/* Generate samples */
var ch1 = (short)(volumeTable[volumeRegisters[0]] * ((toneRegisters[0] < 2 ? true : channelOutput[0]) ? 1.0 : 0.0));
var ch2 = (short)(volumeTable[volumeRegisters[1]] * ((toneRegisters[1] < 2 ? true : channelOutput[1]) ? 1.0 : 0.0));
var ch3 = (short)(volumeTable[volumeRegisters[2]] * ((toneRegisters[2] < 2 ? true : channelOutput[2]) ? 1.0 : 0.0));
var ch4 = (short)(volumeTable[volumeRegisters[3]] * (noiseLfsr & 0x1));
channelSampleBuffer[0].Add(ch1);
channelSampleBuffer[1].Add(ch2);
channelSampleBuffer[2].Add(ch3);
channelSampleBuffer[3].Add(ch4);
/* Mix samples */
var mixed = 0;
if (channel1ForceEnable) mixed += ch1;
if (channel2ForceEnable) mixed += ch2;
if (channel3ForceEnable) mixed += ch3;
if (channel4ForceEnable) mixed += ch4;
mixed /= numChannels;
mixedSampleBuffer.Add((short)mixed);
}
}
public void FlushSamples()
{
for (int i = 0; i < numChannels; i++)
channelSampleBuffer[i].Clear();
mixedSampleBuffer.Clear();
}
private ushort CheckParity(ushort val)
{
val ^= (ushort)(val >> 8);
val ^= (ushort)(val >> 4);
val ^= (ushort)(val >> 2);
val ^= (ushort)(val >> 1);
return (ushort)(val & 0x1);
}
public virtual byte ReadPort(byte port)
{
throw new EmulationException("SN76489: Cannot read ports");
}
public virtual void WritePort(byte port, byte data)
{
if (IsBitSet(data, 7))
{
/* LATCH/DATA byte; get channel (0-3) and type (0 is tone/noise, 1 is volume) */
latchedChannel = (byte)((data >> 5) & 0x03);
latchedType = (byte)((data >> 4) & 0x01);
/* Mask off non-data bits */
data &= 0x0F;
/* If target is channel 3 noise (3 bits), mask off highest bit */
if (latchedChannel == 3 && latchedType == 0)
data &= 0x07;
/* Write to register */
if (latchedType == 0)
{
/* Data is tone/noise */
toneRegisters[latchedChannel] = (ushort)((toneRegisters[latchedChannel] & 0x03F0) | data);
}
else
{
/* Data is volume */
volumeRegisters[latchedChannel] = data;
}
}
else
{
/* DATA byte; mask off non-data bits */
data &= 0x3F;
/* Write to register */
if (latchedType == 0)
{
/* Data is tone/noise */
if (latchedChannel == 3)
{
/* Target is channel 3 noise, mask off excess bits and write to low bits of register */
toneRegisters[latchedChannel] = (ushort)(data & 0x07);
}
else
{
/* Target is not channel 3 noise, write to high bits of register */
toneRegisters[latchedChannel] = (ushort)((toneRegisters[latchedChannel] & 0x000F) | (data << 4));
}
}
else
{
/* Data is volume; mask off excess bits and write to low bits of register */
volumeRegisters[latchedChannel] = (ushort)(data & 0x0F);
}
}
}
}
}