diff --git a/Core/Console.cpp b/Core/Console.cpp
index 2a372889..9bc596c8 100644
--- a/Core/Console.cpp
+++ b/Core/Console.cpp
@@ -163,6 +163,11 @@ uint32_t Console::GetCrc32()
}
}
+NesModel Console::GetModel()
+{
+ return Instance->_model;
+}
+
void Console::Reset(bool softReset)
{
Movie::Stop();
diff --git a/Core/Console.h b/Core/Console.h
index ec97940f..dd17ca64 100644
--- a/Core/Console.h
+++ b/Core/Console.h
@@ -70,6 +70,7 @@ class Console
static string GetROMPath();
static string GetRomName();
static uint32_t GetCrc32();
+ static NesModel GetModel();
static bool IsRunning();
diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj
index 57a959a3..2899204f 100644
--- a/Core/Core.vcxproj
+++ b/Core/Core.vcxproj
@@ -591,6 +591,10 @@
+
+
+
+
diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters
index 866c611e..82ca2b9e 100644
--- a/Core/Core.vcxproj.filters
+++ b/Core/Core.vcxproj.filters
@@ -682,6 +682,18 @@
Nes
+
+ Nes\Mappers\VRC
+
+
+ Nes\Mappers\VRC
+
+
+ Nes\Mappers\VRC
+
+
+ Nes\Mappers\VRC
+
diff --git a/Core/MMC5Audio.h b/Core/MMC5Audio.h
index bb0a9801..b7952d2e 100644
--- a/Core/MMC5Audio.h
+++ b/Core/MMC5Audio.h
@@ -3,6 +3,7 @@
#include "SquareChannel.h"
#include "BaseExpansionAudio.h"
#include "CPU.h"
+#include "Console.h"
class MMC5Square : public SquareChannel
{
@@ -71,7 +72,7 @@ protected:
_square2.Run();
if(_audioCounter <= 0) {
//~240hz envelope/length counter
- _audioCounter = CPU::GetClockRate(EmulationSettings::GetNesModel()) / 240;
+ _audioCounter = CPU::GetClockRate(Console::GetModel()) / 240;
_square1.TickLengthCounter();
_square1.TickEnvelope();
_square2.TickLengthCounter();
diff --git a/Core/NsfMapper.cpp b/Core/NsfMapper.cpp
index b09acd8d..ad842f1e 100644
--- a/Core/NsfMapper.cpp
+++ b/Core/NsfMapper.cpp
@@ -72,6 +72,11 @@ void NsfMapper::InitMapper(RomData& romData)
AddRegisterRange(0xA000, 0xA002, MemoryOperation::Write);
AddRegisterRange(0xB000, 0xB002, MemoryOperation::Write);
}
+
+ if(_nsfHeader.SoundChips & NsfSoundChips::VRC7) {
+ AddRegisterRange(0x9010, 0x9010, MemoryOperation::Write);
+ AddRegisterRange(0x9030, 0x9030, MemoryOperation::Write);
+ }
if(_nsfHeader.SoundChips & NsfSoundChips::Namco) {
AddRegisterRange(0x4800, 0x4FFF, MemoryOperation::Any);
@@ -208,6 +213,9 @@ void NsfMapper::ProcessCpuClock()
if(_nsfHeader.SoundChips & NsfSoundChips::VRC6) {
_vrc6Audio.Clock();
}
+ if(_nsfHeader.SoundChips & NsfSoundChips::VRC7) {
+ _vrc7Audio.Clock();
+ }
if(_nsfHeader.SoundChips & NsfSoundChips::Namco) {
_namcoAudio.Clock();
}
@@ -323,6 +331,11 @@ void NsfMapper::WriteRegister(uint16_t addr, uint8_t value)
case 0x9000: case 0x9001: case 0x9002: case 0x9003: case 0xA000: case 0xA001: case 0xA002: case 0xB000: case 0xB001: case 0xB002:
_vrc6Audio.WriteRegister(addr, value);
break;
+
+ case 0x9010: case 0x9030:
+ _vrc7Audio.WriteReg(addr, value);
+ break;
+
}
}
}
diff --git a/Core/NsfMapper.h b/Core/NsfMapper.h
index 65114096..e66ed9df 100644
--- a/Core/NsfMapper.h
+++ b/Core/NsfMapper.h
@@ -2,6 +2,7 @@
#include "BaseMapper.h"
#include "MMC5Audio.h"
#include "Vrc6Audio.h"
+#include "Vrc7Audio.h"
#include "FdsAudio.h"
#include "Namco163Audio.h"
#include "Sunsoft5bAudio.h"
@@ -32,6 +33,7 @@ private:
NsfHeader _nsfHeader;
MMC5Audio _mmc5Audio;
Vrc6Audio _vrc6Audio;
+ Vrc7Audio _vrc7Audio;
FdsAudio _fdsAudio;
Namco163Audio _namcoAudio;
Sunsoft5bAudio _sunsoftAudio;
diff --git a/Core/OpllChannel.h b/Core/OpllChannel.h
new file mode 100644
index 00000000..0dbda911
--- /dev/null
+++ b/Core/OpllChannel.h
@@ -0,0 +1,424 @@
+//////////////////////////////////////////////////////////////////////////////////////////
+// Adapted from Nintendulator's code, which itself is based on Mitsutaka Okazaki's code //
+// https://www.qmtpro.com/~nes/nintendulator/
+//////////////////////////////////////////////////////////////////////////////////////////
+
+/* Modified for usage in VRC7 sound emulation
+
+Copyright (C) Mitsutaka Okazaki 2004
+
+This software is provided 'as-is', without any express or implied warranty.
+In no event will the authors be held liable for any damages arising from
+the use of this software.
+
+Permission is granted to anyone to use this software for any purpose,
+including commercial applications, and to alter it and redistribute it
+freely, subject to the following restrictions:
+
+1. The origin of this software must not be misrepresented; you must not
+claim that you wrote the original software. If you use this software
+in a product, an acknowledgment in the product documentation would be
+appreciated but is not required.
+2. Altered source versions must be plainly marked as such, and must not
+be misrepresented as being the original software.
+3. This notice may not be removed or altered from any source distribution.
+
+*/
+
+/***********************************************************************************
+
+emu2413.c -- YM2413 emulator written by Mitsutaka Okazaki 2001
+
+2003 01-24 : Modified by xodnizel to remove code not needed for the VRC7, among other things.
+
+References:
+fmopl.c -- 1999,2000 written by Tatsuyuki Satoh (MAME development).
+fmopl.c(fixed) -- (C) 2002 Jarek Burczynski.
+s_opl.c -- 2001 written by Mamiya (NEZplug development).
+fmgen.cpp -- 1999,2000 written by cisc.
+fmpac.ill -- 2000 created by NARUTO.
+MSX-Datapack
+YMU757 data sheet
+YM2143 data sheet
+
+**************************************************************************************/
+
+#pragma once
+#include "stdafx.h"
+#include "OpllTables.h"
+
+namespace Vrc7Opll
+{
+ /* voice data */
+ struct OpllPatch
+ {
+ uint32_t TL, FB, EG, ML, AR, DR, SL, RR, KR, KL, AM, PM, WF;
+ };
+
+ class OpllChannel : public Snapshotable
+ {
+ private:
+ /* Expand x which is s bits to d bits. */
+ uint32_t ExpandBits(uint32_t x, uint32_t s, uint32_t d)
+ {
+ return x << (d-s);
+ }
+
+ /* Cut the lower b bit(s) off. */
+ uint32_t GetHighBits(uint32_t c, uint32_t b)
+ {
+ return c >> b;
+ }
+
+ int32_t wave2_4pi(int32_t e)
+ {
+ return (e) >> (SLOT_AMP_BITS - PG_BITS - 1);
+ }
+
+ /* Convert Amp(0 to EG_HEIGHT) to Phase(0 to 8PI). */
+ int32_t wave2_8pi(int32_t e)
+ {
+ return e >> (SLOT_AMP_BITS - PG_BITS - 2);
+ }
+
+ int32_t EG2DB(int32_t d)
+ {
+ return d*(int32_t)(EG_STEP / DB_STEP);
+ }
+
+ uint32_t SL2EG(int32_t d)
+ {
+ return d*(int32_t)(SL_STEP / EG_STEP);
+ }
+
+ uint32_t S2E(double x)
+ {
+ return (SL2EG((int32_t)(x / SL_STEP)) << (EG_DP_BITS - EG_BITS));
+ }
+
+ shared_ptr _tables;
+ OpllPatch patch;
+
+ int32_t type; /* 0 : modulator 1 : carrier */
+
+ /* OUTPUT */
+ int32_t feedback;
+ int32_t output[2]; /* Output value of slot */
+
+ /* for Phase Generator (PG) */
+ uint16_t *sintbl; /* Wavetable */
+ uint32_t phase; /* Phase */
+ uint32_t dphase; /* Phase increment amount */
+ uint32_t pgout; /* output */
+
+ /* for Envelope Generator (EG) */
+ int32_t fnum; /* F-Number */
+ int32_t block; /* Block */
+ int32_t volume; /* Current volume */
+ int32_t sustine; /* Sustine 1 = ON, 0 = OFF */
+ uint32_t tll; /* Total Level + Key scale level*/
+ uint32_t rks; /* Key scale offset (Rks) */
+ int32_t eg_mode; /* Current state */
+ uint32_t eg_phase; /* Phase */
+ uint32_t eg_dphase; /* Phase increment amount */
+ uint32_t egout; /* output */
+
+ protected:
+ void StreamState(bool saving)
+ {
+ Stream(type, feedback, output[0], output[1], phase, dphase, pgout, fnum, block, volume, sustine, tll, rks, eg_mode, eg_phase, eg_dphase, egout,
+ patch.TL, patch.FB, patch.EG, patch.ML, patch.AR, patch.DR, patch.SL, patch.RR, patch.KR, patch.KL, patch.AM, patch.PM, patch.WF);
+
+ if(!saving) {
+ UpdateAll();
+ }
+ }
+
+ public:
+ OpllPatch* GetPatch()
+ {
+ return &patch;
+ }
+
+ int32_t GetType()
+ {
+ return type;
+ }
+
+ int32_t GetEgMode()
+ {
+ return eg_mode;
+ }
+
+ void SetTables(shared_ptr tables)
+ {
+ _tables = tables;
+ }
+
+ void setSustine(int32_t sustine)
+ {
+ this->sustine = sustine;
+ }
+
+ void setVolume(int32_t volume)
+ {
+ this->volume = volume;
+ }
+
+ void setFnumber(int32_t fnum)
+ {
+ this->fnum = fnum;
+ }
+
+ void setBlock(int32_t block)
+ {
+ this->block = block;
+ }
+
+ void reset(int type)
+ {
+ this->type = type;
+ sintbl = _tables->waveform[0];
+ phase = 0;
+ dphase = 0;
+ output[0] = 0;
+ output[1] = 0;
+ feedback = 0;
+ eg_mode = SETTLE;
+ eg_phase = EG_DP_WIDTH;
+ eg_dphase = 0;
+ rks = 0;
+ tll = 0;
+ sustine = 0;
+ fnum = 0;
+ block = 0;
+ volume = 0;
+ pgout = 0;
+ egout = 0;
+ }
+
+ /* Slot key on */
+ void slotOn()
+ {
+ eg_mode = ATTACK;
+ eg_phase = 0;
+ phase = 0;
+ }
+
+ /* Slot key on without reseting the phase */
+ void slotOn2()
+ {
+ eg_mode = ATTACK;
+ eg_phase = 0;
+ }
+
+ /* Slot key off */
+ void slotOff()
+ {
+ if(eg_mode == ATTACK) {
+ eg_phase = ExpandBits(_tables->AR_ADJUST_TABLE[GetHighBits(eg_phase, EG_DP_BITS - EG_BITS)], EG_BITS, EG_DP_BITS);
+ }
+ eg_mode = RELEASE;
+ }
+
+ void setSlotVolume(int32_t volume)
+ {
+ this->volume = volume;
+ }
+
+ void calc_phase(int32_t lfo)
+ {
+ if(patch.PM) {
+ phase += (dphase * lfo) >> PM_AMP_BITS;
+ } else {
+ phase += dphase;
+ }
+
+ phase &= (DP_WIDTH - 1);
+
+ pgout = GetHighBits(phase, DP_BASE_BITS);
+ }
+
+ int32_t calc_slot_car(int32_t fm)
+ {
+ output[1] = output[0];
+
+ if(egout >= (DB_MUTE - 1)) {
+ output[0] = 0;
+ } else {
+ output[0] = _tables->DB2LIN_TABLE[sintbl[(pgout + wave2_8pi(fm))&(PG_WIDTH - 1)] + egout];
+ }
+
+ return (output[1] + output[0]) >> 1;
+ }
+
+ int32_t calc_slot_mod()
+ {
+ int32_t fm;
+
+ output[1] = output[0];
+
+ if(egout >= (DB_MUTE - 1)) {
+ output[0] = 0;
+ } else if(patch.FB != 0) {
+ fm = wave2_4pi(feedback) >> (7 - patch.FB);
+ output[0] = _tables->DB2LIN_TABLE[sintbl[(pgout + fm)&(PG_WIDTH - 1)] + egout];
+ } else {
+ output[0] = _tables->DB2LIN_TABLE[sintbl[pgout] + egout];
+ }
+
+ feedback = (output[1] + output[0]) >> 1;
+
+ return feedback;
+ }
+
+ void UpdatePg()
+ {
+ dphase = _tables->dphaseTable[fnum][block][patch.ML];
+ }
+
+ void UpdateTll()
+ {
+ if(type == 0) {
+ tll = _tables->tllTable[(fnum) >> 5][block][patch.TL][patch.KL];
+ } else {
+ tll = _tables->tllTable[(fnum) >> 5][block][volume][patch.KL];
+ }
+ }
+
+ void UpdateRks()
+ {
+ rks = _tables->rksTable[(fnum) >> 8][block][patch.KR];
+ }
+
+ void UpdateWf()
+ {
+ sintbl = _tables->waveform[patch.WF];
+ }
+
+ void UpdateEg()
+ {
+ eg_dphase = calc_eg_dphase();
+ }
+
+ void UpdateAll()
+ {
+ UpdatePg();
+ UpdateTll();
+ UpdateRks();
+ UpdateWf();
+ UpdateEg(); /* EG should be updated last. */
+ }
+
+ uint32_t calc_eg_dphase()
+ {
+ switch(eg_mode) {
+ case ATTACK:
+ return _tables->dphaseARTable[patch.AR][rks];
+
+ case DECAY:
+ return _tables->dphaseDRTable[patch.DR][rks];
+
+ case SUSHOLD:
+ return 0;
+
+ case SUSTINE:
+ return _tables->dphaseDRTable[patch.RR][rks];
+
+ case RELEASE:
+ if(sustine) {
+ return _tables->dphaseDRTable[5][rks];
+ } else if(patch.EG) {
+ return _tables->dphaseDRTable[patch.RR][rks];
+ } else {
+ return _tables->dphaseDRTable[7][rks];
+ }
+
+ case FINISH:
+ return 0;
+
+ default:
+ return 0;
+ }
+ }
+
+ void calc_envelope(int32_t lfo)
+ {
+ static uint32_t SL[16] = {
+ S2E(0.0), S2E(3.0), S2E(6.0), S2E(9.0), S2E(12.0), S2E(15.0), S2E(18.0), S2E(21.0),
+ S2E(24.0), S2E(27.0), S2E(30.0), S2E(33.0), S2E(36.0), S2E(39.0), S2E(42.0), S2E(48.0)
+ };
+
+ uint32_t egout;
+
+ switch(eg_mode) {
+
+ case ATTACK:
+ egout = _tables->AR_ADJUST_TABLE[GetHighBits(eg_phase, EG_DP_BITS - EG_BITS)];
+ eg_phase += eg_dphase;
+ if((EG_DP_WIDTH & eg_phase) || (patch.AR == 15)) {
+ egout = 0;
+ eg_phase = 0;
+ eg_mode = DECAY;
+
+ UpdateEg();
+ }
+ break;
+
+ case DECAY:
+ egout = GetHighBits(eg_phase, EG_DP_BITS - EG_BITS);
+ eg_phase += eg_dphase;
+ if(eg_phase >= SL[patch.SL]) {
+ if(patch.EG) {
+ eg_phase = SL[patch.SL];
+ eg_mode = SUSHOLD;
+ UpdateEg();
+ } else {
+ eg_phase = SL[patch.SL];
+ eg_mode = SUSTINE;
+ UpdateEg();
+ }
+ }
+ break;
+
+ case SUSHOLD:
+ egout = GetHighBits(eg_phase, EG_DP_BITS - EG_BITS);
+ if(patch.EG == 0) {
+ eg_mode = SUSTINE;
+ UpdateEg();
+ }
+ break;
+
+ case SUSTINE:
+ case RELEASE:
+ egout = GetHighBits(eg_phase, EG_DP_BITS - EG_BITS);
+ eg_phase += eg_dphase;
+ if(egout >= (1 << EG_BITS)) {
+ eg_mode = FINISH;
+ egout = (1 << EG_BITS) - 1;
+ }
+ break;
+
+ case FINISH:
+ egout = (1 << EG_BITS) - 1;
+ break;
+
+ default:
+ egout = (1 << EG_BITS) - 1;
+ break;
+ }
+
+ if(patch.AM) {
+ egout = EG2DB(egout + tll) + lfo;
+ } else {
+ egout = EG2DB(egout + tll);
+ }
+
+ if(egout >= DB_MUTE) {
+ egout = DB_MUTE - 1;
+ }
+
+ this->egout = egout;
+ }
+ };
+}
diff --git a/Core/OpllEmulator.h b/Core/OpllEmulator.h
new file mode 100644
index 00000000..484c4db5
--- /dev/null
+++ b/Core/OpllEmulator.h
@@ -0,0 +1,541 @@
+//////////////////////////////////////////////////////////////////////////////////////////
+// Adapted from Nintendulator's code, which itself is based on Mitsutaka Okazaki's code //
+// https://www.qmtpro.com/~nes/nintendulator/
+//////////////////////////////////////////////////////////////////////////////////////////
+
+/* Modified for usage in VRC7 sound emulation
+
+Copyright (C) Mitsutaka Okazaki 2004
+
+This software is provided 'as-is', without any express or implied warranty.
+In no event will the authors be held liable for any damages arising from
+the use of this software.
+
+Permission is granted to anyone to use this software for any purpose,
+including commercial applications, and to alter it and redistribute it
+freely, subject to the following restrictions:
+
+1. The origin of this software must not be misrepresented; you must not
+claim that you wrote the original software. If you use this software
+in a product, an acknowledgment in the product documentation would be
+appreciated but is not required.
+2. Altered source versions must be plainly marked as such, and must not
+be misrepresented as being the original software.
+3. This notice may not be removed or altered from any source distribution.
+
+*/
+
+/***********************************************************************************
+
+emu2413.c -- YM2413 emulator written by Mitsutaka Okazaki 2001
+
+2003 01-24 : Modified by xodnizel to remove code not needed for the VRC7, among other things.
+
+References:
+fmopl.c -- 1999,2000 written by Tatsuyuki Satoh (MAME development).
+fmopl.c(fixed) -- (C) 2002 Jarek Burczynski.
+s_opl.c -- 2001 written by Mamiya (NEZplug development).
+fmgen.cpp -- 1999,2000 written by cisc.
+fmpac.ill -- 2000 created by NARUTO.
+MSX-Datapack
+YMU757 data sheet
+YM2143 data sheet
+
+**************************************************************************************/
+
+#pragma once
+#include "stdafx.h"
+#include "OpllTables.h"
+#include "OpllChannel.h"
+
+namespace Vrc7Opll
+{
+ class OpllEmulator : public Snapshotable
+ {
+ private:
+ const unsigned char default_inst[15][8] = {
+ {0x03, 0x21, 0x05, 0x06, 0xB8, 0x82, 0x42, 0x27},
+ {0x13, 0x41, 0x13, 0x0D, 0xD8, 0xD6, 0x23, 0x12},
+ {0x31, 0x11, 0x08, 0x08, 0xFA, 0x9A, 0x22, 0x02},
+ {0x31, 0x61, 0x18, 0x07, 0x78, 0x64, 0x30, 0x27},
+ {0x22, 0x21, 0x1E, 0x06, 0xF0, 0x76, 0x08, 0x28},
+ {0x02, 0x01, 0x06, 0x00, 0xF0, 0xF2, 0x03, 0xF5},
+ {0x21, 0x61, 0x1D, 0x07, 0x82, 0x81, 0x16, 0x07},
+ {0x23, 0x21, 0x1A, 0x17, 0xCF, 0x72, 0x25, 0x17},
+ {0x15, 0x11, 0x25, 0x00, 0x4F, 0x71, 0x00, 0x11},
+ {0x85, 0x01, 0x12, 0x0F, 0x99, 0xA2, 0x40, 0x02},
+ {0x07, 0xC1, 0x69, 0x07, 0xF3, 0xF5, 0xA7, 0x12},
+ {0x71, 0x23, 0x0D, 0x06, 0x66, 0x75, 0x23, 0x16},
+ {0x01, 0x02, 0xD3, 0x05, 0xA3, 0x92, 0xF7, 0x52},
+ {0x61, 0x63, 0x0C, 0x00, 0x94, 0xAF, 0x34, 0x06},
+ {0x21, 0x62, 0x0D, 0x00, 0xB1, 0xA0, 0x54, 0x17}
+ };
+
+ uint32_t adr;
+ int32_t out;
+
+ uint32_t realstep;
+ uint32_t oplltime;
+ uint32_t opllstep;
+ int32_t prev, next;
+
+ /* Register */
+ uint8_t LowFreq[6];
+ uint8_t HiFreq[6];
+ uint8_t InstVol[6];
+
+ uint8_t CustInst[8];
+
+ int32_t slot_on_flag[6 * 2];
+
+ /* Pitch Modulator */
+ uint32_t pm_phase;
+ int32_t lfo_pm;
+
+ /* Amp Modulator */
+ int32_t am_phase;
+ int32_t lfo_am;
+
+ /* Channel Data */
+ int32_t patch_number[6];
+ int32_t key_status[6];
+
+ /* Slot */
+ OpllChannel slot[6 * 2];
+
+ uint32_t mask;
+ shared_ptr tables;
+
+ OpllChannel* GetModulator(uint8_t i)
+ {
+ return &slot[i << 1];
+ }
+
+ OpllChannel* GetCarrier(uint8_t i)
+ {
+ return &slot[(i << 1) | 1];
+ }
+
+ int32_t OPLL_MASK_CH(int32_t x)
+ {
+ return 1 << x;
+ }
+
+ /* Cut the lower b bit(s) off. */
+ uint32_t GetHighBits(uint32_t c, uint32_t b)
+ {
+ return c >> b;
+ }
+ protected:
+ void StreamState(bool saving)
+ {
+ ArrayInfo lowFreq{ LowFreq, 6 };
+ ArrayInfo hiFreq{ HiFreq, 6 };
+ ArrayInfo instVol{ InstVol, 6 };
+ ArrayInfo custInst{ CustInst, 8 };
+ ArrayInfo slotOnFlag{ slot_on_flag, 12 };
+ ArrayInfo patchNumber{ patch_number, 6 };
+ ArrayInfo keyStatus{ key_status, 6 };
+
+ SnapshotInfo opllTables{ tables.get() };
+
+ SnapshotInfo channel0{ &slot[0] };
+ SnapshotInfo channel1{ &slot[1] };
+ SnapshotInfo channel2{ &slot[2] };
+ SnapshotInfo channel3{ &slot[3] };
+ SnapshotInfo channel4{ &slot[4] };
+ SnapshotInfo channel5{ &slot[5] };
+ SnapshotInfo channel6{ &slot[6] };
+ SnapshotInfo channel7{ &slot[7] };
+ SnapshotInfo channel8{ &slot[8] };
+ SnapshotInfo channel9{ &slot[9] };
+ SnapshotInfo channel10{ &slot[10] };
+ SnapshotInfo channel11{ &slot[11] };
+
+ Stream(adr, out, realstep, oplltime, opllstep, prev, next, pm_phase, lfo_pm, am_phase, lfo_am, mask,
+ lowFreq, hiFreq, instVol, custInst, slotOnFlag, patchNumber, keyStatus, opllTables,
+ channel0, channel1, channel2, channel3, channel4, channel5,
+ channel6, channel7, channel8, channel9, channel10, channel11
+ );
+ }
+
+ public:
+ OpllEmulator()
+ {
+ tables.reset(new Vrc7Opll::OpllTables());
+ tables->maketables(3579545, 49716);
+
+ for(int i = 0; i < 12; i++) {
+ slot[i].SetTables(tables);
+ }
+
+ mask = 0;
+ Reset(tables->clk, tables->rate);
+ }
+
+ ~OpllEmulator()
+ {
+
+ }
+
+ /* Setup */
+ void Reset(uint32_t clk, uint32_t rate)
+ {
+ int32_t i;
+
+ adr = 0;
+ out = 0;
+
+ pm_phase = 0;
+ am_phase = 0;
+
+ mask = 0;
+
+ for(i = 0; i < 12; i++) {
+ slot[i].reset(i % 2);
+ }
+
+ for(i = 0; i < 6; i++) {
+ key_status[i] = 0;
+ //setPatch (opll, i, 0);
+ }
+
+ for(i = 0; i < 0x40; i++) {
+ WriteReg(i, 0);
+ }
+
+ realstep = (uint32_t)((1 << 31) / rate);
+ opllstep = (uint32_t)((1 << 31) / (clk / 72));
+ oplltime = 0;
+ }
+
+ /* Port/Register access */
+ void WriteReg(uint32_t reg, uint32_t val)
+ {
+ int32_t i, v, ch;
+
+ uint32_t data = val & 0xff;
+ reg = reg & 0x3f;
+
+ switch(reg) {
+ case 0x00:
+ CustInst[0] = (uint8_t)data;
+ for(i = 0; i < 6; i++) {
+ if(patch_number[i] == 0) {
+ setInstrument(i, 0);
+ GetModulator(i)->UpdatePg();
+ GetModulator(i)->UpdateRks();
+ GetModulator(i)->UpdateEg();
+ }
+ }
+ break;
+
+ case 0x01:
+ CustInst[1] = (uint8_t)data;
+ for(i = 0; i < 6; i++) {
+ if(patch_number[i] == 0) {
+ setInstrument(i, 0);
+ GetCarrier(i)->UpdatePg();
+ GetCarrier(i)->UpdateRks();
+ GetCarrier(i)->UpdateEg();
+ }
+ }
+ break;
+
+ case 0x02:
+ CustInst[2] = (uint8_t)data;
+ for(i = 0; i < 6; i++) {
+ if(patch_number[i] == 0) {
+ setInstrument(i, 0);
+ GetModulator(i)->UpdateTll();
+ }
+ }
+ break;
+
+ case 0x03:
+ CustInst[3] = (uint8_t)data;
+ for(i = 0; i < 6; i++) {
+ if(patch_number[i] == 0) {
+ setInstrument(i, 0);
+ GetModulator(i)->UpdateWf();
+ GetCarrier(i)->UpdateWf();
+ }
+ }
+ break;
+
+ case 0x04:
+ CustInst[4] = (uint8_t)data;
+ for(i = 0; i < 6; i++) {
+ if(patch_number[i] == 0) {
+ setInstrument(i, 0);
+ GetModulator(i)->UpdateEg();
+ }
+ }
+ break;
+
+ case 0x05:
+ CustInst[5] = (uint8_t)data;
+ for(i = 0; i < 6; i++) {
+ if(patch_number[i] == 0) {
+ setInstrument(i, 0);
+ GetCarrier(i)->UpdateEg();
+ }
+ }
+ break;
+
+ case 0x06:
+ CustInst[6] = (uint8_t)data;
+ for(i = 0; i < 6; i++) {
+ if(patch_number[i] == 0) {
+ setInstrument(i, 0);
+ GetModulator(i)->UpdateEg();
+ }
+ }
+ break;
+
+ case 0x07:
+ CustInst[7] = (uint8_t)data;
+ for(i = 0; i < 6; i++) {
+ if(patch_number[i] == 0) {
+ setInstrument(i, 0);
+ GetCarrier(i)->UpdateEg();
+ }
+ }
+ break;
+
+ case 0x10:
+ case 0x11:
+ case 0x12:
+ case 0x13:
+ case 0x14:
+ case 0x15:
+ ch = reg - 0x10;
+ LowFreq[ch] = (uint8_t)data;
+ setFnumber(ch, data + ((HiFreq[ch] & 1) << 8));
+ GetModulator(ch)->UpdateAll();
+ GetCarrier(ch)->UpdateAll();
+ break;
+
+ case 0x20:
+ case 0x21:
+ case 0x22:
+ case 0x23:
+ case 0x24:
+ case 0x25:
+ ch = reg - 0x20;
+ HiFreq[ch] = (uint8_t)data;
+
+ setFnumber(ch, ((data & 1) << 8) + LowFreq[ch]);
+ setBlock(ch, (data >> 1) & 7);
+ setSustine(ch, (data >> 5) & 1);
+ if(data & 0x10) {
+ keyOn(ch);
+ } else {
+ keyOff(ch);
+ }
+ GetModulator(ch)->UpdateAll();
+ GetCarrier(ch)->UpdateAll();
+ update_key_status();
+ break;
+
+ case 0x30:
+ case 0x31:
+ case 0x32:
+ case 0x33:
+ case 0x34:
+ case 0x35:
+ InstVol[reg - 0x30] = (uint8_t)data;
+ i = (data >> 4) & 15;
+ v = data & 15;
+ setInstrument(reg - 0x30, i);
+ setVolume(reg - 0x30, v << 2);
+ GetModulator(reg - 0x30)->UpdateAll();
+ GetCarrier(reg - 0x30)->UpdateAll();
+ break;
+
+ default:
+ break;
+
+ }
+ }
+
+ void setInstrument(unsigned int i, unsigned int inst)
+ {
+ const uint8_t *src;
+ OpllPatch *modp, *carp;
+
+ patch_number[i] = inst;
+
+ if(inst) {
+ src = default_inst[inst - 1];
+ } else {
+ src = CustInst;
+ }
+
+ modp = GetModulator(i)->GetPatch();
+ carp = GetCarrier(i)->GetPatch();
+
+ modp->AM = (src[0] >> 7) & 1;
+ modp->PM = (src[0] >> 6) & 1;
+ modp->EG = (src[0] >> 5) & 1;
+ modp->KR = (src[0] >> 4) & 1;
+ modp->ML = (src[0] & 0xF);
+
+ carp->AM = (src[1] >> 7) & 1;
+ carp->PM = (src[1] >> 6) & 1;
+ carp->EG = (src[1] >> 5) & 1;
+ carp->KR = (src[1] >> 4) & 1;
+ carp->ML = (src[1] & 0xF);
+
+ modp->KL = (src[2] >> 6) & 3;
+ modp->TL = (src[2] & 0x3F);
+
+ carp->KL = (src[3] >> 6) & 3;
+ carp->WF = (src[3] >> 4) & 1;
+
+ modp->WF = (src[3] >> 3) & 1;
+
+ modp->FB = (src[3]) & 7;
+
+ modp->AR = (src[4] >> 4) & 0xF;
+ modp->DR = (src[4] & 0xF);
+
+ carp->AR = (src[5] >> 4) & 0xF;
+ carp->DR = (src[5] & 0xF);
+
+ modp->SL = (src[6] >> 4) & 0xF;
+ modp->RR = (src[6] & 0xF);
+
+ carp->SL = (src[7] >> 4) & 0xF;
+ carp->RR = (src[7] & 0xF);
+ }
+
+ /* Misc */
+ void ForceRefresh()
+ {
+ for(int i = 0; i < 12; i++) {
+ slot[i].UpdatePg();
+ slot[i].UpdateRks();
+ slot[i].UpdateTll();
+ slot[i].UpdateWf();
+ slot[i].UpdateEg();
+ }
+ }
+
+ /* Channel Mask */
+ uint32_t SetMask(uint32_t mask)
+ {
+ uint32_t ret = mask;
+ mask = mask;
+ return ret;
+ }
+
+ uint32_t ToggleMask(uint32_t mask)
+ {
+ uint32_t ret = mask;
+ mask ^= mask;
+ return ret;
+ }
+
+ /* Channel key on */
+ void keyOn(int32_t i)
+ {
+ if(!slot_on_flag[i * 2]) {
+ GetModulator(i)->slotOn();
+ }
+ if(!slot_on_flag[i * 2 + 1]) {
+ GetCarrier(i)->slotOn();
+ }
+ key_status[i] = 1;
+ }
+
+ /* Channel key off */
+ void keyOff(int32_t i)
+ {
+ if(slot_on_flag[i * 2 + 1]) {
+ GetCarrier(i)->slotOff();
+ }
+ key_status[i] = 0;
+ }
+
+ /* Set sustine parameter */
+ void setSustine(int32_t c, int32_t sustine)
+ {
+ GetCarrier(c)->setSustine(sustine);
+ if(GetModulator(c)->GetType()) {
+ GetModulator(c)->setSustine(sustine);
+ }
+ }
+
+ /* Volume : 6bit ( Volume register << 2 ) */
+ void setVolume(int32_t c, int32_t volume)
+ {
+ GetCarrier(c)->setVolume(volume);
+ }
+
+ /* Set F-Number ( fnum : 9bit ) */
+ void setFnumber(int32_t c, int32_t fnum)
+ {
+ GetCarrier(c)->setFnumber(fnum);
+ GetModulator(c)->setFnumber(fnum);
+ }
+
+ /* Set Block data (block : 3bit ) */
+ void setBlock(int32_t c, int32_t block)
+ {
+ GetCarrier(c)->setBlock(block);
+ GetModulator(c)->setBlock(block);
+ }
+
+ void update_key_status()
+ {
+ for(int ch = 0; ch < 6; ch++) {
+ slot_on_flag[ch * 2] = slot_on_flag[ch * 2 + 1] = (HiFreq[ch]) & 0x10;
+ }
+ }
+
+ void update_ampm()
+ {
+ pm_phase = (pm_phase + tables->pm_dphase) & (PM_DP_WIDTH - 1);
+ am_phase = (am_phase + tables->am_dphase) & (AM_DP_WIDTH - 1);
+ lfo_am = tables->amtable[GetHighBits(am_phase, AM_DP_BITS - AM_PG_BITS)];
+ lfo_pm = tables->pmtable[GetHighBits(pm_phase, PM_DP_BITS - PM_PG_BITS)];
+ }
+
+ int32_t calc()
+ {
+ int32_t inst = 0, out = 0;
+ int32_t i;
+
+ update_ampm();
+
+ for(i = 0; i < 12; i++) {
+ slot[i].calc_phase(lfo_pm);
+ slot[i].calc_envelope(lfo_am);
+ }
+
+ for(i = 0; i < 6; i++) {
+ if(!(mask & OPLL_MASK_CH(i)) && (GetCarrier(i)->GetEgMode() != FINISH)) {
+ inst += GetCarrier(i)->calc_slot_car(GetModulator(i)->calc_slot_mod());
+ }
+ }
+
+ out = inst;
+ return (int32_t)out;
+ }
+
+ int16_t GetOutput()
+ {
+ while(realstep > oplltime) {
+ oplltime += opllstep;
+ prev = next;
+ next = calc();
+ }
+
+ oplltime -= realstep;
+ out = (int16_t)(((double)next * (opllstep - oplltime) + (double)prev * oplltime) / opllstep);
+
+ return (int16_t)out;
+ }
+ };
+}
\ No newline at end of file
diff --git a/Core/OpllTables.h b/Core/OpllTables.h
new file mode 100644
index 00000000..a3701f54
--- /dev/null
+++ b/Core/OpllTables.h
@@ -0,0 +1,399 @@
+//////////////////////////////////////////////////////////////////////////////////////////
+// Adapted from Nintendulator's code, which itself is based on Mitsutaka Okazaki's code //
+// https://www.qmtpro.com/~nes/nintendulator/
+//////////////////////////////////////////////////////////////////////////////////////////
+
+/* Modified for usage in VRC7 sound emulation
+
+Copyright (C) Mitsutaka Okazaki 2004
+
+This software is provided 'as-is', without any express or implied warranty.
+In no event will the authors be held liable for any damages arising from
+the use of this software.
+
+Permission is granted to anyone to use this software for any purpose,
+including commercial applications, and to alter it and redistribute it
+freely, subject to the following restrictions:
+
+1. The origin of this software must not be misrepresented; you must not
+claim that you wrote the original software. If you use this software
+in a product, an acknowledgment in the product documentation would be
+appreciated but is not required.
+2. Altered source versions must be plainly marked as such, and must not
+be misrepresented as being the original software.
+3. This notice may not be removed or altered from any source distribution.
+
+*/
+
+/***********************************************************************************
+
+emu2413.c -- YM2413 emulator written by Mitsutaka Okazaki 2001
+
+2003 01-24 : Modified by xodnizel to remove code not needed for the VRC7, among other things.
+
+References:
+fmopl.c -- 1999,2000 written by Tatsuyuki Satoh (MAME development).
+fmopl.c(fixed) -- (C) 2002 Jarek Burczynski.
+s_opl.c -- 2001 written by Mamiya (NEZplug development).
+fmgen.cpp -- 1999,2000 written by cisc.
+fmpac.ill -- 2000 created by NARUTO.
+MSX-Datapack
+YMU757 data sheet
+YM2143 data sheet
+
+**************************************************************************************/
+
+#pragma once
+#include "stdafx.h"
+#include "Snapshotable.h"
+
+namespace Vrc7Opll {
+ /* Size of Sintable ( 8 -- 18 can be used. 9 recommended.)*/
+ const int32_t PG_BITS = 9;
+ const int32_t PG_WIDTH = 1 << PG_BITS;
+
+ /* Phase increment counter */
+ const int32_t DP_BITS = 18;
+ const int32_t DP_WIDTH = 1 << DP_BITS;
+ const int32_t DP_BASE_BITS = DP_BITS - PG_BITS;
+
+ /* Dynamic range (Accuracy of sin table) */
+ const int32_t DB_BITS = 8;
+ const double DB_STEP = (48.0 / (1 << DB_BITS));
+ const int32_t DB_MUTE = 1 << DB_BITS;
+
+ /* Dynamic range of envelope */
+ const double EG_STEP = 0.375;
+ const int32_t EG_BITS = 7;
+ const int32_t EG_MUTE = (1 << EG_BITS);
+
+ /* Dynamic range of total level */
+ const double TL_STEP = 0.75;
+ const int32_t TL_BITS = 6;
+ const int32_t TL_MUTE = (1 << TL_BITS);
+
+ /* Dynamic range of sustine level */
+ const double SL_STEP = 3.0;
+ const int32_t SL_BITS = 4;
+ const int32_t SL_MUTE = (1 << SL_BITS);
+
+ /* Bits for liner value */
+ const int32_t DB2LIN_AMP_BITS = 11;
+ const int32_t SLOT_AMP_BITS = DB2LIN_AMP_BITS;
+
+ /* Bits for envelope phase incremental counter */
+ const int32_t EG_DP_BITS = 22;
+ const int32_t EG_DP_WIDTH = (1 << EG_DP_BITS);
+
+ /* Bits for Pitch and Amp modulator */
+ const int32_t PM_PG_BITS = 8;
+ const int32_t PM_PG_WIDTH = (1 << PM_PG_BITS);
+ const int32_t PM_DP_BITS = 16;
+ const int32_t PM_DP_WIDTH = (1 << PM_DP_BITS);
+ const int32_t AM_PG_BITS = 8;
+ const int32_t AM_PG_WIDTH = (1 << AM_PG_BITS);
+ const int32_t AM_DP_BITS = 16;
+ const int32_t AM_DP_WIDTH = (1 << AM_DP_BITS);
+
+ /* PM table is calcurated by PM_AMP * pow(2,PM_DEPTH*sin(x)/1200) */
+ const int32_t PM_AMP_BITS = 8;
+ const int32_t PM_AMP = (1 << PM_AMP_BITS);
+
+ /* PM speed(Hz) and depth(cent) */
+ const double PM_SPEED = 6.4;
+ const double PM_DEPTH = 13.75;
+
+ /* AM speed(Hz) and depth(dB) */
+ const double AM_SPEED = 3.7;
+ const double AM_DEPTH = 4.8;
+
+ const double PI = 3.14159265358979323846;
+
+ /* Definition of envelope mode */
+ enum
+ {
+ SETTLE, ATTACK, DECAY, SUSHOLD, SUSTINE, RELEASE, FINISH
+ };
+
+ class OpllTables : public Snapshotable
+ {
+ private:
+ /* Convert Amp(0 to EG_HEIGHT) to Phase(0 to 4PI). */
+ int32_t wave2_4pi(int32_t e)
+ {
+ return (e) >> (SLOT_AMP_BITS - PG_BITS - 1);
+ }
+
+ /* Convert Amp(0 to EG_HEIGHT) to Phase(0 to 8PI). */
+ int32_t wave2_8pi(int32_t e)
+ {
+ return e >> (SLOT_AMP_BITS - PG_BITS - 2);
+ }
+
+ uint32_t TL2EG(uint32_t d)
+ {
+ return d*(int32_t)(TL_STEP / EG_STEP);
+ }
+
+ /* Adjust envelope speed which depends on sampling rate. */
+ uint32_t rate_adjust(double x)
+ {
+ return (uint32_t)((rate == 49716 ? x : ((double)(x)*clk / 72 / rate + 0.5))); /* added 0.5 to round the value*/
+ }
+
+ protected:
+ void StreamState(bool saving)
+ {
+ Stream(clk, rate);
+ if(!saving) {
+ maketables(clk, rate);
+ }
+ }
+
+ public:
+ /* Input clock */
+ uint32_t clk = 844451141;
+ /* Sampling rate */
+ uint32_t rate = 3354932;
+
+ /* WaveTable for each envelope amp */
+ uint16_t fullsintable[PG_WIDTH];
+ uint16_t halfsintable[PG_WIDTH];
+
+ /* LFO Table */
+ int32_t pmtable[PM_PG_WIDTH];
+ int32_t amtable[AM_PG_WIDTH];
+
+ /* dB to Liner table */
+ int16_t DB2LIN_TABLE[(DB_MUTE + DB_MUTE) * 2];
+
+ /* Liner to Log curve conversion table (for Attack rate). */
+ uint16_t AR_ADJUST_TABLE[1 << EG_BITS];
+
+ /* Phase incr table for Attack */
+ uint32_t dphaseARTable[16][16];
+ /* Phase incr table for Decay and Release */
+ uint32_t dphaseDRTable[16][16];
+
+ /* KSL + TL Table */
+ uint32_t tllTable[16][8][1 << TL_BITS][4];
+ int32_t rksTable[2][8][2];
+
+ /* Phase incr table for PG */
+ uint32_t dphaseTable[512][8][16];
+
+ uint16_t *waveform[2] = { fullsintable, halfsintable };
+
+ /* Phase delta for LFO */
+ uint32_t pm_dphase;
+ uint32_t am_dphase;
+
+ /* Table for AR to LogCurve. */
+ void makeAdjustTable()
+ {
+ int32_t i;
+
+ AR_ADJUST_TABLE[0] = (1 << EG_BITS);
+ for(i = 1; i < 128; i++)
+ AR_ADJUST_TABLE[i] = (uint16_t)((double)(1 << EG_BITS) - 1 - (1 << EG_BITS) * log((double)i) / log(128.0));
+ }
+
+
+ /* Table for dB(0 -- (1<= DB_MUTE) DB2LIN_TABLE[i] = 0;
+ DB2LIN_TABLE[i + DB_MUTE + DB_MUTE] = (int16_t)(-DB2LIN_TABLE[i]);
+ }
+ }
+
+ /* Liner(+0.0 - +1.0) to dB((1<> (20 - DP_BITS));
+ }
+
+ void makeTllTable(void)
+ {
+ static double kltable[16] = {
+ 0.00, 18.00, 24.00, 27.75, 30.00, 32.25, 33.75, 35.25,
+ 36.00, 37.50, 38.25, 39.00, 39.75, 40.50, 41.25, 42.00
+ };
+
+ int32_t tmp;
+ int32_t fnum, block, TL, KL;
+
+ for(fnum = 0; fnum < 16; fnum++)
+ for(block = 0; block < 8; block++)
+ for(TL = 0; TL < 64; TL++)
+ for(KL = 0; KL < 4; KL++) {
+ if(KL == 0) {
+ tllTable[fnum][block][TL][KL] = TL2EG(TL);
+ } else {
+ tmp = (int32_t)(kltable[fnum] - 6.000 * (7 - block));
+ if(tmp <= 0)
+ tllTable[fnum][block][TL][KL] = TL2EG(TL);
+ else
+ tllTable[fnum][block][TL][KL] = (uint32_t)((tmp >> (3 - KL)) / EG_STEP) + TL2EG(TL);
+ }
+ }
+ }
+
+ /* Rate Table for Attack */
+ void makeDphaseARTable(void)
+ {
+ int32_t AR, Rks, RM, RL;
+
+ for(AR = 0; AR < 16; AR++)
+ for(Rks = 0; Rks < 16; Rks++) {
+ RM = AR + (Rks >> 2);
+ RL = Rks & 3;
+ if(RM > 15)
+ RM = 15;
+ switch(AR) {
+ case 0:
+ dphaseARTable[AR][Rks] = 0;
+ break;
+ case 15:
+ dphaseARTable[AR][Rks] = 0;/*EG_DP_WIDTH;*/
+ break;
+ default:
+ dphaseARTable[AR][Rks] = rate_adjust((3 * (RL + 4) << (RM + 1)));
+ break;
+ }
+ }
+ }
+
+ /* Rate Table for Decay and Release */
+ void makeDphaseDRTable(void)
+ {
+ int32_t DR, Rks, RM, RL;
+
+ for(DR = 0; DR < 16; DR++)
+ for(Rks = 0; Rks < 16; Rks++) {
+ RM = DR + (Rks >> 2);
+ RL = Rks & 3;
+ if(RM > 15)
+ RM = 15;
+ switch(DR) {
+ case 0:
+ dphaseDRTable[DR][Rks] = 0;
+ break;
+ default:
+ dphaseDRTable[DR][Rks] = rate_adjust((RL + 4) << (RM - 1));
+ break;
+ }
+ }
+ }
+
+ void makeRksTable(void)
+ {
+
+ int32_t fnum8, block, KR;
+
+ for(fnum8 = 0; fnum8 < 2; fnum8++)
+ for(block = 0; block < 8; block++)
+ for(KR = 0; KR < 2; KR++) {
+ if(KR != 0)
+ rksTable[fnum8][block][KR] = (block << 1) + fnum8;
+ else
+ rksTable[fnum8][block][KR] = block >> 1;
+ }
+ }
+
+ int32_t Min(int32_t i, int32_t j)
+ {
+ if(i < j)
+ return i;
+ else
+ return j;
+ }
+
+ void internal_refresh()
+ {
+ makeDphaseTable();
+ makeDphaseARTable();
+ makeDphaseDRTable();
+ pm_dphase = (uint32_t)rate_adjust(PM_SPEED * PM_DP_WIDTH / (clk / 72));
+ am_dphase = (uint32_t)rate_adjust(AM_SPEED * AM_DP_WIDTH / (clk / 72));
+ }
+
+ void maketables(uint32_t c, uint32_t r)
+ {
+ clk = c;
+ makePmTable();
+ makeAmTable();
+ makeDB2LinTable();
+ makeAdjustTable();
+ makeTllTable();
+ makeRksTable();
+ makeSinTable();
+ //makeDefaultPatch ();
+
+ rate = r;
+ internal_refresh();
+ }
+ };
+}
\ No newline at end of file
diff --git a/Core/SoundMixer.cpp b/Core/SoundMixer.cpp
index ccf703a2..3a2d5fe1 100644
--- a/Core/SoundMixer.cpp
+++ b/Core/SoundMixer.cpp
@@ -163,7 +163,8 @@ int16_t SoundMixer::GetOutputVolume()
GetChannelOutput(AudioChannel::MMC5) * 40 +
GetChannelOutput(AudioChannel::Namco163) * 20 +
GetChannelOutput(AudioChannel::Sunsoft5B) * 15 +
- GetChannelOutput(AudioChannel::VRC6) * 75);
+ GetChannelOutput(AudioChannel::VRC6) * 75 +
+ GetChannelOutput(AudioChannel::VRC7));
}
void SoundMixer::AddDelta(AudioChannel channel, uint32_t time, int16_t delta)
diff --git a/Core/VRC7.h b/Core/VRC7.h
index 04b4ae48..ccecfcf8 100644
--- a/Core/VRC7.h
+++ b/Core/VRC7.h
@@ -2,11 +2,13 @@
#include "stdafx.h"
#include "BaseMapper.h"
#include "VrcIrq.h"
+#include "Vrc7Audio.h"
//incomplete - missing audio
class VRC7 : public BaseMapper
{
private:
+ Vrc7Audio _audio;
VrcIrq _irq;
uint8_t _controlFlags;
uint8_t _chrRegisters[8];
@@ -32,8 +34,10 @@ protected:
{
BaseMapper::StreamState(saving);
SnapshotInfo irq{ &_irq };
+ SnapshotInfo audio{ &_audio };
ArrayInfo chrRegisters = { _chrRegisters, 8 };
- Stream(_controlFlags, chrRegisters, irq);
+
+ Stream(_controlFlags, chrRegisters, irq, audio);
if(!saving) {
UpdatePrgRamAccess();
@@ -43,6 +47,7 @@ protected:
void ProcessCpuClock()
{
_irq.ProcessCpuClock();
+ _audio.Clock();
}
void UpdateState()
@@ -61,16 +66,16 @@ protected:
{
if(addr & 0x10 && (addr & 0xF010) != 0x9010) {
addr |= 0x08;
+ addr &= ~0x10;
}
- switch(addr & 0xF008) {
+ switch(addr & 0xF038) {
case 0x8000: SelectPRGPage(0, value & 0x3F); break;
case 0x8008: SelectPRGPage(1, value & 0x3F); break;
case 0x9000: SelectPRGPage(2, value & 0x3F); break;
- case 0x9010: break; //Audio
- case 0x9030: break; //Audio
-
+ case 0x9010: case 0x9030: _audio.WriteReg(addr, value); break;
+
case 0xA000: SelectCHRPage(0, value); break;
case 0xA008: SelectCHRPage(1, value); break;
case 0xB000: SelectCHRPage(2, value); break;
diff --git a/Core/Vrc7Audio.h b/Core/Vrc7Audio.h
new file mode 100644
index 00000000..46323541
--- /dev/null
+++ b/Core/Vrc7Audio.h
@@ -0,0 +1,56 @@
+#pragma once
+#include "stdafx.h"
+#include "BaseExpansionAudio.h"
+#include "Console.h"
+#include "OpllEmulator.h"
+
+class Vrc7Audio : public BaseExpansionAudio
+{
+private:
+ unique_ptr _opllEmulator;
+ uint8_t _currentReg;
+ int16_t _previousOutput;
+ double _clockTimer;
+
+protected:
+ void ClockAudio()
+ {
+ _clockTimer--;
+ if(_clockTimer <= 0) {
+ int16_t output = _opllEmulator->GetOutput();
+ APU::AddExpansionAudioDelta(AudioChannel::VRC7, output - _previousOutput);
+ _previousOutput = output;
+ _clockTimer = ((double)CPU::GetClockRate(Console::GetModel())) / 49716;
+ }
+ }
+
+ void StreamState(bool saving)
+ {
+ BaseExpansionAudio::StreamState(saving);
+
+ SnapshotInfo opllEmulator{ _opllEmulator.get() };
+ Stream(opllEmulator, _currentReg, _previousOutput, _clockTimer);
+ }
+
+public:
+ Vrc7Audio()
+ {
+ _previousOutput = 0;
+ _currentReg = 0;
+ _clockTimer = ((double)CPU::GetClockRate(Console::GetModel())) / 49716;
+
+ _opllEmulator.reset(new Vrc7Opll::OpllEmulator());
+ }
+
+ void WriteReg(uint16_t addr, uint8_t value)
+ {
+ switch(addr & 0xF030) {
+ case 0x9010:
+ _currentReg = value;
+ break;
+ case 0x9030:
+ _opllEmulator->WriteReg(_currentReg, value);
+ break;
+ }
+ }
+};
\ No newline at end of file
diff --git a/GUI.NET/Forms/Config/frmAudioConfig.Designer.cs b/GUI.NET/Forms/Config/frmAudioConfig.Designer.cs
index 869ed51b..b9cc8fd8 100644
--- a/GUI.NET/Forms/Config/frmAudioConfig.Designer.cs
+++ b/GUI.NET/Forms/Config/frmAudioConfig.Designer.cs
@@ -282,7 +282,6 @@
// trkVrc7Vol
//
this.trkVrc7Vol.Anchor = System.Windows.Forms.AnchorStyles.Top;
- this.trkVrc7Vol.Enabled = false;
this.trkVrc7Vol.Location = new System.Drawing.Point(231, 160);
this.trkVrc7Vol.Margin = new System.Windows.Forms.Padding(0);
this.trkVrc7Vol.Maximum = 100;