VRC6: Implemented all 3 audio channels

This commit is contained in:
Souryo 2016-06-07 19:36:05 -04:00
parent ac938995b6
commit 76600d31a9
9 changed files with 237 additions and 28 deletions

View File

@ -548,6 +548,8 @@
<ClInclude Include="VRC2_4.h" />
<ClInclude Include="VRC3.h" />
<ClInclude Include="VRC6.h" />
<ClInclude Include="Vrc6Pulse.h" />
<ClInclude Include="Vrc6Saw.h" />
<ClInclude Include="VRC7.h" />
<ClInclude Include="VrcIrq.h" />
<ClInclude Include="VsControlManager.h" />

View File

@ -68,6 +68,9 @@
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Nes\Mappers\VRC">
<UniqueIdentifier>{0de6642c-1ea6-4872-b21f-2c3009eee9ad}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="IAudioDevice.h">
@ -97,9 +100,6 @@
<ClInclude Include="IMessageManager.h">
<Filter>Nes\Interfaces</Filter>
</ClInclude>
<ClInclude Include="VRC2_4.h">
<Filter>Nes\Mappers</Filter>
</ClInclude>
<ClInclude Include="NetMessage.h">
<Filter>NetPlay\Messages</Filter>
</ClInclude>
@ -283,24 +283,12 @@
<ClInclude Include="NtdecTc112.h">
<Filter>Nes\Mappers</Filter>
</ClInclude>
<ClInclude Include="VRC3.h">
<Filter>Nes\Mappers</Filter>
</ClInclude>
<ClInclude Include="Rambo1.h">
<Filter>Nes\Mappers</Filter>
</ClInclude>
<ClInclude Include="BaseMapper.h">
<Filter>Nes\Mappers</Filter>
</ClInclude>
<ClInclude Include="VRC6.h">
<Filter>Nes\Mappers</Filter>
</ClInclude>
<ClInclude Include="VrcIrq.h">
<Filter>Nes\Mappers</Filter>
</ClInclude>
<ClInclude Include="VRC7.h">
<Filter>Nes\Mappers</Filter>
</ClInclude>
<ClInclude Include="FdsLoader.h">
<Filter>Nes\RomLoader</Filter>
</ClInclude>
@ -580,15 +568,36 @@
<ClInclude Include="stdafx.h">
<Filter>Misc</Filter>
</ClInclude>
<ClInclude Include="VRC1.h">
<Filter>Nes\Mappers</Filter>
</ClInclude>
<ClInclude Include="TxSRom.h">
<Filter>Nes\Mappers</Filter>
</ClInclude>
<ClInclude Include="WaveRecorder.h">
<Filter>Misc</Filter>
</ClInclude>
<ClInclude Include="VrcIrq.h">
<Filter>Nes\Mappers\VRC</Filter>
</ClInclude>
<ClInclude Include="VRC1.h">
<Filter>Nes\Mappers\VRC</Filter>
</ClInclude>
<ClInclude Include="VRC2_4.h">
<Filter>Nes\Mappers\VRC</Filter>
</ClInclude>
<ClInclude Include="VRC3.h">
<Filter>Nes\Mappers\VRC</Filter>
</ClInclude>
<ClInclude Include="VRC6.h">
<Filter>Nes\Mappers\VRC</Filter>
</ClInclude>
<ClInclude Include="VRC7.h">
<Filter>Nes\Mappers\VRC</Filter>
</ClInclude>
<ClInclude Include="Vrc6Pulse.h">
<Filter>Nes\Mappers\VRC</Filter>
</ClInclude>
<ClInclude Include="Vrc6Saw.h">
<Filter>Nes\Mappers\VRC</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="stdafx.cpp">

View File

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

View File

@ -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<uint8_t> 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;

73
Core/Vrc6Pulse.h Normal file
View File

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

79
Core/Vrc6Saw.h Normal file
View File

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

View File

@ -2,7 +2,7 @@
#include "Snapshotable.h"
#include "CPU.h"
class VrcIrq : Snapshotable
class VrcIrq : public Snapshotable
{
private:
uint8_t _irqReloadValue;

View File

@ -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);

View File

@ -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;