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;