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