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; byte WaveTableWriteOffset; Queue commands = new Queue(256); long frameStartTime, frameStopTime; const int SampleRate = 44100; const int PsgBase = 3580000; 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 }; static byte[] VolumeReductionTable = { 0x1F, 0x1D, 0x1B, 0x19, 0x17, 0x15, 0x13, 0x10, 0x0F, 0x0D, 0x0B, 0x09, 0x07, 0x05, 0x03, 0x00 }; public byte MainVolumeLeft; public byte MainVolumeRight; public int MaxVolume { get; set; } public HuC6280PSG() { MaxVolume = short.MaxValue; Waves.InitWaves(); for (int i=0; i<8; i++) Channels[i] = new PSGChannel(); } public void BeginFrame(long cycles) { while (commands.Count > 0) { var cmd = commands.Dequeue(); WritePSGImmediate(cmd.Register, cmd.Value); } frameStartTime = cycles; } public void EndFrame(long cycles) { frameStopTime = cycles; } public void WritePSG(byte register, byte value, long cycles) { commands.Enqueue(new QueuedCommand { Register = register, Value = value, Time = cycles-frameStartTime }); } public void WritePSGImmediate(int register, byte value) { register &= 0x0F; switch (register) { case 0: // Set Voice Latch VoiceLatch = (byte) (value & 7); break; case 1: // Global Volume select; MainVolumeLeft = (byte) ((value >> 4) & 0x0F); MainVolumeRight = (byte) (value & 0x0F); break; case 2: // Frequency LSB Channels[VoiceLatch].Frequency &= 0xFF00; Channels[VoiceLatch].Frequency |= value; break; case 3: // Frequency MSB Channels[VoiceLatch].Frequency &= 0x00FF; Channels[VoiceLatch].Frequency |= (ushort)(value << 8); Channels[VoiceLatch].Frequency &= 0x0FFF; break; case 4: // 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 5: // Panning Channels[VoiceLatch].Panning = value; break; case 6: // 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 7: // 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 8: // LFO // TODO: implement LFO break; case 9: // LFO Control if ((value & 0x80) == 0 && (value & 3) != 0) { Console.WriteLine("**************** LFO ON !!!!!!!!!! *****************"); Channels[1].Enabled = false; } else { Channels[1].Enabled = true; } break; } } public void DiscardSamples() { } public void GetSamples(short[] samples) { int elapsedCycles = (int) (frameStopTime - frameStartTime); int start = 0; while (commands.Count > 0) { var cmd = commands.Dequeue(); int pos = (int) ((cmd.Time * samples.Length) / elapsedCycles) & ~1; MixSamples(samples, start, pos - start); start = pos; WritePSGImmediate(cmd.Register, cmd.Value); } MixSamples(samples, start, samples.Length - start); } void MixSamples(short[] samples, int start, int len) { for (int i = 0; i < 6; i++) MixChannel(samples, start, len, Channels[i]); } void MixChannel(short[] samples, int start, int len, PSGChannel channel) { if (channel.Enabled == false) return; if (channel.DDA == false && channel.Volume == 0) return; short[] wave = channel.Wave; int freq; if (channel.NoiseChannel) { wave = Waves.NoiseWave; freq = channel.NoiseFreq; } else if (channel.DDA) { freq = 0; } else { if (channel.Frequency <= 1) return; freq = PsgBase / (32 * ((int)channel.Frequency)); } int globalPanFactorLeft = VolumeReductionTable[MainVolumeLeft]; int globalPanFactorRight = VolumeReductionTable[MainVolumeRight]; int channelPanFactorLeft = VolumeReductionTable[channel.Panning >> 4]; int channelPanFactorRight = VolumeReductionTable[channel.Panning & 0xF]; int channelVolumeFactor = 0x1f - channel.Volume; int volumeLeft = 0x1F - globalPanFactorLeft - channelPanFactorLeft - channelVolumeFactor; if (volumeLeft < 0) volumeLeft = 0; int volumeRight = 0x1F - globalPanFactorRight - channelPanFactorRight - channelVolumeFactor; if (volumeRight < 0) volumeRight = 0; 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