diff --git a/BizHawk.Emulation/BizHawk.Emulation.csproj b/BizHawk.Emulation/BizHawk.Emulation.csproj
index a90d20e13d..47679139e5 100644
--- a/BizHawk.Emulation/BizHawk.Emulation.csproj
+++ b/BizHawk.Emulation/BizHawk.Emulation.csproj
@@ -351,6 +351,7 @@
+
diff --git a/BizHawk.Emulation/Consoles/Nintendo/NES/Boards/VRC6.cs b/BizHawk.Emulation/Consoles/Nintendo/NES/Boards/VRC6.cs
index 5e86e0d3f0..cbada755eb 100644
--- a/BizHawk.Emulation/Consoles/Nintendo/NES/Boards/VRC6.cs
+++ b/BizHawk.Emulation/Consoles/Nintendo/NES/Boards/VRC6.cs
@@ -12,6 +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();
+
//state
int prg_bank_16k, prg_bank_8k;
ByteBuffer prg_banks_8k = new ByteBuffer(4);
@@ -32,6 +34,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo
public override void SyncState(Serializer ser)
{
base.SyncState(ser);
+ VRC6Sound.SyncState(ser);
ser.Sync("prg_bank_16k", ref prg_bank_16k);
ser.Sync("prg_bank_8k", ref prg_bank_8k);
ser.Sync("chr_banks_1k", ref chr_banks_1k);
@@ -132,21 +135,33 @@ namespace BizHawk.Emulation.Consoles.Nintendo
break;
case 0x1000: //$9000
+ VRC6Sound.Write9000(value);
+ break;
case 0x1001: //$9001
+ VRC6Sound.Write9000(value);
+ break;
case 0x1002: //$9002
- //TODO pulse 1
+ VRC6Sound.Write9002(value);
break;
case 0x2000: //$A000
+ VRC6Sound.WriteA000(value);
+ break;
case 0x2001: //$A001
+ VRC6Sound.WriteA001(value);
+ break;
case 0x2002: //$A002
- //TODO pulse 2
+ VRC6Sound.WriteA002(value);
break;
case 0x3000: //$B000
+ VRC6Sound.WriteB000(value);
+ break;
case 0x3001: //$B001
+ VRC6Sound.WriteB001(value);
+ break;
case 0x3002: //$B002
- //TODO sawtooth
+ VRC6Sound.WriteB002(value);
break;
case 0x3003: //$B003
@@ -247,5 +262,16 @@ namespace BizHawk.Emulation.Consoles.Nintendo
}
}
+ public override void ApplyCustomAudio(short[] samples)
+ {
+ short[] fmsamples = new short[samples.Length];
+ VRC6Sound.GetSamples(fmsamples);
+ int len = samples.Length;
+ for (int i = 0; i < len; i++)
+ {
+ samples[i] = (short)((samples[i] >> 1) + (fmsamples[i] >> 1));
+ }
+ }
+
}
}
\ No newline at end of file
diff --git a/BizHawk.Emulation/Sound/VRC6.cs b/BizHawk.Emulation/Sound/VRC6.cs
new file mode 100644
index 0000000000..011ae148db
--- /dev/null
+++ b/BizHawk.Emulation/Sound/VRC6.cs
@@ -0,0 +1,337 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace BizHawk.Emulation.Sound
+{
+ class VRC6 : ISoundProvider
+ {
+ public void DiscardSamples() { }
+ public int MaxVolume { get; set; }
+ public void GetSamples(short[] samples)
+ {
+ for (int i = 0; i < samples.Length; )
+ {
+ short val = 0;
+ val = Pulse1.RenderSample();
+ val *= Pulse2.RenderSample();
+ val *= Sawtooth.RenderSample();
+ val = (short)((val * MaxVolume) / short.MaxValue);
+
+ samples[i++] = val;
+ samples[i++] = val;
+ }
+ }
+
+ public VRC6()
+ {
+ MaxVolume = (short.MaxValue / 2);
+ }
+
+ public void SyncState(Serializer ser)
+ {
+ Pulse1.SyncState(ser);
+ Pulse2.SyncState(ser);
+ Sawtooth.SyncState(ser);
+ }
+
+ public void Write9000(byte value)
+ {
+ Pulse1.Write9000(value);
+ }
+
+ public void Write9001(byte value)
+ {
+ Pulse1.Write9001(value);
+ }
+
+ public void Write9002(byte value)
+ {
+ Pulse1.Write9002(value);
+ }
+
+ public void WriteA000(byte value)
+ {
+ Pulse2.WriteA000(value);
+ }
+
+ public void WriteA001(byte value)
+ {
+ Pulse2.WriteA001(value);
+ }
+
+ public void WriteA002(byte value)
+ {
+ Pulse2.WriteA002(value);
+ }
+
+ public void WriteB000(byte value)
+ {
+ Sawtooth.WriteB000(value);
+ }
+
+ public void WriteB001(byte value)
+ {
+ Sawtooth.WriteB001(value);
+ }
+
+ public void WriteB002(byte value)
+ {
+ Sawtooth.WriteB002(value);
+ }
+
+ private Chn_VRC6Pulse1 Pulse1 = new Chn_VRC6Pulse1();
+ private Chn_VRC6Pulse2 Pulse2 = new Chn_VRC6Pulse2();
+ private Chn_VRC6Sawtooth Sawtooth = new Chn_VRC6Sawtooth();
+
+ public class Chn_VRC6Pulse1
+ {
+ byte _Volume = 0;
+ double DutyPercentage = 0;
+ int _DutyCycle = 0;
+ int _FreqTimer = 0;
+ bool _Enabled = false;
+ double _Frequency = 0;
+ double _SampleCount = 0;
+ double _RenderedLength = 0;
+ bool WaveStatus = false;
+ short OUT = 0;
+
+ public void SyncState(Serializer ser)
+ {
+ ser.Sync("_Volume", ref _Volume);
+ //ser.Sync("DutyPercentage", ref DutyPercentage);
+ ser.Sync("_DutyCycle", ref _DutyCycle);
+ ser.Sync("_FreqTimer", ref _FreqTimer);
+ ser.Sync("_Enabled", ref _Enabled);
+ //ser.Sync("_Frequency", ref _Frequency);
+ //ser.Sync("_SampleCount", ref _SampleCount);
+ //ser.Sync("_RenderedLength", ref _RenderedLength);
+ //ser.Sync("WaveStatus", ref WaveStatus);
+ ser.Sync("OUT", ref OUT);
+ }
+
+ public short RenderSample()
+ {
+ if (_Enabled)
+ {
+ _SampleCount++;
+ if (WaveStatus && (_SampleCount > (_RenderedLength * DutyPercentage)))
+ {
+ _SampleCount -= _RenderedLength * DutyPercentage;
+ WaveStatus = !WaveStatus;
+ }
+ else if (!WaveStatus && (_SampleCount > (_RenderedLength * (1.0 - DutyPercentage))))
+ {
+ _SampleCount -= _RenderedLength * (1.0 - DutyPercentage);
+ WaveStatus = !WaveStatus;
+ }
+ if (WaveStatus)
+ OUT = (short)(-_Volume);
+ else
+ OUT = (short)(_Volume);
+
+ return OUT;
+ }
+ return 0;
+ }
+ public void Write9000(byte data)
+ {
+ _Volume = (byte)(data & 0x0F);//Bit 0 - 3
+ _DutyCycle = (data >> 4); //Bit 4 - 7
+ if (_DutyCycle == 0)
+ DutyPercentage = 0.6250;
+ else if (_DutyCycle == 1)
+ DutyPercentage = 0.1250;
+ else if (_DutyCycle == 2)
+ DutyPercentage = 0.1875;
+ else if (_DutyCycle == 3)
+ DutyPercentage = 0.2500;
+ else if (_DutyCycle == 4)
+ DutyPercentage = 0.3125;
+ else if (_DutyCycle == 5)
+ DutyPercentage = 0.3750;
+ else if (_DutyCycle == 6)
+ DutyPercentage = 0.4375;
+ else if (_DutyCycle == 7)
+ DutyPercentage = 0.5000;
+ else
+ DutyPercentage = 1.0;
+ }
+ public void Write9001(byte data)
+ {
+ _FreqTimer = (_FreqTimer & 0x0F00) | data;
+ //Update freq
+ _Frequency = 1790000 / 16 / (_FreqTimer + 1);
+ _RenderedLength = 44100 / _Frequency;
+ }
+ public void Write9002(byte data)
+ {
+ _FreqTimer = (_FreqTimer & 0x00FF) | ((data & 0x0F) << 8);
+ _Enabled = (data & 0x80) != 0;
+ //Update freq
+ _Frequency = 1790000 / 16 / (_FreqTimer + 1);
+ _RenderedLength = 44100 / _Frequency;
+ }
+ }
+
+ public class Chn_VRC6Pulse2
+ {
+ byte _Volume = 0;
+ double DutyPercentage = 0;
+ int _DutyCycle = 0;
+ int _FreqTimer = 0;
+ bool _Enabled = false;
+ double _Frequency = 0;
+ double _SampleCount = 0;
+ double _RenderedLength = 0;
+ bool WaveStatus = false;
+ short OUT = 0;
+
+ public short RenderSample()
+ {
+ if (_Enabled)
+ {
+ _SampleCount++;
+ if (WaveStatus && (_SampleCount > (_RenderedLength * DutyPercentage)))
+ {
+ _SampleCount -= _RenderedLength * DutyPercentage;
+ WaveStatus = !WaveStatus;
+ }
+ else if (!WaveStatus && (_SampleCount > (_RenderedLength * (1.0 - DutyPercentage))))
+ {
+ _SampleCount -= _RenderedLength * (1.0 - DutyPercentage);
+ WaveStatus = !WaveStatus;
+ }
+ if (WaveStatus)
+ OUT = (short)(-_Volume);
+ else
+ OUT = (short)(_Volume);
+
+ return OUT;
+ }
+ return 0;
+ }
+ public void WriteA000(byte data)
+ {
+ _Volume = (byte)(data & 0x0F);//Bit 0 - 3
+ _DutyCycle = (data >> 4); //Bit 4 - 7
+ if (_DutyCycle == 0)
+ DutyPercentage = 0.6250;
+ else if (_DutyCycle == 1)
+ DutyPercentage = 0.1250;
+ else if (_DutyCycle == 2)
+ DutyPercentage = 0.1875;
+ else if (_DutyCycle == 3)
+ DutyPercentage = 0.2500;
+ else if (_DutyCycle == 4)
+ DutyPercentage = 0.3125;
+ else if (_DutyCycle == 5)
+ DutyPercentage = 0.3750;
+ else if (_DutyCycle == 6)
+ DutyPercentage = 0.4375;
+ else if (_DutyCycle == 7)
+ DutyPercentage = 0.5000;
+ else
+ DutyPercentage = 1.0;
+ }
+ public void WriteA001(byte data)
+ {
+ _FreqTimer = (_FreqTimer & 0x0F00) | data;
+ //Update freq
+ _Frequency = 1790000 / 16 / (_FreqTimer + 1);
+ _RenderedLength = 44100 / _Frequency;
+ }
+ public void WriteA002(byte data)
+ {
+ _FreqTimer = (_FreqTimer & 0x00FF) | ((data & 0x0F) << 8);
+ _Enabled = (data & 0x80) != 0;
+ //Update freq
+ _Frequency = 1790000 / 16 / (_FreqTimer + 1);
+ _RenderedLength = 44100 / _Frequency;
+ }
+
+ public void SyncState(Serializer ser)
+ {
+ ser.Sync("_Volume", ref _Volume); //TODO
+ //st.VRC6Pulse2DutyPercentage = DutyPercentage;
+ //st.VRC6Pulse2_DutyCycle = _DutyCycle;
+ //st.VRC6Pulse2_FreqTimer = _FreqTimer;
+ //st.VRC6Pulse2_Enabled = _Enabled;
+ //st.VRC6Pulse2_Frequency = _Frequency;
+ //st.VRC6Pulse2_SampleCount = _SampleCount;
+ //st.VRC6Pulse2_RenderedLength = _RenderedLength;
+ //st.VRC6Pulse2WaveStatus = WaveStatus;
+ //st.VRC6Pulse2OUT = OUT;
+ }
+ }
+
+ public class Chn_VRC6Sawtooth
+ {
+ byte AccumRate = 0;
+ byte AccumStep = 0;
+ byte Accum = 0;
+ int _FreqTimer = 0;
+ bool _Enabled = false;
+ double _Frequency = 0;
+ double _SampleCount = 0;
+ double _RenderedLength = 0;
+ short OUT = 0;
+ public short RenderSample()
+ {
+ if (_Enabled)
+ {
+ _SampleCount++;
+ if (_SampleCount >= _RenderedLength)
+ {
+ _SampleCount -= _RenderedLength;
+ AccumStep++;
+ if ((AccumStep & 2) != 0)
+ Accum += AccumRate;
+ if (AccumStep >= 14)
+ AccumStep = Accum = 0;
+
+ OUT = (short)(Accum >> 3);
+
+ }
+ return (short)((OUT - 5));
+ }
+ return 0;
+ }
+ public void WriteB000(byte data)
+ {
+ AccumRate = (byte)(data & 0x3F);
+ }
+ public void WriteB001(byte data)
+ {
+ _FreqTimer = (_FreqTimer & 0x0F00) | data;
+ //Update freq
+ _Frequency = 1790000 / (_FreqTimer + 1);
+ _RenderedLength = 44100 / _Frequency;
+ }
+ public void WriteB002(byte data)
+ {
+ _FreqTimer = (_FreqTimer & 0x00FF) | ((data & 0x0F) << 8);
+ _Enabled = (data & 0x80) != 0;
+ //Update freq
+ _Frequency = 1790000 / (_FreqTimer + 1);
+ _RenderedLength = 44100 / _Frequency;
+ }
+
+ public void SyncState(Serializer ser)
+ {
+ //ser.Sync("_Volume", ref _Volume); //TODO
+ //st.VRC6Pulse2DutyPercentage = DutyPercentage;
+ //st.VRC6Pulse2_DutyCycle = _DutyCycle;
+ //st.VRC6Pulse2_FreqTimer = _FreqTimer;
+ //st.VRC6Pulse2_Enabled = _Enabled;
+ //st.VRC6Pulse2_Frequency = _Frequency;
+ //st.VRC6Pulse2_SampleCount = _SampleCount;
+ //st.VRC6Pulse2_RenderedLength = _RenderedLength;
+ //st.VRC6Pulse2WaveStatus = WaveStatus;
+ //st.VRC6Pulse2OUT = OUT;
+ }
+ }
+ }
+}