diff --git a/BizHawk.Emulation/BizHawk.Emulation.csproj b/BizHawk.Emulation/BizHawk.Emulation.csproj
index 42098b7d69..43ffeb3499 100644
--- a/BizHawk.Emulation/BizHawk.Emulation.csproj
+++ b/BizHawk.Emulation/BizHawk.Emulation.csproj
@@ -302,6 +302,7 @@
+
Code
diff --git a/BizHawk.Emulation/Consoles/Nintendo/NES/Boards/NAMCOT_m19_m210.cs b/BizHawk.Emulation/Consoles/Nintendo/NES/Boards/NAMCOT_m19_m210.cs
index 6e40ace7db..f42b5b70be 100644
--- a/BizHawk.Emulation/Consoles/Nintendo/NES/Boards/NAMCOT_m19_m210.cs
+++ b/BizHawk.Emulation/Consoles/Nintendo/NES/Boards/NAMCOT_m19_m210.cs
@@ -26,12 +26,20 @@ namespace BizHawk.Emulation.Consoles.Nintendo
int irq_cycles;
bool irq_pending;
+ Namco163Audio audio;
+ int audio_cycles;
+
public override void Dispose()
{
base.Dispose();
prg_banks_8k.Dispose();
chr_banks_1k.Dispose();
nt_banks_1k.Dispose();
+ if (audio != null)
+ {
+ audio.Dispose();
+ audio = null;
+ }
}
public override void SyncState(Serializer ser)
@@ -40,12 +48,18 @@ namespace BizHawk.Emulation.Consoles.Nintendo
ser.Sync("prg_banks_8k", ref prg_banks_8k);
ser.Sync("chr_banks_1k", ref chr_banks_1k);
ser.Sync("nt_banks_1k", ref nt_banks_1k);
- for(int i=0;i<2;i++) ser.Sync("vram_enable_" + i, ref vram_enable[i]);
+ for (int i = 0; i < 2; i++)
+ ser.Sync("vram_enable_" + i, ref vram_enable[i]);
ser.Sync("irq_counter", ref irq_counter);
ser.Sync("irq_enabled", ref irq_enabled);
ser.Sync("irq_cycles", ref irq_cycles);
ser.Sync("irq_pending", ref irq_pending);
SyncIRQ();
+ if (audio != null)
+ {
+ ser.Sync("audio_cycles", ref audio_cycles);
+ audio.SyncState(ser);
+ }
}
public override bool Configure(NES.EDetectionOrigin origin)
@@ -66,6 +80,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo
//hydelide 3 *this is a good test of more advanced features
Cart.vram_size = 8; //not many test cases of this, but hydelide 3 needs it.
AssertPrg(128,256); AssertChr(128,256); AssertVram(8); AssertWram(0,8);
+ audio = new Namco163Audio();
break;
//mapper 210:
@@ -99,6 +114,11 @@ namespace BizHawk.Emulation.Consoles.Nintendo
addr &= 0xF800;
switch (addr)
{
+ case 0x0800:
+ if (audio != null)
+ return audio.ReadData();
+ else
+ break;
case 0x1000:
return (byte)(irq_counter & 0xFF);
case 0x1800:
@@ -113,7 +133,8 @@ namespace BizHawk.Emulation.Consoles.Nintendo
switch (addr)
{
case 0x0800:
- //sound data port. not used.
+ if (audio != null)
+ audio.WriteData(value);
break;
case 0x1000:
irq_counter = (irq_counter & 0xFF00) | value;
@@ -175,6 +196,10 @@ namespace BizHawk.Emulation.Consoles.Nintendo
case 0x7000: //$F000
prg_banks_8k[2] = (value & 0x3F) & prg_bank_mask_8k;
break;
+ case 0x7800: //$F800
+ if (audio != null)
+ audio.WriteAddr(value);
+ break;
}
}
@@ -279,15 +304,30 @@ namespace BizHawk.Emulation.Consoles.Nintendo
public override void ClockPPU()
{
- if (!irq_enabled) return;
-
- irq_cycles--;
- if (irq_cycles == 0)
+ if (irq_enabled)
{
- irq_cycles += 3;
- ClockIRQ();
+ irq_cycles--;
+ if (irq_cycles == 0)
+ {
+ irq_cycles += 3;
+ ClockIRQ();
+ }
+ }
+ if (audio != null)
+ {
+ audio_cycles++;
+ if (audio_cycles == 15 * 3)
+ {
+ audio_cycles = 0;
+ audio.Clock();
+ }
}
}
+ public override void ApplyCustomAudio(short[] samples)
+ {
+ if (audio != null)
+ audio.ApplyCustomAudio(samples);
+ }
}
}
\ No newline at end of file
diff --git a/BizHawk.Emulation/Consoles/Nintendo/NES/Boards/Namcot1xx/Namco163Audio.cs b/BizHawk.Emulation/Consoles/Nintendo/NES/Boards/Namcot1xx/Namco163Audio.cs
new file mode 100644
index 0000000000..f1db069855
--- /dev/null
+++ b/BizHawk.Emulation/Consoles/Nintendo/NES/Boards/Namcot1xx/Namco163Audio.cs
@@ -0,0 +1,179 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace BizHawk.Emulation.Consoles.Nintendo
+{
+ // http://wiki.nesdev.com/w/index.php/Namco_163_audio
+ public class Namco163Audio : IDisposable
+ {
+ //ByteBuffer ram = new ByteBuffer(0x80);
+ byte[] ram = new byte[0x80];
+ int addr;
+ bool autoincrement;
+
+ public void Dispose()
+ {
+ //ram.Dispose();
+ ram = null;
+ resampler.Dispose();
+ resampler = null;
+ }
+
+ ///
+ /// F800:FFFF
+ ///
+ ///
+ public void WriteAddr(byte value)
+ {
+ addr = value & 0x7f;
+ autoincrement = (value & 0x80) != 0;
+ }
+
+ ///
+ /// 4800:4FFF
+ ///
+ ///
+ public void WriteData(byte value)
+ {
+ ram[addr] = value;
+ if (autoincrement)
+ {
+ addr++;
+ addr &= 0x7f;
+ }
+ }
+
+ ///
+ /// 4800:4FFF
+ ///
+ ///
+ public byte ReadData()
+ {
+ byte ret = ram[addr];
+ if (autoincrement)
+ {
+ addr++;
+ addr &= 0x7f;
+ }
+ return ret;
+ }
+
+ ///
+ /// last channel clocked
+ ///
+ int ch;
+
+ // output buffer; not savestated
+ //short[] samplebuff = new short[2048];
+ //int samplebuffpos;
+
+ ///
+ /// 119318hz (CPU / 15)
+ ///
+ public void Clock()
+ {
+ ch--;
+ int lastch = 8 - (ram[0x7f] >> 4 & 7);
+ if (ch < lastch)
+ ch = 8;
+
+ byte samp = ClockChannel(ch);
+
+ //samplebuff[samplebuffpos++] = samp;
+ //samplebuffpos &= 2047;
+ short ss = (short)(samp * 50 - 12096);
+ resampler.EnqueueSample(ss, ss);
+ }
+
+ byte ClockChannel(int ch)
+ {
+ // channel regs are at [b..b+7]
+ int b = ch * 8 + 56;
+
+ // not that bad really, and best to do exactly as specified because
+ // the results (phase) can be read back
+ int phase = ram[b + 1] | ram[b + 3] << 8 | ram[b + 5] << 16;
+ int freq = ram[b] | ram[b + 2] << 8 | ram[b + 4] << 16 & 0x030000;
+ int length = 256 - (ram[b + 4] & 0xfc);
+ phase = (phase + freq) % (length << 16);
+
+ int pos = (phase >> 16) + ram[b + 6];
+ pos &= 0xff;
+ int sample = ram[pos / 2] >> (pos & 1) * 4 & 0xf;
+ byte ret = (byte)(sample * (ram[b + 7] & 0x0f));
+ // writeback phase
+ ram[b + 5] = (byte)(phase >> 16);
+ ram[b + 3] = (byte)(phase >> 8);
+ ram[b + 1] = (byte)phase;
+ return ret;
+ }
+
+ public void SyncState(Serializer ser)
+ {
+ ser.Sync("ram", ref ram, false);
+ ser.Sync("addr", ref addr);
+ ser.Sync("autoincrement", ref autoincrement);
+ ser.Sync("ch", ref ch);
+ }
+
+ Sound.Utilities.SpeexResampler resampler;
+ Sound.Utilities.DCFilter dc;
+ Sound.MetaspuAsync metaspu;
+
+ public Namco163Audio()
+ {
+ resampler = new Sound.Utilities.SpeexResampler(2, 119318, 44100, 119318, 44100, null, null);
+ dc = new Sound.Utilities.DCFilter(4096);
+ metaspu = new Sound.MetaspuAsync(resampler, Sound.ESynchMethod.ESynchMethod_V);
+ }
+
+ public void ApplyCustomAudio(short[] samples)
+ {
+ short[] tmp = new short[samples.Length];
+ metaspu.GetSamples(tmp);
+ for (int i = 0; i < samples.Length; i++)
+ {
+ int samp = samples[i] + tmp[i];
+ if (samp > 32767)
+ samples[i] = 32767;
+ else if (samp < -32768)
+ samples[i] = -32768;
+ else
+ samples[i] = (short)samp;
+ }
+ dc.PushThroughSamples(samples, samples.Length);
+ }
+
+ // the same junk used in FDSAudio
+ // the problem here is, the raw 120khz output contains significant amounts of crap that gets
+ // massively garbaged up by this resampling
+ /*
+ public void ApplyCustomAudio(short[] samples)
+ {
+ for (int i = 0; i < samples.Length; i += 2)
+ {
+ // worst imaginable resampling
+ int pos = i * samplebuffpos / samples.Length;
+ int samp = samplebuff[pos] * 50 - 12096;
+ samp += samples[i];
+ if (samp > 32767)
+ samples[i] = 32767;
+ else if (samp < -32768)
+ samples[i] = -32768;
+ else
+ samples[i] = (short)samp;
+
+ // NES audio is mono, so this should be identical anyway
+ samples[i + 1] = samples[i];
+ }
+ samplebuffpos = 0;
+
+ dc.PushThroughSamples(samples, samples.Length);
+ }
+ */
+
+
+ }
+}