diff --git a/BizHawk.Emulation/BizHawk.Emulation.csproj b/BizHawk.Emulation/BizHawk.Emulation.csproj
index 1d57848153..5b8f4d6330 100644
--- a/BizHawk.Emulation/BizHawk.Emulation.csproj
+++ b/BizHawk.Emulation/BizHawk.Emulation.csproj
@@ -441,6 +441,7 @@
+
diff --git a/BizHawk.Emulation/Consoles/Nintendo/NES/Boards/ExROM.cs b/BizHawk.Emulation/Consoles/Nintendo/NES/Boards/ExROM.cs
index 24c2be1726..fd113563f1 100644
--- a/BizHawk.Emulation/Consoles/Nintendo/NES/Boards/ExROM.cs
+++ b/BizHawk.Emulation/Consoles/Nintendo/NES/Boards/ExROM.cs
@@ -37,12 +37,14 @@ namespace BizHawk.Emulation.Consoles.Nintendo
int wram_bank;
byte[] EXRAM = new byte[1024];
byte multiplicand, multiplier;
+ Sound.MMC5Audio audio;
//regeneratable state
IntBuffer a_banks_1k = new IntBuffer(8);
IntBuffer b_banks_1k = new IntBuffer(8);
IntBuffer prg_banks_8k = new IntBuffer(4);
byte product_low, product_high;
int last_nt_read;
+ bool irq_audio;
public MemoryDomain GetExRAM()
{
@@ -76,6 +78,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo
SyncCHRBanks();
SyncMultiplier();
SyncIRQ();
+ audio.SyncState(ser);
}
public override void Dispose()
@@ -111,6 +114,9 @@ namespace BizHawk.Emulation.Consoles.Nintendo
PoweronState();
+ if (NES.apu != null)
+ audio = new Sound.MMC5Audio(NES.apu.ExternalQueue, (e) => { irq_audio = e; SyncIRQ(); });
+
return true;
}
@@ -320,6 +326,11 @@ namespace BizHawk.Emulation.Consoles.Nintendo
public override void WriteEXP(int addr, byte value)
{
//NES.LogLine("MMC5 WriteEXP: ${0:x4} = ${1:x2}", addr, value);
+ if (addr >= 0x1000 && addr <= 0x1015)
+ {
+ audio.WriteExp(addr + 0x4000, value);
+ return;
+ }
switch (addr)
{
case 0x1100: //$5100: [.... ..PP] PRG Mode Select:
@@ -440,6 +451,14 @@ namespace BizHawk.Emulation.Consoles.Nintendo
case 0x1206: //$5206: high 8 bits of product
ret = product_high;
break;
+
+ case 0x1015: // $5015: apu status
+ ret = audio.Read5015();
+ break;
+
+ case 0x1010: // $5010: apu PCM
+ ret = audio.Read5010();
+ break;
}
//TODO - additional r/w timing security
@@ -455,7 +474,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo
void SyncIRQ()
{
- IRQSignal = (irq_pending && irq_enabled);
+ IRQSignal = (irq_pending && irq_enabled) || irq_audio;
}
public override void ClockPPU()
@@ -493,6 +512,11 @@ namespace BizHawk.Emulation.Consoles.Nintendo
}
+ public override void ClockCPU()
+ {
+ audio.Clock();
+ }
+
void SetBank(IntBuffer target, int offset, int size, int value)
{
value &= ~(size-1);
diff --git a/BizHawk.Emulation/Sound/MMC5Audio.cs b/BizHawk.Emulation/Sound/MMC5Audio.cs
new file mode 100644
index 0000000000..6152bb658e
--- /dev/null
+++ b/BizHawk.Emulation/Sound/MMC5Audio.cs
@@ -0,0 +1,306 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace BizHawk.Emulation.Sound
+{
+ public class MMC5Audio
+ {
+ class Pulse
+ {
+ // regs
+ int V;
+ int T;
+ int L;
+ int D;
+ bool LenCntDisable;
+ bool ConstantVolume;
+ bool Enable;
+ // envelope
+ bool estart;
+ int etime;
+ int ecount;
+ // length
+ static int[] lenlookup =
+ {
+ 10,254, 20, 2, 40, 4, 80, 6, 160, 8, 60, 10, 14, 12, 26, 14,
+ 12, 16, 24, 18, 48, 20, 96, 22, 192, 24, 72, 26, 16, 28, 32, 30
+ };
+ int length;
+
+ // pulse
+ int sequence;
+ static int[,] sequencelookup =
+ {
+ {0,0,0,0,0,0,0,1},
+ {0,0,0,0,0,0,1,1},
+ {0,0,0,0,1,1,1,1},
+ {1,1,1,1,1,1,0,0}
+ };
+ int clock;
+ int output;
+
+ public Action SendDiff;
+
+ public Pulse(Action SendDiff)
+ {
+ this.SendDiff = SendDiff;
+ }
+
+ public void SyncState(Serializer ser)
+ {
+ ser.Sync("V", ref V);
+ ser.Sync("T", ref T);
+ ser.Sync("L", ref L);
+ ser.Sync("D", ref D);
+ ser.Sync("LenCntDisable", ref LenCntDisable);
+ ser.Sync("ConstantVolume", ref ConstantVolume);
+ ser.Sync("Enable", ref Enable);
+ ser.Sync("estart", ref estart);
+ ser.Sync("etime", ref etime);
+ ser.Sync("ecount", ref ecount);
+ ser.Sync("length", ref length);
+ ser.Sync("sequence", ref sequence);
+ ser.Sync("clock", ref clock);
+ ser.Sync("output", ref output);
+ }
+
+ public void Write0(byte val)
+ {
+ V = val & 15;
+ ConstantVolume = val.Bit(4);
+ LenCntDisable = val.Bit(5);
+ D = val >> 6;
+ }
+ public void Write2(byte val)
+ {
+ T &= 0x700;
+ T |= val;
+ }
+ public void Write3(byte val)
+ {
+ T &= 0xff;
+ T |= val << 8 & 0x700;
+ L = val >> 3;
+ estart = true;
+ if (Enable)
+ length = lenlookup[L];
+ sequence = 0;
+ }
+ public void SetEnable(bool val)
+ {
+ Enable = val;
+ if (!Enable)
+ length = 0;
+ }
+ public bool ReadLength()
+ {
+ return length > 0;
+ }
+
+ public void ClockFrame()
+ {
+ // envelope
+ if (estart)
+ {
+ estart = false;
+ ecount = 15;
+ etime = V;
+ }
+ else
+ {
+ etime--;
+ if (etime < 0)
+ {
+ etime = V;
+ if (ecount > 0)
+ {
+ ecount--;
+ }
+ else if (LenCntDisable)
+ {
+ ecount = 15;
+ }
+ }
+ }
+ // length
+ if (Enable && !LenCntDisable && length > 0)
+ {
+ length--;
+ }
+ }
+
+ public void Clock()
+ {
+ clock--;
+ if (clock < 0)
+ {
+ clock = T * 2;
+ sequence--;
+ if (sequence < 0)
+ sequence += 8;
+
+ int sequenceval = sequencelookup[D, sequence];
+
+ int newvol = 0;
+
+ if (sequenceval > 0 && length > 0)
+ {
+ if (ConstantVolume)
+ newvol = V;
+ else
+ newvol = ecount;
+ }
+
+ if (newvol != output)
+ {
+ //Console.WriteLine("{0},{1}", newvol, output);
+ SendDiff(output - newvol);
+ output = newvol;
+ }
+ }
+ }
+ }
+
+ Pulse[] pulse = new Pulse[2];
+
+ ///
+ ///
+ ///
+ /// 0x5000..0x5015
+ ///
+ public void WriteExp(int addr, byte val)
+ {
+ switch (addr)
+ {
+ case 0x5000: pulse[0].Write0(val); break;
+ case 0x5002: pulse[0].Write2(val); break;
+ case 0x5003: pulse[0].Write3(val); break;
+ case 0x5004: pulse[1].Write0(val); break;
+ case 0x5006: pulse[1].Write2(val); break;
+ case 0x5007: pulse[1].Write3(val); break;
+ case 0x5010: // pcm mode/irq
+ PCMRead = val.Bit(0);
+ PCMEnableIRQ = val.Bit(7);
+ RaiseIRQ(PCMEnableIRQ && PCMIRQTriggered);
+ break;
+ case 0x5011: // PCM value
+ if (!PCMRead)
+ WritePCM(val);
+ break;
+ case 0x5015:
+ pulse[0].SetEnable(val.Bit(0));
+ pulse[1].SetEnable(val.Bit(1));
+ break;
+ }
+ }
+
+ public byte Read5015()
+ {
+ byte ret = 0;
+ if (pulse[0].ReadLength())
+ ret |= 1;
+ if (pulse[1].ReadLength())
+ ret |= 2;
+ return ret;
+ }
+
+ public byte Read5010()
+ {
+ byte ret = 0;
+ if (PCMEnableIRQ && PCMIRQTriggered)
+ {
+ ret |= 0x80;
+ }
+ PCMIRQTriggered = false; // ack
+ RaiseIRQ(PCMEnableIRQ && PCMIRQTriggered);
+ return ret;
+ }
+
+ ///
+ /// call for 8000:bfff reads
+ ///
+ ///
+ public void ReadROMTrigger(byte val)
+ {
+ if (PCMRead)
+ WritePCM(val);
+ }
+
+ void WritePCM(byte val)
+ {
+ if (val == 0)
+ {
+ PCMIRQTriggered = true;
+ }
+ else
+ {
+ PCMIRQTriggered = false;
+ // can't set diff here, because APU cycle clock might be wrong
+ PCMNextVal = val;
+ }
+ RaiseIRQ(PCMEnableIRQ && PCMIRQTriggered);
+ }
+
+ Action RaiseIRQ;
+
+ const int framereload = 7458; // ???
+ int frame = 0;
+ bool PCMRead;
+ bool PCMEnableIRQ;
+ bool PCMIRQTriggered;
+ byte PCMVal;
+ byte PCMNextVal;
+
+ public void SyncState(Serializer ser)
+ {
+ ser.BeginSection("MMC5Audio");
+ ser.Sync("frame", ref frame);
+ pulse[0].SyncState(ser);
+ pulse[1].SyncState(ser);
+ ser.Sync("PCMRead", ref PCMRead);
+ ser.Sync("PCMEnableIRQ", ref PCMEnableIRQ);
+ ser.Sync("PCMIRQTriggered", ref PCMIRQTriggered);
+ ser.Sync("PCMVal", ref PCMVal);
+ ser.Sync("PCMNextVal", ref PCMNextVal);
+ ser.EndSection();
+ if (ser.IsReader)
+ RaiseIRQ(PCMEnableIRQ && PCMIRQTriggered);
+ }
+
+ public void Clock()
+ {
+ pulse[0].Clock();
+ pulse[1].Clock();
+ frame++;
+ if (frame == framereload)
+ {
+ frame = 0;
+ pulse[0].ClockFrame();
+ pulse[1].ClockFrame();
+ }
+ if (PCMNextVal != PCMVal)
+ {
+ enqueuer(20 * (int)(PCMVal - PCMNextVal));
+ PCMVal = PCMNextVal;
+ }
+ }
+
+ Action enqueuer;
+
+ void PulseAddDiff(int value)
+ {
+ enqueuer(value * 370);
+ //Console.WriteLine(value);
+ }
+
+ public MMC5Audio(Action enqueuer, Action RaiseIRQ)
+ {
+ this.enqueuer = enqueuer;
+ this.RaiseIRQ = RaiseIRQ;
+ for (int i = 0; i < pulse.Length; i++)
+ pulse[i] = new Pulse(PulseAddDiff);
+ }
+ }
+}