BizHawk/BizHawk.Emulation/Sound/HuC6280PSG.cs

338 lines
14 KiB
C#
Raw Normal View History

2011-01-11 02:55:51 +00:00
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
namespace BizHawk.Emulation.Sound
{
// Emulates PSG audio unit of a PC Engine / Turbografx-16 / SuperGrafx.
// It is embedded on the CPU and doesn't have its own part number. None the less, it is emulated separately from the 6280 CPU.
public sealed class HuC6280PSG : ISoundProvider
{
public class PSGChannel
{
public ushort Frequency;
public byte Panning;
public byte Volume;
public bool Enabled;
public bool NoiseChannel;
public bool DDA;
public ushort NoiseFreq;
public short DDAValue;
public short[] Wave = new short[32];
public float SampleOffset;
}
public PSGChannel[] Channels = new PSGChannel[8];
public byte VoiceLatch;
private byte WaveTableWriteOffset;
private Queue<QueuedCommand> commands = new Queue<QueuedCommand>(256);
private int frameStartTime, frameStopTime;
private const int SampleRate = 44100;
private const int PsgBase = 3580000;
private static byte[] LogScale = { 0, 0, 10, 10, 13, 13, 16, 16, 20, 20, 26, 26, 32, 32, 40, 40, 51, 51, 64, 64, 81, 81, 102, 102, 128, 128, 161, 161, 203, 203, 255, 255 };
public byte MainVolumeLeft;
public byte MainVolumeRight;
public HuC6280PSG()
{
2011-01-16 21:06:14 +00:00
Waves.InitWaves();
2011-01-11 02:55:51 +00:00
for (int i=0; i<8; i++)
Channels[i] = new PSGChannel();
}
public void BeginFrame(int cycles)
{
while (commands.Count > 0)
{
var cmd = commands.Dequeue();
WritePSGImmediate(cmd.Register, cmd.Value);
}
frameStartTime = cycles;
}
public void EndFrame(int cycles)
{
frameStopTime = cycles;
}
public void WritePSG(ushort register, byte value, int cycles)
{
commands.Enqueue(new QueuedCommand { Register = register, Value = value, Time = cycles-frameStartTime });
}
public void WritePSGImmediate(ushort register, byte value)
{
switch (register)
{
case 0x800: // Set Voice Latch
VoiceLatch = (byte) (value & 7);
break;
case 0x801: // Global Volume select;
MainVolumeLeft = (byte) ((value >> 4) & 0x0F);
MainVolumeRight = (byte) (value & 0x0F);
break;
case 0x802: // Frequency LSB
Channels[VoiceLatch].Frequency &= 0xFF00;
Channels[VoiceLatch].Frequency |= value;
break;
case 0x803: // Frequency MSB
Channels[VoiceLatch].Frequency &= 0x00FF;
Channels[VoiceLatch].Frequency |= (ushort)(value << 8);
Channels[VoiceLatch].Frequency &= 0x0FFF;
break;
case 0x804: // Voice Volume
Channels[VoiceLatch].Volume = (byte) (value & 0x1F);
Channels[VoiceLatch].Enabled = (value & 0x80) != 0;
Channels[VoiceLatch].DDA = (value & 0x40) != 0;
if (Channels[VoiceLatch].Enabled == false && Channels[VoiceLatch].DDA)
WaveTableWriteOffset = 0;
break;
case 0x805: // Panning
Channels[VoiceLatch].Panning = value;
break;
case 0x806: // Wave data
if (Channels[VoiceLatch].DDA == false)
{
Channels[VoiceLatch].Wave[WaveTableWriteOffset++] = (short) ((value*2047) - 32767);
WaveTableWriteOffset &= 31;
} else {
Channels[VoiceLatch].DDAValue = (short)((value * 2047) - 32767);
}
break;
case 0x807: // Noise
Channels[VoiceLatch].NoiseChannel = ((value & 0x80) != 0) && VoiceLatch >= 4;
if ((value & 0x1F) == 0x1F)
value &= 0xFE;
Channels[VoiceLatch].NoiseFreq = (ushort) (PsgBase/(64*(0x1F - (value & 0x1F))));
break;
case 0x0808: // LFO
// TODO: implement LFO
break;
case 0x809: // LFO Control
if ((value & 0x80) == 0 && (value & 3) != 0)
{
Channels[1].Enabled = false;
} else
{
Channels[1].Enabled = true;
}
break;
}
}
public void GetSamples(short[] samples)
{
int elapsedCycles = frameStopTime - frameStartTime;
int start = 0;
while (commands.Count > 0)
{
var cmd = commands.Dequeue();
int pos = ((cmd.Time * samples.Length) / elapsedCycles) & ~1;
MixSamples(samples, start, pos - start);
start = pos;
WritePSGImmediate(cmd.Register, cmd.Value);
}
MixSamples(samples, start, samples.Length - start);
}
private void MixSamples(short[] samples, int start, int len)
{
for (int i = 0; i < 6; i++)
MixChannel(samples, start, len, Channels[i]);
}
private void MixChannel(short[] samples, int start, int len, PSGChannel channel)
{
if (channel.Enabled == false) return;
if (channel.DDA == false && (channel.Frequency == 0 || channel.Volume == 0)) return;
int freq = PsgBase / (32 * (channel.DDA ? 1 : (int)channel.Frequency));
int leftVol = channel.Panning >> 4;
int rightVol = channel.Panning & 15;
leftVol *= MainVolumeLeft;
rightVol *= MainVolumeRight;
leftVol /= 16;
rightVol /= 16;
short[] wave = channel.Wave;
if (channel.NoiseChannel)
{
wave = Waves.NoiseWave;
freq = channel.NoiseFreq;
}
2011-01-16 21:06:14 +00:00
float adjustedWaveLengthInSamples = SampleRate / (channel.NoiseChannel ? freq/(float)channel.Wave.Length : freq);
2011-01-11 02:55:51 +00:00
float moveThroughWaveRate = wave.Length / adjustedWaveLengthInSamples;
int end = start + len;
for (int i=start; i<end;)
{
channel.SampleOffset %= wave.Length;
short value = channel.DDA ? channel.DDAValue : wave[(int) channel.SampleOffset];
2011-01-16 21:06:14 +00:00
samples[i++] += (short)((value * LogScale[channel.Volume] / 255f / 6f) * (leftVol / 15f));
2011-01-11 02:55:51 +00:00
samples[i++] += (short)((value * LogScale[channel.Volume] / 255f / 6f) * (rightVol / 15f));
channel.SampleOffset += moveThroughWaveRate;
channel.SampleOffset %= wave.Length;
}
}
public void SaveStateText(TextWriter writer)
{
writer.WriteLine("[PSG]");
writer.WriteLine("MainVolumeLeft {0:X2}", MainVolumeLeft);
writer.WriteLine("MainVolumeRight {0:X2}", MainVolumeRight);
writer.WriteLine("VoiceLatch {0}", VoiceLatch);
writer.WriteLine("WaveTableWriteOffset {0:X2}", WaveTableWriteOffset);
writer.WriteLine();
for (int i = 0; i<6; i++)
{
writer.WriteLine("[Channel{0}]",i+1);
writer.WriteLine("Frequency {0:X4}", Channels[i].Frequency);
writer.WriteLine("Panning {0:X2}", Channels[i].Panning);
writer.WriteLine("Volume {0:X2}", Channels[i].Volume);
writer.WriteLine("Enabled {0}", Channels[i].Enabled);
if (i.In(4,5))
{
writer.WriteLine("NoiseChannel {0}", Channels[i].NoiseChannel);
writer.WriteLine("NoiseFreq {0:X4}", Channels[i].NoiseFreq);
}
writer.WriteLine("DDA {0}",Channels[i].DDA);
writer.WriteLine("DDAValue {0:X4}", Channels[i].DDAValue);
writer.WriteLine("SampleOffset {0}", Channels[i].SampleOffset);
writer.Write("Wave ");
Channels[i].Wave.SaveAsHex(writer);
writer.WriteLine("[/Channel{0}]\n",i+1);
}
writer.WriteLine("[/PSG]\n");
}
public void LoadStateText(TextReader reader)
{
while (true)
{
string[] args = reader.ReadLine().Split(' ');
if (args[0].Trim() == "") continue;
if (args[0] == "[/PSG]") break;
if (args[0] == "MainVolumeLeft")
MainVolumeLeft = byte.Parse(args[1], NumberStyles.HexNumber);
else if (args[0] == "MainVolumeRight")
MainVolumeRight = byte.Parse(args[1], NumberStyles.HexNumber);
else if (args[0] == "VoiceLatch")
VoiceLatch = byte.Parse(args[1]);
else if (args[0] == "WaveTableWriteOffset")
WaveTableWriteOffset = byte.Parse(args[1]);
else if (args[0] == "[Channel1]")
LoadChannelStateText(reader, 0);
else if (args[0] == "[Channel2]")
LoadChannelStateText(reader, 1);
else if (args[0] == "[Channel3]")
LoadChannelStateText(reader, 2);
else if (args[0] == "[Channel4]")
LoadChannelStateText(reader, 3);
else if (args[0] == "[Channel5]")
LoadChannelStateText(reader, 4);
else if (args[0] == "[Channel6]")
LoadChannelStateText(reader, 5);
else
Console.WriteLine("Skipping unrecognized identifier " + args[0]);
}
}
private void LoadChannelStateText(TextReader reader, int channel)
{
while (true)
{
string[] args = reader.ReadLine().Split(' ');
if (args[0].Trim() == "") continue;
if (args[0] == "[/Channel"+(channel+1)+"]") break;
if (args[0] == "Frequency")
Channels[channel].Frequency = ushort.Parse(args[1], NumberStyles.HexNumber);
else if (args[0] == "Panning")
Channels[channel].Panning = byte.Parse(args[1], NumberStyles.HexNumber);
else if (args[0] == "Volume")
Channels[channel].Volume = byte.Parse(args[1], NumberStyles.HexNumber);
else if (args[0] == "Enabled")
Channels[channel].Enabled = bool.Parse(args[1]);
else if (args[0] == "NoiseChannel")
Channels[channel].NoiseChannel = bool.Parse(args[1]);
else if (args[0] == "NoiseFreq")
Channels[channel].NoiseFreq = ushort.Parse(args[1], NumberStyles.HexNumber);
else if (args[0] == "DDA")
Channels[channel].DDA = bool.Parse(args[1]);
else if (args[0] == "DDAValue")
Channels[channel].DDAValue = short.Parse(args[1], NumberStyles.HexNumber);
else if (args[0] == "SampleOffset")
Channels[channel].SampleOffset = float.Parse(args[1]);
else if (args[0] == "Wave")
Channels[channel].Wave.ReadFromHex(args[1]);
else
Console.WriteLine("Skipping unrecognized identifier " + args[0]);
}
}
public void SaveStateBinary(BinaryWriter writer)
{
writer.Write(MainVolumeLeft);
writer.Write(MainVolumeRight);
writer.Write(VoiceLatch);
writer.Write(WaveTableWriteOffset);
for (int i = 0; i < 6; i++)
{
writer.Write(Channels[i].Frequency);
writer.Write(Channels[i].Panning);
writer.Write(Channels[i].Volume);
writer.Write(Channels[i].Enabled);
writer.Write(Channels[i].NoiseChannel);
writer.Write(Channels[i].NoiseFreq);
writer.Write(Channels[i].DDA);
writer.Write(Channels[i].DDAValue);
writer.Write(Channels[i].SampleOffset);
for (int j = 0; j < 32; j++)
writer.Write(Channels[i].Wave[j]);
}
}
public void LoadStateBinary(BinaryReader reader)
{
MainVolumeLeft = reader.ReadByte();
MainVolumeRight = reader.ReadByte();
VoiceLatch = reader.ReadByte();
WaveTableWriteOffset = reader.ReadByte();
for (int i=0; i<6; i++)
{
Channels[i].Frequency = reader.ReadUInt16();
Channels[i].Panning = reader.ReadByte();
Channels[i].Volume = reader.ReadByte();
Channels[i].Enabled = reader.ReadBoolean();
Channels[i].NoiseChannel = reader.ReadBoolean();
Channels[i].NoiseFreq = reader.ReadUInt16();
Channels[i].DDA = reader.ReadBoolean();
Channels[i].DDAValue = reader.ReadInt16();
Channels[i].SampleOffset = reader.ReadSingle();
for (int j = 0; j < 32; j++)
Channels[i].Wave[j] = reader.ReadInt16();
}
}
class QueuedCommand
{
public ushort Register;
public byte Value;
public int Time;
}
}
}