diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index 21cfe969..f80b6074 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -548,6 +548,8 @@ + + diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index 1a35a3cd..9f35d72e 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -68,6 +68,9 @@ {4FC737F1-C7A5-4376-A066-2A32D752A2FF} cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + {0de6642c-1ea6-4872-b21f-2c3009eee9ad} + @@ -97,9 +100,6 @@ Nes\Interfaces - - Nes\Mappers - NetPlay\Messages @@ -283,24 +283,12 @@ Nes\Mappers - - Nes\Mappers - Nes\Mappers Nes\Mappers - - Nes\Mappers - - - Nes\Mappers - - - Nes\Mappers - Nes\RomLoader @@ -580,15 +568,36 @@ Misc - - Nes\Mappers - Nes\Mappers Misc + + Nes\Mappers\VRC + + + Nes\Mappers\VRC + + + Nes\Mappers\VRC + + + Nes\Mappers\VRC + + + Nes\Mappers\VRC + + + Nes\Mappers\VRC + + + Nes\Mappers\VRC + + + Nes\Mappers\VRC + diff --git a/Core/SoundMixer.cpp b/Core/SoundMixer.cpp index d0293df2..ea916781 100644 --- a/Core/SoundMixer.cpp +++ b/Core/SoundMixer.cpp @@ -151,6 +151,7 @@ int16_t SoundMixer::GetOutputVolume() int16_t expansionOutput = 0; switch(_expansionAudioType) { case AudioChannel::FDS: expansionOutput = (int16_t)(_currentOutput[ExpansionAudioIndex] * _volumes[ExpansionAudioIndex] * 20); break; + case AudioChannel::VRC6: expansionOutput = (int16_t)(_currentOutput[ExpansionAudioIndex] * _volumes[ExpansionAudioIndex] * 75); break; } return squareVolume + tndVolume + expansionOutput; } diff --git a/Core/VRC6.h b/Core/VRC6.h index 788bbdc7..bbba26e7 100644 --- a/Core/VRC6.h +++ b/Core/VRC6.h @@ -2,6 +2,9 @@ #include "stdafx.h" #include "BaseMapper.h" #include "VrcIrq.h" +#include "Vrc6Pulse.h" +#include "Vrc6Saw.h" + enum class VRCVariant; //incomplete - missing audio and more @@ -9,9 +12,16 @@ class VRC6 : public BaseMapper { private: VrcIrq _irq; + Vrc6Pulse _pulse1; + Vrc6Pulse _pulse2; + Vrc6Saw _saw; + VRCVariant _model; uint8_t _bankingMode; uint8_t _chrRegisters[8]; + int32_t _lastOutput; + + bool _haltAudio; void UpdatePrgRamAccess() { @@ -25,7 +35,9 @@ protected: void InitMapper() { _irq.Reset(); + _lastOutput = 0; _bankingMode = 0; + _haltAudio = false; memset(_chrRegisters, 0, sizeof(_chrRegisters)); SelectPRGPage(3, -1); } @@ -33,9 +45,13 @@ protected: virtual void StreamState(bool saving) { BaseMapper::StreamState(saving); - Stream(_irq); ArrayInfo chrRegisters = { _chrRegisters, 8 }; - Stream(_bankingMode, chrRegisters); + Stream(_bankingMode, chrRegisters, _lastOutput, _haltAudio); + + Stream(&_irq); + Stream(&_pulse1); + Stream(&_pulse2); + Stream(&_saw); if(!saving) { UpdatePrgRamAccess(); @@ -45,6 +61,15 @@ protected: void ProcessCpuClock() { _irq.ProcessCpuClock(); + if(!_haltAudio) { + _pulse1.Clock(); + _pulse2.Clock(); + _saw.Clock(); + } + + int32_t outputLevel = _pulse1.GetVolume() + _pulse2.GetVolume() + _saw.GetVolume(); + APU::AddExpansionAudioDelta(AudioChannel::VRC6, outputLevel - _lastOutput); + _lastOutput = outputLevel; } void UpdatePpuBanking() @@ -100,11 +125,32 @@ protected: SelectPrgPage2x(0, (value & 0x0F) << 1); break; + case 0x9000: case 0x9001: case 0x9002: + _pulse1.WriteReg(addr, value); + break; + + case 0x9003: { + _haltAudio = (value & 0x01) == 0x01; + uint8_t frequencyShift = (value & 0x04) == 0x04 ? 8 : ((value & 0x02) == 0x02 ? 4 : 0); + _pulse1.SetFrequencyShift(frequencyShift); + _pulse2.SetFrequencyShift(frequencyShift); + _saw.SetFrequencyShift(frequencyShift); + break; + } + + case 0xA000: case 0xA001: case 0xA002: + _pulse2.WriteReg(addr, value); + break; + + case 0xB000: case 0xB001: case 0xB002: + _saw.WriteReg(addr, value); + break; + case 0xB003: _bankingMode = value; UpdatePpuBanking(); break; - + case 0xC000: case 0xC001: case 0xC002: case 0xC003: SelectPRGPage(2, value & 0x1F); break; diff --git a/Core/Vrc6Pulse.h b/Core/Vrc6Pulse.h new file mode 100644 index 00000000..570d6915 --- /dev/null +++ b/Core/Vrc6Pulse.h @@ -0,0 +1,73 @@ +#pragma once +#include "stdafx.h" +#include "Snapshotable.h" + +class Vrc6Pulse: public Snapshotable +{ +private: + uint8_t _volume = 0; + uint8_t _dutyCycle = 0; + bool _ignoreDuty = false; + uint16_t _frequency = 1; + bool _enabled = false; + + int32_t _timer = 1; + uint8_t _step = 0; + uint8_t _frequencyShift = 0; + + void StreamState(bool saving) + { + Stream(_volume, _dutyCycle, _ignoreDuty, _frequency, _enabled, _timer, _step, _frequencyShift); + } + +public: + void WriteReg(uint16_t addr, uint8_t value) + { + switch(addr & 0x03) { + case 0: + _volume = value & 0x0F; + _dutyCycle = (value & 0x70) >> 4; + _ignoreDuty = (value & 0x80) == 0x80; + break; + + case 1: + _frequency = (_frequency & 0x0F00) | value; + break; + + case 2: + _frequency = (_frequency & 0xFF) | ((value & 0x0F) << 8); + _enabled = (value & 0x80) == 0x80; + if(!_enabled) { + _step = 0; + } + break; + } + } + + void SetFrequencyShift(uint8_t shift) + { + _frequencyShift = shift; + } + + void Clock() + { + if(_enabled) { + _timer--; + if(_timer == 0) { + _step = (_step + 1) & 0x0F; + _timer = (_frequency >> _frequencyShift) + 1; + } + } + } + + uint8_t GetVolume() + { + if(!_enabled) { + return 0; + } else if(_ignoreDuty) { + return _volume; + } else { + return _step <= _dutyCycle ? _volume : 0; + } + } +}; \ No newline at end of file diff --git a/Core/Vrc6Saw.h b/Core/Vrc6Saw.h new file mode 100644 index 00000000..474103f6 --- /dev/null +++ b/Core/Vrc6Saw.h @@ -0,0 +1,79 @@ +#pragma once +#include "stdafx.h" +#include "Snapshotable.h" + +class Vrc6Saw : public Snapshotable +{ +private: + uint8_t _accumulatorRate = 0; + uint8_t _accumulator = 0; + uint16_t _frequency = 1; + bool _enabled = false; + + int32_t _timer = 1; + uint8_t _step = 0; + uint8_t _frequencyShift = 0; + + void StreamState(bool saving) + { + Stream(_accumulatorRate, _accumulator, _frequency, _enabled, _timer, _step, _frequencyShift); + } + +public: + void WriteReg(uint16_t addr, uint8_t value) + { + switch(addr & 0x03) { + case 0: + _accumulatorRate = value & 0x3F; + break; + + case 1: + _frequency = (_frequency & 0x0F00) | value; + break; + + case 2: + _frequency = (_frequency & 0xFF) | ((value & 0x0F) << 8); + _enabled = (value & 0x80) == 0x80; + if(!_enabled) { + //If E is clear, the accumulator is forced to zero until E is again set. + _accumulator = 0; + + //"The phase of the saw generator can be mostly reset by clearing and immediately setting E. Clearing E does not reset the frequency divider, however." + _step = 0; + } + break; + } + } + + void SetFrequencyShift(uint8_t shift) + { + _frequencyShift = shift; + } + + void Clock() + { + if(_enabled) { + _timer--; + if(_timer == 0) { + _step = (_step + 1) % 14; + _timer = (_frequency >> _frequencyShift) + 1; + + if(_step == 0) { + _accumulator = 0; + } else if((_step & 0x01) == 0x00) { + _accumulator += _accumulatorRate; + } + } + } + } + + uint8_t GetVolume() + { + if(!_enabled) { + return 0; + } else { + //"The high 5 bits of the accumulator are then output (provided the channel is enabled by having the E bit set)." + return _accumulator >> 3; + } + } +}; \ No newline at end of file diff --git a/Core/VrcIrq.h b/Core/VrcIrq.h index 074cc0ea..a6309460 100644 --- a/Core/VrcIrq.h +++ b/Core/VrcIrq.h @@ -2,7 +2,7 @@ #include "Snapshotable.h" #include "CPU.h" -class VrcIrq : Snapshotable +class VrcIrq : public Snapshotable { private: uint8_t _irqReloadValue; diff --git a/GUI.NET/Config/AudioInfo.cs b/GUI.NET/Config/AudioInfo.cs index 364a6a24..e7a0c87b 100644 --- a/GUI.NET/Config/AudioInfo.cs +++ b/GUI.NET/Config/AudioInfo.cs @@ -62,11 +62,11 @@ namespace Mesen.GUI.Config InteropEmu.SetChannelVolume(AudioChannel.Noise, ConvertVolume(audioInfo.NoiseVolume)); InteropEmu.SetChannelVolume(AudioChannel.DMC, ConvertVolume(audioInfo.DmcVolume)); InteropEmu.SetChannelVolume(AudioChannel.FDS, ConvertVolume(audioInfo.FdsVolume)); - InteropEmu.SetChannelVolume(AudioChannel.MMC5, ConvertVolume(audioInfo.FdsVolume)); - InteropEmu.SetChannelVolume(AudioChannel.VRC6, ConvertVolume(audioInfo.FdsVolume)); - InteropEmu.SetChannelVolume(AudioChannel.VRC7, ConvertVolume(audioInfo.FdsVolume)); - InteropEmu.SetChannelVolume(AudioChannel.Namco163, ConvertVolume(audioInfo.FdsVolume)); - InteropEmu.SetChannelVolume(AudioChannel.Sunsoft5B, ConvertVolume(audioInfo.FdsVolume)); + InteropEmu.SetChannelVolume(AudioChannel.MMC5, ConvertVolume(audioInfo.Mmc5Volume)); + InteropEmu.SetChannelVolume(AudioChannel.VRC6, ConvertVolume(audioInfo.Vrc6Volume)); + InteropEmu.SetChannelVolume(AudioChannel.VRC7, ConvertVolume(audioInfo.Vrc7Volume)); + InteropEmu.SetChannelVolume(AudioChannel.Namco163, ConvertVolume(audioInfo.Namco163Volume)); + InteropEmu.SetChannelVolume(AudioChannel.Sunsoft5B, ConvertVolume(audioInfo.Sunsoft5bVolume)); InteropEmu.SetSampleRate(audioInfo.SampleRate); InteropEmu.SetFlag(EmulationFlags.MuteSoundInBackground, audioInfo.MuteSoundInBackground); diff --git a/GUI.NET/Forms/Config/frmAudioConfig.Designer.cs b/GUI.NET/Forms/Config/frmAudioConfig.Designer.cs index 47f58042..24bb3263 100644 --- a/GUI.NET/Forms/Config/frmAudioConfig.Designer.cs +++ b/GUI.NET/Forms/Config/frmAudioConfig.Designer.cs @@ -269,7 +269,6 @@ // trkVrc6Vol // this.trkVrc6Vol.Anchor = System.Windows.Forms.AnchorStyles.Top; - this.trkVrc6Vol.Enabled = false; this.trkVrc6Vol.Location = new System.Drawing.Point(156, 160); this.trkVrc6Vol.Margin = new System.Windows.Forms.Padding(0); this.trkVrc6Vol.Maximum = 100;