VRC7 audio support

This commit is contained in:
Souryo 2016-07-01 23:54:31 -04:00
parent d3e8baf530
commit 6660dc860d
14 changed files with 1471 additions and 8 deletions

View file

@ -163,6 +163,11 @@ uint32_t Console::GetCrc32()
}
}
NesModel Console::GetModel()
{
return Instance->_model;
}
void Console::Reset(bool softReset)
{
Movie::Stop();

View file

@ -70,6 +70,7 @@ class Console
static string GetROMPath();
static string GetRomName();
static uint32_t GetCrc32();
static NesModel GetModel();
static bool IsRunning();

View file

@ -591,6 +591,10 @@
<ClInclude Include="Vrc6Pulse.h" />
<ClInclude Include="Vrc6Saw.h" />
<ClInclude Include="VRC7.h" />
<ClInclude Include="Vrc7Audio.h" />
<ClInclude Include="OpllTables.h" />
<ClInclude Include="OpllEmulator.h" />
<ClInclude Include="OpllChannel.h" />
<ClInclude Include="VrcIrq.h" />
<ClInclude Include="VsControlManager.h" />
<ClInclude Include="VsSystem.h" />

View file

@ -682,6 +682,18 @@
<ClInclude Include="NsfPpu.h">
<Filter>Nes</Filter>
</ClInclude>
<ClInclude Include="Vrc7Audio.h">
<Filter>Nes\Mappers\VRC</Filter>
</ClInclude>
<ClInclude Include="OpllEmulator.h">
<Filter>Nes\Mappers\VRC</Filter>
</ClInclude>
<ClInclude Include="OpllTables.h">
<Filter>Nes\Mappers\VRC</Filter>
</ClInclude>
<ClInclude Include="OpllChannel.h">
<Filter>Nes\Mappers\VRC</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="stdafx.cpp">

View file

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

View file

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

View file

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

424
Core/OpllChannel.h Normal file
View file

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

541
Core/OpllEmulator.h Normal file
View file

@ -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<OpllTables> 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<uint8_t> lowFreq{ LowFreq, 6 };
ArrayInfo<uint8_t> hiFreq{ HiFreq, 6 };
ArrayInfo<uint8_t> instVol{ InstVol, 6 };
ArrayInfo<uint8_t> custInst{ CustInst, 8 };
ArrayInfo<int32_t> slotOnFlag{ slot_on_flag, 12 };
ArrayInfo<int32_t> patchNumber{ patch_number, 6 };
ArrayInfo<int32_t> 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;
}
};
}

399
Core/OpllTables.h Normal file
View file

@ -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_BITS)-1) to Liner(0 -- DB2LIN_AMP_WIDTH) */
void makeDB2LinTable()
{
int32_t i;
for(i = 0; i < DB_MUTE + DB_MUTE; i++) {
DB2LIN_TABLE[i] = (int16_t)((double)((1 << DB2LIN_AMP_BITS) - 1) * pow(10.0, -(double)i * DB_STEP / 20));
if(i >= 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<<DB_BITS) - 1 -- 0) */
int32_t lin2db(double d)
{
if(d == 0)
return (DB_MUTE - 1);
else
return Min(-(int32_t)(20.0 * log10(d) / DB_STEP), DB_MUTE - 1); /* 0 -- 127 */
}
/* Sin Table */
void makeSinTable(void)
{
int32_t i;
for(i = 0; i < PG_WIDTH / 4; i++) {
fullsintable[i] = (uint16_t)lin2db(sin(2.0 * PI * i / PG_WIDTH));
}
for(i = 0; i < PG_WIDTH / 4; i++) {
fullsintable[PG_WIDTH / 2 - 1 - i] = fullsintable[i];
}
for(i = 0; i < PG_WIDTH / 2; i++) {
fullsintable[PG_WIDTH / 2 + i] = (uint16_t)(DB_MUTE + DB_MUTE + fullsintable[i]);
}
for(i = 0; i < PG_WIDTH / 2; i++)
halfsintable[i] = fullsintable[i];
for(i = PG_WIDTH / 2; i < PG_WIDTH; i++)
halfsintable[i] = fullsintable[0];
}
/* Table for Pitch Modulator */
void makePmTable(void)
{
int32_t i;
for(i = 0; i < PM_PG_WIDTH; i++)
pmtable[i] = (int32_t)((double)PM_AMP * pow(2.0, (double)PM_DEPTH * sin(2.0 * PI * i / PM_PG_WIDTH) / 1200));
}
/* Table for Amp Modulator */
void makeAmTable(void)
{
int32_t i;
for(i = 0; i < AM_PG_WIDTH; i++)
amtable[i] = (int32_t)((double)AM_DEPTH / 2 / DB_STEP * (1.0 + sin(2.0 * PI * i / PM_PG_WIDTH)));
}
/* Phase increment counter table */
void makeDphaseTable(void)
{
uint32_t fnum, block, ML;
uint32_t mltable[16] =
{ 1, 1 * 2, 2 * 2, 3 * 2, 4 * 2, 5 * 2, 6 * 2, 7 * 2, 8 * 2, 9 * 2, 10 * 2, 10 * 2, 12 * 2, 12 * 2, 15 * 2, 15 * 2 };
for(fnum = 0; fnum < 512; fnum++)
for(block = 0; block < 8; block++)
for(ML = 0; ML < 16; ML++)
dphaseTable[fnum][block][ML] = rate_adjust(((fnum * mltable[ML]) << block) >> (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();
}
};
}

View file

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

View file

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

56
Core/Vrc7Audio.h Normal file
View file

@ -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<Vrc7Opll::OpllEmulator> _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;
}
}
};

View file

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