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;