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();
+ }
+ }
+ }
+
+ }
+}