VRC7 audio support
This commit is contained in:
parent
d3e8baf530
commit
6660dc860d
14 changed files with 1471 additions and 8 deletions
|
@ -163,6 +163,11 @@ uint32_t Console::GetCrc32()
|
|||
}
|
||||
}
|
||||
|
||||
NesModel Console::GetModel()
|
||||
{
|
||||
return Instance->_model;
|
||||
}
|
||||
|
||||
void Console::Reset(bool softReset)
|
||||
{
|
||||
Movie::Stop();
|
||||
|
|
|
@ -70,6 +70,7 @@ class Console
|
|||
static string GetROMPath();
|
||||
static string GetRomName();
|
||||
static uint32_t GetCrc32();
|
||||
static NesModel GetModel();
|
||||
|
||||
static bool IsRunning();
|
||||
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
424
Core/OpllChannel.h
Normal 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
541
Core/OpllEmulator.h
Normal 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
399
Core/OpllTables.h
Normal 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();
|
||||
}
|
||||
};
|
||||
}
|
|
@ -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)
|
||||
|
|
15
Core/VRC7.h
15
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<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
56
Core/Vrc7Audio.h
Normal 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;
|
||||
}
|
||||
}
|
||||
};
|
1
GUI.NET/Forms/Config/frmAudioConfig.Designer.cs
generated
1
GUI.NET/Forms/Config/frmAudioConfig.Designer.cs
generated
|
@ -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;
|
||||
|
|
Loading…
Add table
Reference in a new issue