From 966495cbaef7a5b90edda9e4c7334c65379bf8dc Mon Sep 17 00:00:00 2001 From: goyuken Date: Mon, 22 Oct 2012 16:10:19 +0000 Subject: [PATCH] fds audio channel. completely untested --- BizHawk.Emulation/BizHawk.Emulation.csproj | 1 + .../Consoles/Nintendo/NES/FDS/FDS.cs | 25 +- .../Consoles/Nintendo/NES/FDS/FDSAudio.cs | 281 ++++++++++++++++++ 3 files changed, 304 insertions(+), 3 deletions(-) create mode 100644 BizHawk.Emulation/Consoles/Nintendo/NES/FDS/FDSAudio.cs diff --git a/BizHawk.Emulation/BizHawk.Emulation.csproj b/BizHawk.Emulation/BizHawk.Emulation.csproj index fe1219f20f..229d2f8aba 100644 --- a/BizHawk.Emulation/BizHawk.Emulation.csproj +++ b/BizHawk.Emulation/BizHawk.Emulation.csproj @@ -269,6 +269,7 @@ + diff --git a/BizHawk.Emulation/Consoles/Nintendo/NES/FDS/FDS.cs b/BizHawk.Emulation/Consoles/Nintendo/NES/FDS/FDS.cs index 5f7c4a48d9..7f9d8704d8 100644 --- a/BizHawk.Emulation/Consoles/Nintendo/NES/FDS/FDS.cs +++ b/BizHawk.Emulation/Consoles/Nintendo/NES/FDS/FDS.cs @@ -24,8 +24,9 @@ namespace BizHawk.Emulation.Consoles.Nintendo /// public byte[] diskimage; - RamAdapter diskdrive; + FDSAudio audio; + int audioclock; // as we have [INESBoardImplCancel], this will only be called with an fds disk image public override bool Configure(NES.EDetectionOrigin origin) @@ -40,6 +41,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo Cart.board_type = "FAMICOM_DISK_SYSTEM"; diskdrive = new RamAdapter(); + audio = new FDSAudio(); InsertSide(0); @@ -89,9 +91,14 @@ namespace BizHawk.Emulation.Consoles.Nintendo public override void WriteEXP(int addr, byte value) { - Console.WriteLine("W{0:x4}:{1:x2} {2:x4}", addr + 0x4000, value, NES.cpu.PC); + if (addr >= 0x0040) + { + audio.WriteReg(addr + 0x4000, value); + return; + } + switch (addr) { case 0x0020: @@ -133,6 +140,9 @@ namespace BizHawk.Emulation.Consoles.Nintendo { byte ret = NES.DB; + if (addr >= 0x0040) + return audio.ReadReg(addr + 0x4000, ret); + switch (addr) { case 0x0030: @@ -194,6 +204,12 @@ namespace BizHawk.Emulation.Consoles.Nintendo } diskdrive.Clock(); diskirq = diskdrive.irq; + audioclock++; + if (audioclock == 3) + { + audioclock = 0; + audio.Clock(); + } } public override byte ReadWRAM(int addr) @@ -220,6 +236,9 @@ namespace BizHawk.Emulation.Consoles.Nintendo WRAM[addr + 0x2000] = value; } - + public override void ApplyCustomAudio(short[] samples) + { + audio.ApplyCustomAudio(samples); + } } } diff --git a/BizHawk.Emulation/Consoles/Nintendo/NES/FDS/FDSAudio.cs b/BizHawk.Emulation/Consoles/Nintendo/NES/FDS/FDSAudio.cs new file mode 100644 index 0000000000..e1b324ca64 --- /dev/null +++ b/BizHawk.Emulation/Consoles/Nintendo/NES/FDS/FDSAudio.cs @@ -0,0 +1,281 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace BizHawk.Emulation.Consoles.Nintendo +{ + // http://wiki.nesdev.com/w/index.php/FDS_audio + public class FDSAudio + { + //4040:407f + byte[] waveram = new byte[64]; + int waverampos; + //4080 + int volumespd; + bool r4080_6; + bool r4080_7; + //4082:4083 + int frequency; + bool r4083_6; + bool r4083_7; + //4084 + int sweepspd; + bool r4084_6; + bool r4084_7; + //4085 + int sweepbias; + //4086:4087 + int modfreq; + bool r4087_7; + //4088 + byte[] modtable = new byte[64]; + int modtablepos; + //4089 + int mastervol_num; + int mastervol_den; + bool waveram_writeenable; + //408a + int envspeed; + + int volumeclock; + int sweepclock; + int modclock; + int mainclock; + + int modoutput; + + int volumegain; + int sweepgain; + + int waveramoutput; + + int latchedoutput; + + /// + /// enough room to hold roughly one frame of final output, 0-2047 + /// + short[] samplebuff = new short[32768]; + int samplebuffpos = 0; + + void CalcMod() + { + // really don't quite get this... + int tmp = sweepbias * sweepgain; + if ((tmp & 0xf) != 0) + { + tmp /= 16; + if (sweepbias < 0) + tmp -= 1; + else + tmp += 2; + } + else + tmp /= 16; + + if (tmp > 193) + tmp -= 258; + else if (tmp < -64) + tmp += 256; + + modoutput = frequency * tmp / 64; + } + + void CalcOut() + { + int tmp = volumegain < 32 ? volumegain : 32; + tmp *= waveramoutput; + tmp *= mastervol_num; + tmp /= mastervol_den; + latchedoutput = tmp; + } + + /// + /// ~1.7mhz + /// + public void Clock() + { + // volume envelope unit + if (!r4080_7 && envspeed > 0 && !r4080_6) + { + volumeclock++; + if (volumeclock >= 8 * envspeed * (volumespd + 1)) + { + volumeclock = 0; + if (r4080_6 && volumegain < 32) + volumegain++; + else if (!r4080_6 && volumegain > 0) + volumegain--; + CalcOut(); + } + } + // sweep unit + if (!r4084_7 && envspeed > 0 && !r4083_6) + { + sweepclock++; + if (sweepclock >= 8 * envspeed * (sweepspd + 1)) + { + sweepclock = 0; + if (r4084_6 && sweepgain < 32) + sweepgain++; + else if (!r4084_6 && sweepgain > 0) + sweepgain--; + CalcMod(); + } + } + // modulation unit + if (!r4087_7 && modfreq > 0) + { + modclock += modfreq; + if (modclock >= 0x10000) + { + modclock -= 0x10000; + // our modtable is really twice as big (64 entries) + switch (modtable[modtablepos++]) + { + case 0: sweepbias += 0; break; + case 1: sweepbias += 1; break; + case 2: sweepbias += 2; break; + case 3: sweepbias += 4; break; + case 4: sweepbias = 0; break; + case 5: sweepbias -= 4; break; + case 6: sweepbias -= 2; break; + case 7: sweepbias -= 1; break; + } + sweepbias &= 0x7f; + modtablepos &= 63; + CalcMod(); + } + } + // main unit + if (!r4083_7 && frequency > 0 && frequency + modoutput > 0 && !waveram_writeenable) + { + mainclock += frequency + modoutput; + if (mainclock >= 0x10000) + { + mainclock -= 0x10000; + waveramoutput = waveram[waverampos++]; + waverampos &= 63; + CalcOut(); + } + } + samplebuff[samplebuffpos++] = (short)latchedoutput; + // if for some reason ApplyCustomAudio() is not called, glitch up but don't crash + samplebuffpos &= 32767; + } + + public void WriteReg(int addr, byte value) + { + if (addr < 0x4080) + { + if (waveram_writeenable) + // can waverampos ever be reset? + waveram[addr - 0x4040] = (byte)(value & 63); + return; + } + switch (addr) + { + case 0x4080: + r4080_6 = (value & 0x40) != 0; + r4080_7 = (value & 0x80) != 0; + if (r4080_7) // envelope is off, so written value gets sent to gain directly + volumegain = value & 63; + else // envelope is on; written value is speed of change + volumespd = value & 63; + break; + case 0x4082: + frequency &= 0xf00; + frequency |= value; + break; + case 0x4083: + frequency &= 0x0ff; + frequency |= value << 8 & 0xf00; + r4083_6 = (value & 0x40) != 0; + r4083_7 = (value & 0x80) != 0; + break; + case 0x4084: + sweepspd = value & 63; + r4084_6 = (value & 0x40) != 0; + r4084_7 = (value & 0x80) != 0; + break; + case 0x4085: + modtablepos = 0; // reset + sweepbias = value & 0x7f; + // sign extend + sweepbias <<= 25; + sweepbias >>= 25; + break; + case 0x4086: + modfreq &= 0xf00; + modfreq |= value; + if (r4087_7 || modfreq == 0) // when mod unit is disabled, mod output is fixed to 0, not hanging + modoutput = 0; + break; + case 0x4087: + modfreq &= 0x0ff; + modfreq |= value << 8 & 0xf00; + r4087_7 = (value & 0x80) != 0; + if (r4087_7 || modfreq == 0) // when mod unit is disabled, mod output is fixed to 0, not hanging + modoutput = 0; + break; + case 0x4088: + // write twice into virtual 64 unit buffer + Buffer.BlockCopy(modtable, 2, modtable, 0, 62); + modtable[62] = (byte)(value & 7); + modtable[63] = (byte)(value & 7); + break; + case 0x4089: + switch (value & 3) + { + case 0: mastervol_num = 1; mastervol_den = 1; break; + case 1: mastervol_num = 2; mastervol_den = 3; break; + case 2: mastervol_num = 2; mastervol_den = 4; break; + case 3: mastervol_num = 2; mastervol_den = 5; break; + } + waveram_writeenable = (value & 0x80) != 0; + break; + case 0x408a: + envspeed = value; + break; + } + } + + public byte ReadReg(int addr, byte openbus) + { + byte ret = openbus; + + if (addr < 0x4080) + { + ret &= 0xc0; + ret |= waveram[addr - 0x4040]; + } + else if (addr == 0x4090) + { + ret &= 0xc0; + ret |= (byte)volumegain; + } + else if (addr == 0x4092) + { + ret &= 0xc0; + ret |= (byte)sweepgain; + } + return ret; + } + + public void ApplyCustomAudio(short[] samples) + { + for (int i = 0; i < samples.Length; i += 2) + { + int pos = i * samplebuffpos / samples.Length; + + short samp = (short)(samplebuff[pos] * 20 - 20160); + // nothing for clipping, and the worst imaginable resampling + samples[i] += samp; + samples[i + 1] += samp; + + } + Console.WriteLine("##{0}##", samplebuffpos); + samplebuffpos = 0; + } + } +}