diff --git a/BizHawk.Emulation/BizHawk.Emulation.csproj b/BizHawk.Emulation/BizHawk.Emulation.csproj index 7e0118d411..2b4d5347b7 100644 --- a/BizHawk.Emulation/BizHawk.Emulation.csproj +++ b/BizHawk.Emulation/BizHawk.Emulation.csproj @@ -532,6 +532,7 @@ + diff --git a/BizHawk.Emulation/Consoles/Nintendo/NES/Boards/VRC6.cs b/BizHawk.Emulation/Consoles/Nintendo/NES/Boards/VRC6.cs index 80b180f845..0f96b4df30 100644 --- a/BizHawk.Emulation/Consoles/Nintendo/NES/Boards/VRC6.cs +++ b/BizHawk.Emulation/Consoles/Nintendo/NES/Boards/VRC6.cs @@ -12,7 +12,8 @@ namespace BizHawk.Emulation.Consoles.Nintendo int prg_bank_mask_8k, chr_bank_mask_1k; bool newer_variant; - Sound.VRC6 VRC6Sound = new Sound.VRC6(); + //Sound.VRC6 VRC6Sound = new Sound.VRC6(); + Sound.VRC6Alt VRC6Sound; //state int prg_bank_16k, prg_bank_8k; @@ -29,6 +30,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo base.Dispose(); prg_banks_8k.Dispose(); chr_banks_1k.Dispose(); + VRC6Sound.Dispose(); } public override void SyncState(Serializer ser) @@ -93,6 +95,8 @@ namespace BizHawk.Emulation.Consoles.Nintendo SyncPRG(); SetMirrorType(EMirrorType.Vertical); + VRC6Sound = new Sound.VRC6Alt((uint)NES.cpuclockrate); + return true; } public override byte ReadPRG(int addr) @@ -246,6 +250,8 @@ namespace BizHawk.Emulation.Consoles.Nintendo public override void ClockCPU() { + VRC6Sound.Clock(); + if (!irq_enabled) return; if (irq_mode) @@ -265,6 +271,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo public override void ApplyCustomAudio(short[] samples) { + /* short[] fmsamples = new short[samples.Length]; VRC6Sound.GetSamples(fmsamples); int len = samples.Length; @@ -272,6 +279,8 @@ namespace BizHawk.Emulation.Consoles.Nintendo { samples[i] = (short)((samples[i] >> 1) + (fmsamples[i] >> 1)); } + */ + VRC6Sound.ApplyCustomAudio(samples); } } diff --git a/BizHawk.Emulation/Sound/VRC6Alt.cs b/BizHawk.Emulation/Sound/VRC6Alt.cs new file mode 100644 index 0000000000..ebb17f139f --- /dev/null +++ b/BizHawk.Emulation/Sound/VRC6Alt.cs @@ -0,0 +1,314 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace BizHawk.Emulation.Sound +{ + public class VRC6Alt : IDisposable + { + // http://wiki.nesdev.com/w/index.php/VRC6_audio + // $9003 not implemented + + + + + + #region blip-buf interface + + Sound.Utilities.BlipBuffer blip; + // yes, some of this is copy+pasted from the FDS, and more or less from the NES + // as soon as i decide that i like it and i use it a third time, i'll put it in a class + + struct Delta + { + public uint time; + public int value; + public Delta(uint time, int value) + { + this.time = time; + this.value = value; + } + } + List dlist = new List(); + + uint sampleclock = 0; + const int blipsize = 4096; + + short[] mixout = new short[blipsize]; + + public void ApplyCustomAudio(short[] samples) + { + int nsamp = samples.Length / 2; + if (nsamp > blipsize) // oh well. + nsamp = blipsize; + uint targetclock = (uint)blip.ClocksNeeded(nsamp); + foreach (var d in dlist) + blip.AddDelta(d.time * targetclock / sampleclock, d.value); + dlist.Clear(); + blip.EndFrame(targetclock); + sampleclock = 0; + blip.ReadSamples(mixout, nsamp, false); + + for (int i = 0, j = 0; i < nsamp; i++, j += 2) + { + int s = mixout[i] +samples[j]; + if (s > 32767) + samples[j] = 32767; + else if (s <= -32768) + samples[j] = -32768; + else + samples[j] = (short)s; + // nes audio is mono, so we can ignore the original value of samples[j+1] + samples[j + 1] = samples[j]; + } + } + + #endregion + + Pulse pulse1, pulse2; + Saw saw; + + /// + /// + /// + /// frequency of the M2 clock in hz + public VRC6Alt(uint freq) + { + if (freq > 0) + { + blip = new Utilities.BlipBuffer(blipsize); + blip.SetRates(freq, 44100); + } + pulse1 = new Pulse(PulseAddDiff); + pulse2 = new Pulse(PulseAddDiff); + saw = new Saw(SawAddDiff); + } + + public void Dispose() + { + if (blip != null) + { + blip.Dispose(); + blip = null; + } + } + + void PulseAddDiff(int value) + { + dlist.Add(new Delta(sampleclock, value * 360)); + } + void SawAddDiff(int value) + { + dlist.Add(new Delta(sampleclock, value * 180)); + } + + public void SyncState(Serializer ser) + { + ser.BeginSection("VRC6Alt"); + pulse1.SyncState(ser); + pulse2.SyncState(ser); + saw.SyncState(ser); + ser.EndSection(); + } + + public void Write9000(byte value) { pulse1.Write0(value); } + public void Write9001(byte value) { pulse1.Write1(value); } + public void Write9002(byte value) { pulse1.Write2(value); } + + public void Write9003(byte value) + { + + } + + public void WriteA000(byte value) { pulse2.Write0(value); } + public void WriteA001(byte value) { pulse2.Write1(value); } + public void WriteA002(byte value) { pulse2.Write2(value); } + + public void WriteB000(byte value) { saw.Write0(value); } + public void WriteB001(byte value) { saw.Write1(value); } + public void WriteB002(byte value) { saw.Write2(value); } + + public void Clock() + { + pulse1.Clock(); + pulse2.Clock(); + saw.Clock(); + sampleclock++; + } + + class Saw + { + Action SendDiff; + public Saw(Action SendDiff) { this.SendDiff = SendDiff; } + + // set by regs + byte A; + int F; + bool E; + // internal state + int count; + byte accum; + int acount; + int value; + + void SendNew() + { + int newvalue = accum >> 3; + if (newvalue != value) + { + SendDiff(value - newvalue); // intentionally flipped + value = newvalue; + } + } + + public void SyncState(Serializer ser) + { + ser.Sync("A", ref A); + ser.Sync("F", ref F); + ser.Sync("E", ref E); + ser.Sync("count", ref count); + ser.Sync("accum", ref accum); + ser.Sync("acount", ref acount); + ser.Sync("value", ref value); + } + + public void Write0(byte value) + { + A = (byte)(value & 63); + } + public void Write1(byte value) + { + F &= 0xf00; + F |= value; + } + public void Write2(byte value) + { + F &= 0x0ff; + F |= value << 8 & 0xf00; + E = value.Bit(7); + if (!E) + { + accum = 0; + SendNew(); + } + } + + public void Clock() + { + if (!E) + return; + count--; + if (count <= 0) + { + count = F; + acount++; + if (acount % 2 == 0) + { + if (acount < 14) + { + accum += A; + } + else + { + accum = 0; + acount = 0; + } + SendNew(); + } + } + } + } + + class Pulse + { + Action SendDiff; + public Pulse(Action SendDiff) { this.SendDiff = SendDiff; } + + // set by regs + int V; + int D; + int F; + bool E; + // internal state + int count; + int duty; + int value; + + void SendNew() + { + int newvalue; + if (duty <= D) + newvalue = V; + else + newvalue = 0; + if (newvalue != value) + { + SendDiff(value - newvalue); // intentionally flipped + value = newvalue; + } + } + + void SendNewZero() + { + if (0 != value) + { + SendDiff(value - 0); // intentionally flipped + value = 0; + } + } + + public void SyncState(Serializer ser) + { + ser.Sync("V", ref V); + ser.Sync("D", ref D); + ser.Sync("F", ref F); + ser.Sync("E", ref E); + ser.Sync("count", ref count); + ser.Sync("duty", ref duty); + ser.Sync("value", ref value); + } + + public void Write0(byte value) + { + V = value & 15; + if (value.Bit(7)) + D = 16; + else + D = value >> 4 & 7; + SendNew(); // this actually happens, right? + } + public void Write1(byte value) + { + F &= 0xf00; + F |= value; + } + public void Write2(byte value) + { + F &= 0x0ff; + F |= value << 8 & 0xf00; + E = value.Bit(7); + if (E) + SendNew(); + else + SendNewZero(); + } + + public void Clock() + { + if (!E) + return; + count--; + if (count <= 0) + { + count = F; + duty--; + if (duty < 0) + duty += 16; + SendNew(); + } + } + } + + } +}