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 commands = new Queue(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() { Waves.InitWaves(); 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; } float adjustedWaveLengthInSamples = SampleRate / (channel.NoiseChannel ? freq/(float)(channel.Wave.Length*128) : freq); float moveThroughWaveRate = wave.Length / adjustedWaveLengthInSamples; int end = start + len; for (int i=start; i