mirror of
https://github.com/tildearrow/furnace.git
synced 2024-11-04 20:05:05 +00:00
Merge branch 'master' of https://github.com/tildearrow/furnace into es5506_alt
* 'master' of https://github.com/tildearrow/furnace: (26 commits) prepare for #38 NES: DPCM work! fix bug caused by new renderSamples approach NES: prepare for DPCM OPZ: more fixed frequency mode fixes OPZ: possibly fix fixed freq emulation OPZ: fix muting OPL: fix panning damn it per-channel oscilloscope, part 10 NES: NSFPlay per-channel osc NES: NSFPlay muting NES: now fix tri_mute NES: finally fix typo in playback engine NES: wire up NSFplay now fix it damn it now move these files add readme convertir de Shift-JIS a UTF-8 ... # Conflicts: # src/engine/engine.cpp # src/engine/engine.h # src/engine/platform/nes.cpp # src/engine/vgmOps.cpp # src/gui/stats.cpp
This commit is contained in:
commit
e136db6d7a
53 changed files with 3990 additions and 393 deletions
|
@ -250,6 +250,13 @@ src/engine/platform/sound/nes/mmc5.c
|
||||||
src/engine/platform/sound/vera_psg.c
|
src/engine/platform/sound/vera_psg.c
|
||||||
src/engine/platform/sound/vera_pcm.c
|
src/engine/platform/sound/vera_pcm.c
|
||||||
|
|
||||||
|
src/engine/platform/sound/nes_nsfplay/nes_apu.cpp
|
||||||
|
src/engine/platform/sound/nes_nsfplay/nes_dmc.cpp
|
||||||
|
src/engine/platform/sound/nes_nsfplay/nes_fds.cpp
|
||||||
|
src/engine/platform/sound/nes_nsfplay/nes_mmc5.cpp
|
||||||
|
src/engine/platform/sound/nes_nsfplay/nes_n106.cpp
|
||||||
|
src/engine/platform/sound/nes_nsfplay/nes_vrc6.cpp
|
||||||
|
|
||||||
src/engine/platform/sound/c64/sid.cc
|
src/engine/platform/sound/c64/sid.cc
|
||||||
src/engine/platform/sound/c64/voice.cc
|
src/engine/platform/sound/c64/voice.cc
|
||||||
src/engine/platform/sound/c64/wave.cc
|
src/engine/platform/sound/c64/wave.cc
|
||||||
|
|
1
TODO.md
1
TODO.md
|
@ -36,7 +36,6 @@
|
||||||
- store edit/followOrders/followPattern state in config
|
- store edit/followOrders/followPattern state in config
|
||||||
- add ability to select a column by double clicking
|
- add ability to select a column by double clicking
|
||||||
- add ability to move selection by dragging
|
- add ability to move selection by dragging
|
||||||
- NSFPlay core for NES
|
|
||||||
- settings: OK/Cancel buttons should be always visible
|
- settings: OK/Cancel buttons should be always visible
|
||||||
- Apply button in settings
|
- Apply button in settings
|
||||||
- better FM chip names (number and codename)
|
- better FM chip names (number and codename)
|
||||||
|
|
3
extern/imgui_patched/imgui_impl_sdl.cpp
vendored
3
extern/imgui_patched/imgui_impl_sdl.cpp
vendored
|
@ -300,6 +300,9 @@ bool ImGui_ImplSDL2_ProcessEvent(const SDL_Event* event)
|
||||||
{
|
{
|
||||||
float wheel_x = (event->wheel.x > 0) ? 1.0f : (event->wheel.x < 0) ? -1.0f : 0.0f;
|
float wheel_x = (event->wheel.x > 0) ? 1.0f : (event->wheel.x < 0) ? -1.0f : 0.0f;
|
||||||
float wheel_y = (event->wheel.y > 0) ? 1.0f : (event->wheel.y < 0) ? -1.0f : 0.0f;
|
float wheel_y = (event->wheel.y > 0) ? 1.0f : (event->wheel.y < 0) ? -1.0f : 0.0f;
|
||||||
|
#ifdef __APPLE__
|
||||||
|
wheel_x = -wheel_x;
|
||||||
|
#endif
|
||||||
io.AddMouseWheelEvent(wheel_x, wheel_y);
|
io.AddMouseWheelEvent(wheel_x, wheel_y);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,3 +19,7 @@ also known as Famicom. It is a five-channel PSG: first two channels play pulse w
|
||||||
- `x` is the time.
|
- `x` is the time.
|
||||||
- `y` is the shift.
|
- `y` is the shift.
|
||||||
- set to 0 to disable it.
|
- set to 0 to disable it.
|
||||||
|
- `18xx`: set PCM channel mode.
|
||||||
|
- `00`: PCM (software).
|
||||||
|
- `01`: DPCM (hardware).
|
||||||
|
- when in DPCM mode, samples will sound muffled (due to its nature), availables pitches are limited and loop point is ignored.
|
|
@ -675,6 +675,26 @@ size | description
|
||||||
1 | parameter 2
|
1 | parameter 2
|
||||||
1 | parameter 3
|
1 | parameter 3
|
||||||
1 | parameter 4
|
1 | parameter 4
|
||||||
|
--- | **additional macro mode flags** (>=84)
|
||||||
|
1 | volume macro mode
|
||||||
|
1 | duty macro mode
|
||||||
|
1 | wave macro mode
|
||||||
|
1 | pitch macro mode
|
||||||
|
1 | extra 1 macro mode
|
||||||
|
1 | extra 2 macro mode
|
||||||
|
1 | extra 3 macro mode
|
||||||
|
1 | alg macro mode
|
||||||
|
1 | fb macro mode
|
||||||
|
1 | fms macro mode
|
||||||
|
1 | ams macro mode
|
||||||
|
1 | left panning macro mode
|
||||||
|
1 | right panning macro mode
|
||||||
|
1 | phase reset macro mode
|
||||||
|
1 | extra 4 macro mode
|
||||||
|
1 | extra 5 macro mode
|
||||||
|
1 | extra 6 macro mode
|
||||||
|
1 | extra 7 macro mode
|
||||||
|
1 | extra 8 macro mode
|
||||||
--- | **extra C64 data** (>=89)
|
--- | **extra C64 data** (>=89)
|
||||||
1 | don't test/gate before new note
|
1 | don't test/gate before new note
|
||||||
```
|
```
|
||||||
|
|
|
@ -64,9 +64,24 @@ enum DivDispatchCmds {
|
||||||
DIV_CMD_FM_LFO, // (speed)
|
DIV_CMD_FM_LFO, // (speed)
|
||||||
DIV_CMD_FM_LFO_WAVE, // (waveform)
|
DIV_CMD_FM_LFO_WAVE, // (waveform)
|
||||||
DIV_CMD_FM_TL, // (op, value)
|
DIV_CMD_FM_TL, // (op, value)
|
||||||
|
DIV_CMD_FM_AM, // (op, value)
|
||||||
DIV_CMD_FM_AR, // (op, value)
|
DIV_CMD_FM_AR, // (op, value)
|
||||||
|
DIV_CMD_FM_DR, // (op, value)
|
||||||
|
DIV_CMD_FM_SL, // (op, value)
|
||||||
|
DIV_CMD_FM_D2R, // (op, value)
|
||||||
|
DIV_CMD_FM_RR, // (op, value)
|
||||||
|
DIV_CMD_FM_DT, // (op, value)
|
||||||
|
DIV_CMD_FM_DT2, // (op, value)
|
||||||
|
DIV_CMD_FM_RS, // (op, value)
|
||||||
|
DIV_CMD_FM_KSR, // (op, value)
|
||||||
|
DIV_CMD_FM_VIB, // (op, value)
|
||||||
|
DIV_CMD_FM_SUS, // (op, value)
|
||||||
|
DIV_CMD_FM_WS, // (op, value)
|
||||||
|
DIV_CMD_FM_SSG, // (op, value)
|
||||||
DIV_CMD_FM_FB, // (value)
|
DIV_CMD_FM_FB, // (value)
|
||||||
DIV_CMD_FM_MULT, // (op, value)
|
DIV_CMD_FM_MULT, // (op, value)
|
||||||
|
DIV_CMD_FM_FINE, // (op, value)
|
||||||
|
DIV_CMD_FM_FIXFREQ, // (op, value)
|
||||||
DIV_CMD_FM_EXTCH, // (enabled)
|
DIV_CMD_FM_EXTCH, // (enabled)
|
||||||
DIV_CMD_FM_AM_DEPTH, // (depth)
|
DIV_CMD_FM_AM_DEPTH, // (depth)
|
||||||
DIV_CMD_FM_PM_DEPTH, // (depth)
|
DIV_CMD_FM_PM_DEPTH, // (depth)
|
||||||
|
@ -155,6 +170,8 @@ enum DivDispatchCmds {
|
||||||
DIV_CMD_ES5506_FILTER_MODE, // (value)
|
DIV_CMD_ES5506_FILTER_MODE, // (value)
|
||||||
DIV_CMD_ES5506_FILTER_K1, // (value)
|
DIV_CMD_ES5506_FILTER_K1, // (value)
|
||||||
DIV_CMD_ES5506_FILTER_K2, // (value)
|
DIV_CMD_ES5506_FILTER_K2, // (value)
|
||||||
|
DIV_CMD_ES5506_FILTER_K1_SLIDE, // (value, negative)
|
||||||
|
DIV_CMD_ES5506_FILTER_K2_SLIDE, // (value, negative)
|
||||||
DIV_CMD_ES5506_ENVELOPE_COUNT, // (count)
|
DIV_CMD_ES5506_ENVELOPE_COUNT, // (count)
|
||||||
DIV_CMD_ES5506_ENVELOPE_LVRAMP, // (ramp)
|
DIV_CMD_ES5506_ENVELOPE_LVRAMP, // (ramp)
|
||||||
DIV_CMD_ES5506_ENVELOPE_RVRAMP, // (ramp)
|
DIV_CMD_ES5506_ENVELOPE_RVRAMP, // (ramp)
|
||||||
|
@ -218,11 +235,13 @@ struct DivRegWrite {
|
||||||
struct DivDispatchOscBuffer {
|
struct DivDispatchOscBuffer {
|
||||||
unsigned int rate;
|
unsigned int rate;
|
||||||
unsigned short needle;
|
unsigned short needle;
|
||||||
|
unsigned short readNeedle;
|
||||||
short data[65536];
|
short data[65536];
|
||||||
|
|
||||||
DivDispatchOscBuffer():
|
DivDispatchOscBuffer():
|
||||||
rate(65536),
|
rate(65536),
|
||||||
needle(0) {
|
needle(0),
|
||||||
|
readNeedle(0) {
|
||||||
memset(data,0,65536*sizeof(short));
|
memset(data,0,65536*sizeof(short));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -447,6 +466,26 @@ class DivDispatch {
|
||||||
*/
|
*/
|
||||||
virtual const char** getRegisterSheet();
|
virtual const char** getRegisterSheet();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get sample memory buffer.
|
||||||
|
*/
|
||||||
|
virtual const void* getSampleMem(int index = 0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get sample memory capacity.
|
||||||
|
*/
|
||||||
|
virtual size_t getSampleMemCapacity(int index = 0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get sample memory usage.
|
||||||
|
*/
|
||||||
|
virtual size_t getSampleMemUsage(int index = 0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render samples into sample memory.
|
||||||
|
*/
|
||||||
|
virtual void renderSamples();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* initialize this DivDispatch.
|
* initialize this DivDispatch.
|
||||||
* @param parent the parent DivEngine.
|
* @param parent the parent DivEngine.
|
||||||
|
|
|
@ -190,6 +190,7 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
|
||||||
break;
|
break;
|
||||||
case DIV_SYSTEM_NES:
|
case DIV_SYSTEM_NES:
|
||||||
dispatch=new DivPlatformNES;
|
dispatch=new DivPlatformNES;
|
||||||
|
((DivPlatformNES*)dispatch)->setNSFPlay(eng->getConfInt("nesCore",0)==1);
|
||||||
break;
|
break;
|
||||||
case DIV_SYSTEM_C64_6581:
|
case DIV_SYSTEM_C64_6581:
|
||||||
dispatch=new DivPlatformC64;
|
dispatch=new DivPlatformC64;
|
||||||
|
|
|
@ -507,148 +507,12 @@ void DivEngine::renderSamples() {
|
||||||
song.sample[i]->render();
|
song.sample[i]->render();
|
||||||
}
|
}
|
||||||
|
|
||||||
// step 2: allocate ADPCM-A samples
|
// step 2: render samples to dispatch
|
||||||
if (adpcmAMem==NULL) adpcmAMem=new unsigned char[16777216];
|
for (int i=0; i<song.systemLen; i++) {
|
||||||
|
if (disCont[i].dispatch!=NULL) {
|
||||||
size_t memPos=0;
|
disCont[i].dispatch->renderSamples();
|
||||||
for (int i=0; i<song.sampleLen; i++) {
|
|
||||||
DivSample* s=song.sample[i];
|
|
||||||
int paddedLen=(s->lengthA+255)&(~0xff);
|
|
||||||
if ((memPos&0xf00000)!=((memPos+paddedLen)&0xf00000)) {
|
|
||||||
memPos=(memPos+0xfffff)&0xf00000;
|
|
||||||
}
|
|
||||||
if (memPos>=16777216) {
|
|
||||||
logW("out of ADPCM-A memory for sample %d!",i);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (memPos+paddedLen>=16777216) {
|
|
||||||
memcpy(adpcmAMem+memPos,s->dataA,16777216-memPos);
|
|
||||||
logW("out of ADPCM-A memory for sample %d!",i);
|
|
||||||
} else {
|
|
||||||
memcpy(adpcmAMem+memPos,s->dataA,paddedLen);
|
|
||||||
}
|
|
||||||
s->offA=memPos;
|
|
||||||
memPos+=paddedLen;
|
|
||||||
}
|
|
||||||
adpcmAMemLen=memPos+256;
|
|
||||||
|
|
||||||
// step 2: allocate ADPCM-B samples
|
|
||||||
if (adpcmBMem==NULL) adpcmBMem=new unsigned char[16777216];
|
|
||||||
|
|
||||||
memPos=0;
|
|
||||||
for (int i=0; i<song.sampleLen; i++) {
|
|
||||||
DivSample* s=song.sample[i];
|
|
||||||
int paddedLen=(s->lengthB+255)&(~0xff);
|
|
||||||
if ((memPos&0xf00000)!=((memPos+paddedLen)&0xf00000)) {
|
|
||||||
memPos=(memPos+0xfffff)&0xf00000;
|
|
||||||
}
|
|
||||||
if (memPos>=16777216) {
|
|
||||||
logW("out of ADPCM-B memory for sample %d!",i);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (memPos+paddedLen>=16777216) {
|
|
||||||
memcpy(adpcmBMem+memPos,s->dataB,16777216-memPos);
|
|
||||||
logW("out of ADPCM-B memory for sample %d!",i);
|
|
||||||
} else {
|
|
||||||
memcpy(adpcmBMem+memPos,s->dataB,paddedLen);
|
|
||||||
}
|
|
||||||
s->offB=memPos;
|
|
||||||
memPos+=paddedLen;
|
|
||||||
}
|
|
||||||
adpcmBMemLen=memPos+256;
|
|
||||||
|
|
||||||
// step 4: allocate qsound pcm samples
|
|
||||||
if (qsoundMem==NULL) qsoundMem=new unsigned char[16777216];
|
|
||||||
memset(qsoundMem,0,16777216);
|
|
||||||
|
|
||||||
memPos=0;
|
|
||||||
for (int i=0; i<song.sampleLen; i++) {
|
|
||||||
DivSample* s=song.sample[i];
|
|
||||||
int length=s->length8;
|
|
||||||
if (length>65536-16) {
|
|
||||||
length=65536-16;
|
|
||||||
}
|
|
||||||
if ((memPos&0xff0000)!=((memPos+length)&0xff0000)) {
|
|
||||||
memPos=(memPos+0xffff)&0xff0000;
|
|
||||||
}
|
|
||||||
if (memPos>=16777216) {
|
|
||||||
logW("out of QSound PCM memory for sample %d!",i);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (memPos+length>=16777216) {
|
|
||||||
for (unsigned int i=0; i<16777216-(memPos+length); i++) {
|
|
||||||
qsoundMem[(memPos+i)^0x8000]=s->data8[i];
|
|
||||||
}
|
|
||||||
logW("out of QSound PCM memory for sample %d!",i);
|
|
||||||
} else {
|
|
||||||
for (int i=0; i<length; i++) {
|
|
||||||
qsoundMem[(memPos+i)^0x8000]=s->data8[i];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s->offQSound=memPos^0x8000;
|
|
||||||
memPos+=length+16;
|
|
||||||
}
|
|
||||||
qsoundMemLen=memPos+256;
|
|
||||||
|
|
||||||
// step 4: allocate x1-010 pcm samples
|
|
||||||
if (x1_010Mem==NULL) x1_010Mem=new unsigned char[1048576];
|
|
||||||
memset(x1_010Mem,0,1048576);
|
|
||||||
|
|
||||||
memPos=0;
|
|
||||||
for (int i=0; i<song.sampleLen; i++) {
|
|
||||||
DivSample* s=song.sample[i];
|
|
||||||
int paddedLen=(s->length8+4095)&(~0xfff);
|
|
||||||
// fit sample bank size to 128KB for Seta 2 external bankswitching logic (not emulated yet!)
|
|
||||||
if (paddedLen>131072) {
|
|
||||||
paddedLen=131072;
|
|
||||||
}
|
|
||||||
if ((memPos&0xfe0000)!=((memPos+paddedLen)&0xfe0000)) {
|
|
||||||
memPos=(memPos+0x1ffff)&0xfe0000;
|
|
||||||
}
|
|
||||||
if (memPos>=1048576) {
|
|
||||||
logW("out of X1-010 memory for sample %d!",i);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (memPos+paddedLen>=1048576) {
|
|
||||||
memcpy(x1_010Mem+memPos,s->data8,1048576-memPos);
|
|
||||||
logW("out of X1-010 memory for sample %d!",i);
|
|
||||||
} else {
|
|
||||||
memcpy(x1_010Mem+memPos,s->data8,paddedLen);
|
|
||||||
}
|
|
||||||
s->offX1_010=memPos;
|
|
||||||
memPos+=paddedLen;
|
|
||||||
}
|
|
||||||
x1_010MemLen=memPos+256;
|
|
||||||
|
|
||||||
// step 5: allocate ES5506 pcm samples (forces depth for all samples to 16 bit due to chip limitation, compressed sample just LSB disconnected)
|
|
||||||
if (es5506Mem==NULL) es5506Mem=new signed short[16777216/sizeof(short)]; // 2Mword * 4 banks
|
|
||||||
memset(es5506Mem,0,16777216);
|
|
||||||
|
|
||||||
memPos=128;
|
|
||||||
for (int i=0; i<song.sampleLen; i++) {
|
|
||||||
DivSample* s=song.sample[i];
|
|
||||||
unsigned int length=s->length16;
|
|
||||||
// fit sample size to single bank size
|
|
||||||
if (length>(2097152-64)*sizeof(short)) {
|
|
||||||
length=(2097152-64)*sizeof(short);
|
|
||||||
}
|
|
||||||
if ((memPos&0xc00000)!=((memPos+length+128)&0xc00000)) {
|
|
||||||
memPos=((memPos+0x3fffff)&0xc00000)+128;
|
|
||||||
}
|
|
||||||
if (memPos>=(16777216-128)) {
|
|
||||||
logW("out of ES5506 memory for sample %d!",i);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (memPos+length>=(16777216-128)) {
|
|
||||||
memcpy(es5506Mem+(memPos/sizeof(short)),s->data16,16777216-memPos-128);
|
|
||||||
logW("out of ES5506 memory for sample %d!",i);
|
|
||||||
} else {
|
|
||||||
memcpy(es5506Mem+(memPos/sizeof(short)),s->data16,length);
|
|
||||||
}
|
|
||||||
s->offES5506=memPos;
|
|
||||||
memPos+=length;
|
|
||||||
}
|
|
||||||
es5506MemLen=memPos+256;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String DivEngine::encodeSysDesc(std::vector<int>& desc) {
|
String DivEngine::encodeSysDesc(std::vector<int>& desc) {
|
||||||
|
@ -756,11 +620,11 @@ void DivEngine::createNew(const int* description) {
|
||||||
initSongWithDesc(description);
|
initSongWithDesc(description);
|
||||||
}
|
}
|
||||||
recalcChans();
|
recalcChans();
|
||||||
renderSamples();
|
|
||||||
saveLock.unlock();
|
saveLock.unlock();
|
||||||
BUSY_END;
|
BUSY_END;
|
||||||
initDispatch();
|
initDispatch();
|
||||||
BUSY_BEGIN;
|
BUSY_BEGIN;
|
||||||
|
renderSamples();
|
||||||
reset();
|
reset();
|
||||||
BUSY_END;
|
BUSY_END;
|
||||||
}
|
}
|
||||||
|
@ -998,6 +862,11 @@ DivSample* DivEngine::getSample(int index) {
|
||||||
return song.sample[index];
|
return song.sample[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DivDispatch* DivEngine::getDispatch(int index) {
|
||||||
|
if (index<0 || index>=song.systemLen) return NULL;
|
||||||
|
return disCont[index].dispatch;
|
||||||
|
}
|
||||||
|
|
||||||
void DivEngine::setLoops(int loops) {
|
void DivEngine::setLoops(int loops) {
|
||||||
remainingLoops=loops;
|
remainingLoops=loops;
|
||||||
}
|
}
|
||||||
|
@ -2511,6 +2380,7 @@ bool DivEngine::switchMaster() {
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
renderSamples();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2808,9 +2678,9 @@ bool DivEngine::init() {
|
||||||
|
|
||||||
// set default system preset
|
// set default system preset
|
||||||
if (!hasLoadedSomething) {
|
if (!hasLoadedSomething) {
|
||||||
logI("setting");
|
logD("setting default preset");
|
||||||
std::vector<int> preset=decodeSysDesc(getConfString("initialSys",""));
|
std::vector<int> preset=decodeSysDesc(getConfString("initialSys",""));
|
||||||
logI("preset size %ld",preset.size());
|
logD("preset size %ld",preset.size());
|
||||||
if (preset.size()>0 && (preset.size()&3)==0) {
|
if (preset.size()>0 && (preset.size()&3)==0) {
|
||||||
preset.push_back(0);
|
preset.push_back(0);
|
||||||
initSongWithDesc(preset.data());
|
initSongWithDesc(preset.data());
|
||||||
|
@ -2856,6 +2726,7 @@ bool DivEngine::init() {
|
||||||
oscBuf[1]=new float[32768];
|
oscBuf[1]=new float[32768];
|
||||||
|
|
||||||
initDispatch();
|
initDispatch();
|
||||||
|
renderSamples();
|
||||||
reset();
|
reset();
|
||||||
active=true;
|
active=true;
|
||||||
|
|
||||||
|
|
|
@ -424,6 +424,7 @@ class DivEngine {
|
||||||
DivInstrument* getIns(int index, DivInstrumentType fallbackType=DIV_INS_FM);
|
DivInstrument* getIns(int index, DivInstrumentType fallbackType=DIV_INS_FM);
|
||||||
DivWavetable* getWave(int index);
|
DivWavetable* getWave(int index);
|
||||||
DivSample* getSample(int index);
|
DivSample* getSample(int index);
|
||||||
|
DivDispatch* getDispatch(int index);
|
||||||
// parse system setup description
|
// parse system setup description
|
||||||
String encodeSysDesc(std::vector<int>& desc);
|
String encodeSysDesc(std::vector<int>& desc);
|
||||||
std::vector<int> decodeSysDesc(String desc);
|
std::vector<int> decodeSysDesc(String desc);
|
||||||
|
@ -851,21 +852,6 @@ class DivEngine {
|
||||||
// terminate the engine.
|
// terminate the engine.
|
||||||
bool quit();
|
bool quit();
|
||||||
|
|
||||||
unsigned char* adpcmAMem;
|
|
||||||
size_t adpcmAMemLen;
|
|
||||||
unsigned char* adpcmBMem;
|
|
||||||
size_t adpcmBMemLen;
|
|
||||||
unsigned char* qsoundMem;
|
|
||||||
size_t qsoundMemLen;
|
|
||||||
unsigned char* qsoundAMem;
|
|
||||||
size_t qsoundAMemLen;
|
|
||||||
unsigned char* dpcmMem;
|
|
||||||
size_t dpcmMemLen;
|
|
||||||
unsigned char* x1_010Mem;
|
|
||||||
size_t x1_010MemLen;
|
|
||||||
signed short* es5506Mem;
|
|
||||||
size_t es5506MemLen;
|
|
||||||
|
|
||||||
DivEngine():
|
DivEngine():
|
||||||
output(NULL),
|
output(NULL),
|
||||||
exportThread(NULL),
|
exportThread(NULL),
|
||||||
|
@ -939,21 +925,7 @@ class DivEngine {
|
||||||
oscSize(1),
|
oscSize(1),
|
||||||
oscReadPos(0),
|
oscReadPos(0),
|
||||||
oscWritePos(0),
|
oscWritePos(0),
|
||||||
tickMult(1),
|
tickMult(1) {
|
||||||
adpcmAMem(NULL),
|
|
||||||
adpcmAMemLen(0),
|
|
||||||
adpcmBMem(NULL),
|
|
||||||
adpcmBMemLen(0),
|
|
||||||
qsoundMem(NULL),
|
|
||||||
qsoundMemLen(0),
|
|
||||||
qsoundAMem(NULL),
|
|
||||||
qsoundAMemLen(0),
|
|
||||||
dpcmMem(NULL),
|
|
||||||
dpcmMemLen(0),
|
|
||||||
x1_010Mem(NULL),
|
|
||||||
x1_010MemLen(0),
|
|
||||||
es5506Mem(NULL),
|
|
||||||
es5506MemLen(0) {
|
|
||||||
memset(isMuted,0,DIV_MAX_CHANS*sizeof(bool));
|
memset(isMuted,0,DIV_MAX_CHANS*sizeof(bool));
|
||||||
memset(keyHit,0,DIV_MAX_CHANS*sizeof(bool));
|
memset(keyHit,0,DIV_MAX_CHANS*sizeof(bool));
|
||||||
memset(dispatchChanOfChan,0,DIV_MAX_CHANS*sizeof(int));
|
memset(dispatchChanOfChan,0,DIV_MAX_CHANS*sizeof(int));
|
||||||
|
|
|
@ -899,12 +899,14 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
|
||||||
song.unload();
|
song.unload();
|
||||||
song=ds;
|
song=ds;
|
||||||
recalcChans();
|
recalcChans();
|
||||||
renderSamples();
|
|
||||||
saveLock.unlock();
|
saveLock.unlock();
|
||||||
BUSY_END;
|
BUSY_END;
|
||||||
if (active) {
|
if (active) {
|
||||||
initDispatch();
|
initDispatch();
|
||||||
syncReset();
|
BUSY_BEGIN;
|
||||||
|
renderSamples();
|
||||||
|
reset();
|
||||||
|
BUSY_END;
|
||||||
}
|
}
|
||||||
} catch (EndOfFileException& e) {
|
} catch (EndOfFileException& e) {
|
||||||
logE("premature end of file!");
|
logE("premature end of file!");
|
||||||
|
@ -1603,12 +1605,14 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
|
||||||
song.unload();
|
song.unload();
|
||||||
song=ds;
|
song=ds;
|
||||||
recalcChans();
|
recalcChans();
|
||||||
renderSamples();
|
|
||||||
saveLock.unlock();
|
saveLock.unlock();
|
||||||
BUSY_END;
|
BUSY_END;
|
||||||
if (active) {
|
if (active) {
|
||||||
initDispatch();
|
initDispatch();
|
||||||
syncReset();
|
BUSY_BEGIN;
|
||||||
|
renderSamples();
|
||||||
|
reset();
|
||||||
|
BUSY_END;
|
||||||
}
|
}
|
||||||
} catch (EndOfFileException& e) {
|
} catch (EndOfFileException& e) {
|
||||||
logE("premature end of file!");
|
logE("premature end of file!");
|
||||||
|
@ -2018,12 +2022,14 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) {
|
||||||
song.unload();
|
song.unload();
|
||||||
song=ds;
|
song=ds;
|
||||||
recalcChans();
|
recalcChans();
|
||||||
renderSamples();
|
|
||||||
saveLock.unlock();
|
saveLock.unlock();
|
||||||
BUSY_END;
|
BUSY_END;
|
||||||
if (active) {
|
if (active) {
|
||||||
initDispatch();
|
initDispatch();
|
||||||
syncReset();
|
BUSY_BEGIN;
|
||||||
|
renderSamples();
|
||||||
|
reset();
|
||||||
|
BUSY_END;
|
||||||
}
|
}
|
||||||
success=true;
|
success=true;
|
||||||
} catch (EndOfFileException& e) {
|
} catch (EndOfFileException& e) {
|
||||||
|
|
|
@ -141,6 +141,22 @@ const char** DivDispatch::getRegisterSheet() {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const void* DivDispatch::getSampleMem(int index) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t DivDispatch::getSampleMemCapacity(int index) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t DivDispatch::getSampleMemUsage(int index) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DivDispatch::renderSamples() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
int DivDispatch::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
|
int DivDispatch::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -135,6 +135,18 @@ const char* DivPlatformES5506::getEffectName(unsigned char effect) {
|
||||||
case 0x27:
|
case 0x27:
|
||||||
return "27xx: Set envelope k2 ramp (signed, slower)";
|
return "27xx: Set envelope k2 ramp (signed, slower)";
|
||||||
break;
|
break;
|
||||||
|
case 0x28:
|
||||||
|
return "28xx: Set filter K1 slide up";
|
||||||
|
break;
|
||||||
|
case 0x29:
|
||||||
|
return "29xx: Set filter K1 slide down";
|
||||||
|
break;
|
||||||
|
case 0x2a:
|
||||||
|
return "28xx: Set filter K2 slide up";
|
||||||
|
break;
|
||||||
|
case 0x2b:
|
||||||
|
return "29xx: Set filter K2 slide down";
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
if ((effect&0xf0)==0x30) {
|
if ((effect&0xf0)==0x30) {
|
||||||
return "3xxx: Set filter K1";
|
return "3xxx: Set filter K1";
|
||||||
|
@ -254,6 +266,7 @@ void DivPlatformES5506::tick(bool sysTick) {
|
||||||
for (int i=0; i<=chanMax; i++) {
|
for (int i=0; i<=chanMax; i++) {
|
||||||
chan[i].std.next();
|
chan[i].std.next();
|
||||||
DivInstrument* ins=parent->getIns(chan[i].ins);
|
DivInstrument* ins=parent->getIns(chan[i].ins);
|
||||||
|
signed int k1=chan[i].k1Prev,k2=chan[i].k2Prev;
|
||||||
// volume/panning macros
|
// volume/panning macros
|
||||||
if (chan[i].std.vol.had) {
|
if (chan[i].std.vol.had) {
|
||||||
const unsigned int nextVol=((chan[i].vol&0xff)*MIN(0xffff,chan[i].std.vol.val))/0xff;
|
const unsigned int nextVol=((chan[i].vol&0xff)*MIN(0xffff,chan[i].std.vol.val))/0xff;
|
||||||
|
@ -413,6 +426,23 @@ void DivPlatformES5506::tick(bool sysTick) {
|
||||||
chan[i].envChanged.k2Ramp=1;
|
chan[i].envChanged.k2Ramp=1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// filter slide
|
||||||
|
if (!chan[i].keyOn) {
|
||||||
|
if (chan[i].k1Slide!=0 && chan[i].filter.k1>0 && chan[i].filter.k1<65535) {
|
||||||
|
signed int next=CLAMP_VAL(chan[i].filter.k1+chan[i].k1Slide,0,65535);
|
||||||
|
if (chan[i].filter.k1!=next) {
|
||||||
|
chan[i].filter.k1=next;
|
||||||
|
chan[i].filterChanged.k1=1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (chan[i].k2Slide!=0 && chan[i].filter.k2>0 && chan[i].filter.k2<65535) {
|
||||||
|
signed int next=CLAMP_VAL(chan[i].filter.k2+chan[i].k2Slide,0,65535);
|
||||||
|
if (chan[i].filter.k2!=next) {
|
||||||
|
chan[i].filter.k2=next;
|
||||||
|
chan[i].filterChanged.k2=1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
// update registers
|
// update registers
|
||||||
if (chan[i].volChanged.changed) {
|
if (chan[i].volChanged.changed) {
|
||||||
if (!isMuted[i]) { // calculate volume (16 bit)
|
if (!isMuted[i]) { // calculate volume (16 bit)
|
||||||
|
@ -470,16 +500,16 @@ void DivPlatformES5506::tick(bool sysTick) {
|
||||||
}
|
}
|
||||||
if (chan[i].filterChanged.k2) {
|
if (chan[i].filterChanged.k2) {
|
||||||
if (chan[i].std.ex2.mode!=1) { // Relative
|
if (chan[i].std.ex2.mode!=1) { // Relative
|
||||||
pageWrite(0x00|i,0x07,CLAMP_VAL(chan[i].filter.k2+chan[i].k2Offs,0,65535));
|
k2=CLAMP_VAL(chan[i].filter.k2+chan[i].k2Offs,0,65535);
|
||||||
} else {
|
} else {
|
||||||
pageWrite(0x00|i,0x07,chan[i].filter.k2);
|
k2=chan[i].filter.k2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (chan[i].filterChanged.k1) {
|
if (chan[i].filterChanged.k1) {
|
||||||
if (chan[i].std.ex1.mode!=1) { // Relative
|
if (chan[i].std.ex1.mode!=1) { // Relative
|
||||||
pageWrite(0x00|i,0x09,CLAMP_VAL(chan[i].filter.k1+chan[i].k1Offs,0,65535));
|
k1=CLAMP_VAL(chan[i].filter.k1+chan[i].k1Offs,0,65535);
|
||||||
} else {
|
} else {
|
||||||
pageWrite(0x00|i,0x09,chan[i].filter.k1);
|
k1=chan[i].filter.k1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -511,6 +541,8 @@ void DivPlatformES5506::tick(bool sysTick) {
|
||||||
if (chan[i].freq>0x1ffff) chan[i].freq=0x1ffff;
|
if (chan[i].freq>0x1ffff) chan[i].freq=0x1ffff;
|
||||||
if (chan[i].keyOn) {
|
if (chan[i].keyOn) {
|
||||||
if (chan[i].pcm.index>=0) {
|
if (chan[i].pcm.index>=0) {
|
||||||
|
chan[i].k1Prev=0xffff;
|
||||||
|
chan[i].k2Prev=0xffff;
|
||||||
pageWriteMask(0x00|i,0x5f,0x00,0x0303); // Wipeout CR
|
pageWriteMask(0x00|i,0x5f,0x00,0x0303); // Wipeout CR
|
||||||
pageWrite(0x00|i,0x06,0); // Clear ECOUNT
|
pageWrite(0x00|i,0x06,0); // Clear ECOUNT
|
||||||
pageWrite(0x20|i,0x03,chan[i].pcm.reversed?chan[i].pcm.end:chan[i].pcm.start); // Set ACCUM to start address
|
pageWrite(0x20|i,0x03,chan[i].pcm.reversed?chan[i].pcm.end:chan[i].pcm.start); // Set ACCUM to start address
|
||||||
|
@ -527,15 +559,19 @@ void DivPlatformES5506::tick(bool sysTick) {
|
||||||
// initialize filter
|
// initialize filter
|
||||||
pageWriteMask(0x00|i,0x5f,0x00,(chan[i].pcm.bank<<14)|(chan[i].filter.mode<<8),0xc300);
|
pageWriteMask(0x00|i,0x5f,0x00,(chan[i].pcm.bank<<14)|(chan[i].filter.mode<<8),0xc300);
|
||||||
if ((chan[i].std.ex2.mode!=1) && (chan[i].std.ex2.had)) {
|
if ((chan[i].std.ex2.mode!=1) && (chan[i].std.ex2.had)) {
|
||||||
pageWrite(0x00|i,0x07,CLAMP_VAL(chan[i].filter.k2+chan[i].k2Offs,0,65535));
|
k2=CLAMP_VAL(chan[i].filter.k2+chan[i].k2Offs,0,65535);
|
||||||
} else {
|
} else {
|
||||||
pageWrite(0x00|i,0x07,chan[i].filter.k2);
|
k2=chan[i].filter.k2;
|
||||||
}
|
}
|
||||||
|
pageWrite(0x00|i,0x07,k2);
|
||||||
|
chan[i].k2Prev=k2;
|
||||||
if ((chan[i].std.ex1.mode!=1) && (chan[i].std.ex1.had)) {
|
if ((chan[i].std.ex1.mode!=1) && (chan[i].std.ex1.had)) {
|
||||||
pageWrite(0x00|i,0x09,CLAMP_VAL(chan[i].filter.k1+chan[i].k1Offs,0,65535));
|
k1=CLAMP_VAL(chan[i].filter.k1+chan[i].k1Offs,0,65535);
|
||||||
} else {
|
} else {
|
||||||
pageWrite(0x00|i,0x09,chan[i].filter.k1);
|
k1=chan[i].filter.k1;
|
||||||
}
|
}
|
||||||
|
pageWrite(0x00|i,0x09,k1);
|
||||||
|
chan[i].k1Prev=k1;
|
||||||
pageWrite(0x00|i,0x02,chan[i].resLVol);
|
pageWrite(0x00|i,0x02,chan[i].resLVol);
|
||||||
pageWrite(0x00|i,0x04,chan[i].resRVol);
|
pageWrite(0x00|i,0x04,chan[i].resRVol);
|
||||||
unsigned int loopFlag=chan[i].pcm.reversed?0x0040:0x0000;
|
unsigned int loopFlag=chan[i].pcm.reversed?0x0040:0x0000;
|
||||||
|
@ -569,6 +605,16 @@ void DivPlatformES5506::tick(bool sysTick) {
|
||||||
if (chan[i].keyOff) chan[i].keyOff=false;
|
if (chan[i].keyOff) chan[i].keyOff=false;
|
||||||
chan[i].freqChanged=false;
|
chan[i].freqChanged=false;
|
||||||
}
|
}
|
||||||
|
if (!chan[i].keyOn) {
|
||||||
|
if (chan[i].k2Prev!=k2) {
|
||||||
|
pageWrite(0x00|i,0x07,k2);
|
||||||
|
chan[i].k2Prev=k2;
|
||||||
|
}
|
||||||
|
if (chan[i].k1Prev!=k1) {
|
||||||
|
pageWrite(0x00|i,0x09,k1);
|
||||||
|
chan[i].k1Prev=k1;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -704,6 +750,12 @@ int DivPlatformES5506::dispatch(DivCommand c) {
|
||||||
chan[c.chan].filter.k2=(chan[c.chan].filter.k2&0xf)|((c.value&0xfff)<<4);
|
chan[c.chan].filter.k2=(chan[c.chan].filter.k2&0xf)|((c.value&0xfff)<<4);
|
||||||
chan[c.chan].filterChanged.k2=1;
|
chan[c.chan].filterChanged.k2=1;
|
||||||
break;
|
break;
|
||||||
|
case DIV_CMD_ES5506_FILTER_K1_SLIDE:
|
||||||
|
chan[c.chan].k1Slide=c.value2?(-c.value):c.value;
|
||||||
|
break;
|
||||||
|
case DIV_CMD_ES5506_FILTER_K2_SLIDE:
|
||||||
|
chan[c.chan].k2Slide=c.value2?(-c.value):c.value;
|
||||||
|
break;
|
||||||
// Envelope commands
|
// Envelope commands
|
||||||
case DIV_CMD_ES5506_ENVELOPE_COUNT:
|
case DIV_CMD_ES5506_ENVELOPE_COUNT:
|
||||||
chan[c.chan].envelope.ecount=c.value&0x1ff;
|
chan[c.chan].envelope.ecount=c.value&0x1ff;
|
||||||
|
@ -896,8 +948,51 @@ unsigned char* DivPlatformES5506::getRegisterPool() {
|
||||||
int DivPlatformES5506::getRegisterPoolSize() {
|
int DivPlatformES5506::getRegisterPoolSize() {
|
||||||
return 4*16*128; // 7 bit page x 16 registers per page x 32 bit per registers
|
return 4*16*128; // 7 bit page x 16 registers per page x 32 bit per registers
|
||||||
}
|
}
|
||||||
|
const void* DivPlatformES5506::getSampleMem(int index) {
|
||||||
|
return index == 0 ? sampleMem : NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t DivPlatformES5506::getSampleMemCapacity(int index) {
|
||||||
|
return index == 0 ? 16777216 : 0; // 2Mword x 16bit * 4 banks
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t DivPlatformES5506::getSampleMemUsage(int index) {
|
||||||
|
return index == 0 ? sampleMemLen : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DivPlatformES5506::renderSamples() {
|
||||||
|
memset(sampleMem,0,getSampleMemCapacity());
|
||||||
|
|
||||||
|
int memPos=128;
|
||||||
|
for (int i=0; i<parent->song.sampleLen; i++) {
|
||||||
|
DivSample* s=parent->song.sample[i];
|
||||||
|
unsigned int length=s->length16;
|
||||||
|
// fit sample size to single bank size
|
||||||
|
if (length>(2097152-64)*sizeof(short)) {
|
||||||
|
length=(2097152-64)*sizeof(short);
|
||||||
|
}
|
||||||
|
if ((memPos&0xc00000)!=((memPos+length+128)&0xc00000)) {
|
||||||
|
memPos=((memPos+0x3fffff)&0xc00000)+128;
|
||||||
|
}
|
||||||
|
if (memPos>=(getSampleMemCapacity()-128)) {
|
||||||
|
logW("out of ES5506 memory for sample %d!",i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (memPos+length>=(getSampleMemCapacity()-128)) {
|
||||||
|
memcpy(sampleMem+(memPos/sizeof(short)),s->data16,getSampleMemCapacity()-memPos-128);
|
||||||
|
logW("out of ES5506 memory for sample %d!",i);
|
||||||
|
} else {
|
||||||
|
memcpy(sampleMem+(memPos/sizeof(short)),s->data16,length);
|
||||||
|
}
|
||||||
|
s->offES5506=memPos;
|
||||||
|
memPos+=length;
|
||||||
|
}
|
||||||
|
sampleMemLen=memPos+256;
|
||||||
|
}
|
||||||
|
|
||||||
int DivPlatformES5506::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
|
int DivPlatformES5506::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
|
||||||
|
sampleMem=new signed short[getSampleMemCapacity()/sizeof(short)];
|
||||||
|
sampleMemLen=0;
|
||||||
parent=p;
|
parent=p;
|
||||||
dumpWrites=false;
|
dumpWrites=false;
|
||||||
skipRegisterWrites=false;
|
skipRegisterWrites=false;
|
||||||
|
@ -916,6 +1011,7 @@ int DivPlatformES5506::init(DivEngine* p, int channels, int sugRate, unsigned in
|
||||||
}
|
}
|
||||||
|
|
||||||
void DivPlatformES5506::quit() {
|
void DivPlatformES5506::quit() {
|
||||||
|
delete[] sampleMem;
|
||||||
for (int i=0; i<32; i++) {
|
for (int i=0; i<32; i++) {
|
||||||
delete oscBuf[i];
|
delete oscBuf[i];
|
||||||
}
|
}
|
||||||
|
|
|
@ -105,6 +105,8 @@ class DivPlatformES5506: public DivDispatch, public es550x_intf {
|
||||||
} envChanged;
|
} envChanged;
|
||||||
|
|
||||||
signed int k1Offs, k2Offs;
|
signed int k1Offs, k2Offs;
|
||||||
|
signed int k1Slide, k2Slide;
|
||||||
|
signed int k1Prev, k2Prev;
|
||||||
unsigned int vol, lVol, rVol;
|
unsigned int vol, lVol, rVol;
|
||||||
unsigned int outVol, outLVol, outRVol;
|
unsigned int outVol, outLVol, outRVol;
|
||||||
unsigned int resLVol, resRVol;
|
unsigned int resLVol, resRVol;
|
||||||
|
@ -121,6 +123,8 @@ class DivPlatformES5506: public DivDispatch, public es550x_intf {
|
||||||
if (std.ex1.mode==2) {
|
if (std.ex1.mode==2) {
|
||||||
k2Offs=0;
|
k2Offs=0;
|
||||||
}
|
}
|
||||||
|
k1Prev=0xffff;
|
||||||
|
k2Prev=0xffff;
|
||||||
}
|
}
|
||||||
Channel():
|
Channel():
|
||||||
freq(0),
|
freq(0),
|
||||||
|
@ -142,6 +146,10 @@ class DivPlatformES5506: public DivDispatch, public es550x_intf {
|
||||||
isReverseLoop(false),
|
isReverseLoop(false),
|
||||||
k1Offs(0),
|
k1Offs(0),
|
||||||
k2Offs(0),
|
k2Offs(0),
|
||||||
|
k1Slide(0),
|
||||||
|
k2Slide(0),
|
||||||
|
k1Prev(0xffff),
|
||||||
|
k2Prev(0xffff),
|
||||||
vol(0xff),
|
vol(0xff),
|
||||||
lVol(0xff),
|
lVol(0xff),
|
||||||
rVol(0xff),
|
rVol(0xff),
|
||||||
|
@ -157,6 +165,8 @@ class DivPlatformES5506: public DivDispatch, public es550x_intf {
|
||||||
Channel chan[32];
|
Channel chan[32];
|
||||||
DivDispatchOscBuffer* oscBuf[32];
|
DivDispatchOscBuffer* oscBuf[32];
|
||||||
bool isMuted[32];
|
bool isMuted[32];
|
||||||
|
signed short* sampleMem; // ES5506 uses 16 bit data bus for samples
|
||||||
|
size_t sampleMemLen;
|
||||||
struct QueuedHostIntf {
|
struct QueuedHostIntf {
|
||||||
unsigned char step;
|
unsigned char step;
|
||||||
unsigned char addr;
|
unsigned char addr;
|
||||||
|
@ -203,8 +213,8 @@ class DivPlatformES5506: public DivDispatch, public es550x_intf {
|
||||||
|
|
||||||
virtual void irqb(bool state) override; // IRQB output
|
virtual void irqb(bool state) override; // IRQB output
|
||||||
virtual s16 read_sample(u8 voice, u8 bank, u32 address) override {
|
virtual s16 read_sample(u8 voice, u8 bank, u32 address) override {
|
||||||
if (parent->es5506Mem==NULL) return 0;
|
if (sampleMem==NULL) return 0;
|
||||||
return parent->es5506Mem[((bank&3)<<21)|(address&0x1fffff)];
|
return sampleMem[((bank&3)<<21)|(address&0x1fffff)];
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void acquire(short* bufL, short* bufR, size_t start, size_t len) override;
|
virtual void acquire(short* bufL, short* bufR, size_t start, size_t len) override;
|
||||||
|
@ -225,6 +235,10 @@ class DivPlatformES5506: public DivDispatch, public es550x_intf {
|
||||||
virtual void notifyInsDeletion(void* ins) override;
|
virtual void notifyInsDeletion(void* ins) override;
|
||||||
virtual void poke(unsigned int addr, unsigned short val) override;
|
virtual void poke(unsigned int addr, unsigned short val) override;
|
||||||
virtual void poke(std::vector<DivRegWrite>& wlist) override;
|
virtual void poke(std::vector<DivRegWrite>& wlist) override;
|
||||||
|
virtual const void* getSampleMem(int index = 0) override;
|
||||||
|
virtual size_t getSampleMemCapacity(int index = 0) override;
|
||||||
|
virtual size_t getSampleMemUsage(int index = 0) override;
|
||||||
|
virtual void renderSamples() override;
|
||||||
virtual const char** getRegisterSheet() override;
|
virtual const char** getRegisterSheet() override;
|
||||||
virtual const char* getEffectName(unsigned char effect) override;
|
virtual const char* getEffectName(unsigned char effect) override;
|
||||||
virtual int init(DivEngine* parent, int channels, int sugRate, unsigned int flags) override;
|
virtual int init(DivEngine* parent, int channels, int sugRate, unsigned int flags) override;
|
||||||
|
|
|
@ -20,14 +20,15 @@
|
||||||
#include "nes.h"
|
#include "nes.h"
|
||||||
#include "sound/nes/cpu_inline.h"
|
#include "sound/nes/cpu_inline.h"
|
||||||
#include "../engine.h"
|
#include "../engine.h"
|
||||||
#include <cstddef>
|
#include "../../ta-log.h"
|
||||||
|
#include <stddef.h>
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
|
||||||
struct _nla_table nla_table;
|
struct _nla_table nla_table;
|
||||||
|
|
||||||
#define CHIP_DIVIDER 16
|
#define CHIP_DIVIDER 16
|
||||||
|
|
||||||
#define rWrite(a,v) if (!skipRegisterWrites) {apu_wr_reg(nes,a,v); regPool[(a)&0x7f]=v; if (dumpWrites) {addWrite(a,v);} }
|
#define rWrite(a,v) if (!skipRegisterWrites) {doWrite(a,v); regPool[(a)&0x7f]=v; if (dumpWrites) {addWrite(a,v);} }
|
||||||
|
|
||||||
const char* regCheatSheetNES[]={
|
const char* regCheatSheetNES[]={
|
||||||
"S0Volume", "4000",
|
"S0Volume", "4000",
|
||||||
|
@ -53,6 +54,10 @@ const char* regCheatSheetNES[]={
|
||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
|
|
||||||
|
unsigned char _readDMC(void* user, unsigned short addr) {
|
||||||
|
return ((DivPlatformNES*)user)->readDMC(addr);
|
||||||
|
}
|
||||||
|
|
||||||
const char** DivPlatformNES::getRegisterSheet() {
|
const char** DivPlatformNES::getRegisterSheet() {
|
||||||
return regCheatSheetNES;
|
return regCheatSheetNES;
|
||||||
}
|
}
|
||||||
|
@ -71,41 +76,56 @@ const char* DivPlatformNES::getEffectName(unsigned char effect) {
|
||||||
case 0x14:
|
case 0x14:
|
||||||
return "14xy: Sweep down (x: time; y: shift)";
|
return "14xy: Sweep down (x: time; y: shift)";
|
||||||
break;
|
break;
|
||||||
|
case 0x18:
|
||||||
|
return "18xx: Select PCM/DPCM mode (0: PCM; 1: DPCM)";
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DivPlatformNES::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
void DivPlatformNES::doWrite(unsigned short addr, unsigned char data) {
|
||||||
|
if (useNP) {
|
||||||
|
nes1_NP->Write(addr,data);
|
||||||
|
nes2_NP->Write(addr,data);
|
||||||
|
} else {
|
||||||
|
apu_wr_reg(nes,addr,data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#define doPCM \
|
||||||
|
if (!dpcmMode && dacSample!=-1) { \
|
||||||
|
dacPeriod+=dacRate; \
|
||||||
|
if (dacPeriod>=rate) { \
|
||||||
|
DivSample* s=parent->getSample(dacSample); \
|
||||||
|
if (s->samples>0) { \
|
||||||
|
if (!isMuted[4]) { \
|
||||||
|
unsigned char next=((unsigned char)s->data8[dacPos]+0x80)>>1; \
|
||||||
|
if (dacAntiClickOn && dacAntiClick<next) { \
|
||||||
|
dacAntiClick+=8; \
|
||||||
|
rWrite(0x4011,dacAntiClick); \
|
||||||
|
} else { \
|
||||||
|
dacAntiClickOn=false; \
|
||||||
|
rWrite(0x4011,next); \
|
||||||
|
} \
|
||||||
|
} \
|
||||||
|
dacPos++; \
|
||||||
|
if (((s->loopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT) && dacPos>=s->loopEnd) || (dacPos>=s->samples)) { \
|
||||||
|
if (s->isLoopable()) { \
|
||||||
|
dacPos=s->loopStart; \
|
||||||
|
} else { \
|
||||||
|
dacSample=-1; \
|
||||||
|
} \
|
||||||
|
} \
|
||||||
|
dacPeriod-=rate; \
|
||||||
|
} else { \
|
||||||
|
dacSample=-1; \
|
||||||
|
} \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
void DivPlatformNES::acquire_puNES(short* bufL, short* bufR, size_t start, size_t len) {
|
||||||
for (size_t i=start; i<start+len; i++) {
|
for (size_t i=start; i<start+len; i++) {
|
||||||
if (dacSample!=-1) {
|
doPCM;
|
||||||
dacPeriod+=dacRate;
|
|
||||||
if (dacPeriod>=rate) {
|
|
||||||
DivSample* s=parent->getSample(dacSample);
|
|
||||||
if (s->samples>0) {
|
|
||||||
if (!isMuted[4]) {
|
|
||||||
unsigned char next=((unsigned char)s->data8[dacPos]+0x80)>>1;
|
|
||||||
if (dacAntiClickOn && dacAntiClick<next) {
|
|
||||||
dacAntiClick+=8;
|
|
||||||
rWrite(0x4011,dacAntiClick);
|
|
||||||
} else {
|
|
||||||
dacAntiClickOn=false;
|
|
||||||
rWrite(0x4011,next);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dacPos++;
|
|
||||||
if (((s->loopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT) && dacPos>=s->loopEnd) || (dacPos>=s->samples)) {
|
|
||||||
if (s->isLoopable()) {
|
|
||||||
dacPos=s->loopStart;
|
|
||||||
} else {
|
|
||||||
dacSample=-1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dacPeriod-=rate;
|
|
||||||
} else {
|
|
||||||
dacSample=-1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
apu_tick(nes,NULL);
|
apu_tick(nes,NULL);
|
||||||
nes->apu.odd_cycle=!nes->apu.odd_cycle;
|
nes->apu.odd_cycle=!nes->apu.odd_cycle;
|
||||||
|
@ -127,6 +147,41 @@ void DivPlatformNES::acquire(short* bufL, short* bufR, size_t start, size_t len)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DivPlatformNES::acquire_NSFPlay(short* bufL, short* bufR, size_t start, size_t len) {
|
||||||
|
int out1[2];
|
||||||
|
int out2[2];
|
||||||
|
for (size_t i=start; i<start+len; i++) {
|
||||||
|
doPCM;
|
||||||
|
|
||||||
|
nes1_NP->Tick(1);
|
||||||
|
nes2_NP->TickFrameSequence(1);
|
||||||
|
nes2_NP->Tick(1);
|
||||||
|
nes1_NP->Render(out1);
|
||||||
|
nes2_NP->Render(out2);
|
||||||
|
|
||||||
|
int sample=(out1[0]+out1[1]+out2[0]+out2[1])<<1;
|
||||||
|
if (sample>32767) sample=32767;
|
||||||
|
if (sample<-32768) sample=-32768;
|
||||||
|
bufL[i]=sample;
|
||||||
|
if (++writeOscBuf>=32) {
|
||||||
|
writeOscBuf=0;
|
||||||
|
oscBuf[0]->data[oscBuf[0]->needle++]=nes1_NP->out[0]<<11;
|
||||||
|
oscBuf[1]->data[oscBuf[1]->needle++]=nes1_NP->out[1]<<11;
|
||||||
|
oscBuf[2]->data[oscBuf[2]->needle++]=nes2_NP->out[0]<<11;
|
||||||
|
oscBuf[3]->data[oscBuf[3]->needle++]=nes2_NP->out[1]<<11;
|
||||||
|
oscBuf[4]->data[oscBuf[4]->needle++]=nes2_NP->out[2]<<8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DivPlatformNES::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||||
|
if (useNP) {
|
||||||
|
acquire_NSFPlay(bufL,bufR,start,len);
|
||||||
|
} else {
|
||||||
|
acquire_puNES(bufL,bufR,start,len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static unsigned char noiseTable[253]={
|
static unsigned char noiseTable[253]={
|
||||||
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 5, 4,
|
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 5, 4,
|
||||||
15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4,
|
15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4,
|
||||||
|
@ -152,6 +207,25 @@ static unsigned char noiseTable[253]={
|
||||||
15
|
15
|
||||||
};
|
};
|
||||||
|
|
||||||
|
unsigned char DivPlatformNES::calcDPCMRate(int inRate) {
|
||||||
|
if (inRate<4450) return 0;
|
||||||
|
if (inRate<5000) return 1;
|
||||||
|
if (inRate<5400) return 2;
|
||||||
|
if (inRate<5900) return 3;
|
||||||
|
if (inRate<6650) return 4;
|
||||||
|
if (inRate<7450) return 5;
|
||||||
|
if (inRate<8100) return 6;
|
||||||
|
if (inRate<8800) return 7;
|
||||||
|
if (inRate<10200) return 8;
|
||||||
|
if (inRate<11700) return 9;
|
||||||
|
if (inRate<13300) return 10;
|
||||||
|
if (inRate<15900) return 11;
|
||||||
|
if (inRate<18900) return 12;
|
||||||
|
if (inRate<23500) return 13;
|
||||||
|
if (inRate<29000) return 14;
|
||||||
|
return 15;
|
||||||
|
}
|
||||||
|
|
||||||
void DivPlatformNES::tick(bool sysTick) {
|
void DivPlatformNES::tick(bool sysTick) {
|
||||||
for (int i=0; i<4; i++) {
|
for (int i=0; i<4; i++) {
|
||||||
chan[i].std.next();
|
chan[i].std.next();
|
||||||
|
@ -279,6 +353,9 @@ void DivPlatformNES::tick(bool sysTick) {
|
||||||
off=(double)s->centerRate/8363.0;
|
off=(double)s->centerRate/8363.0;
|
||||||
}
|
}
|
||||||
dacRate=MIN(chan[4].freq*off,32000);
|
dacRate=MIN(chan[4].freq*off,32000);
|
||||||
|
if (dpcmMode && !skipRegisterWrites) {
|
||||||
|
rWrite(0x4010,calcDPCMRate(dacRate));
|
||||||
|
}
|
||||||
if (dumpWrites) addWrite(0xffff0001,dacRate);
|
if (dumpWrites) addWrite(0xffff0001,dacRate);
|
||||||
}
|
}
|
||||||
chan[4].freqChanged=false;
|
chan[4].freqChanged=false;
|
||||||
|
@ -294,10 +371,10 @@ int DivPlatformNES::dispatch(DivCommand c) {
|
||||||
dacSample=ins->amiga.initSample;
|
dacSample=ins->amiga.initSample;
|
||||||
if (dacSample<0 || dacSample>=parent->song.sampleLen) {
|
if (dacSample<0 || dacSample>=parent->song.sampleLen) {
|
||||||
dacSample=-1;
|
dacSample=-1;
|
||||||
if (dumpWrites) addWrite(0xffff0002,0);
|
if (dumpWrites && !dpcmMode) addWrite(0xffff0002,0);
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
if (dumpWrites) addWrite(0xffff0000,dacSample);
|
if (dumpWrites && !dpcmMode) addWrite(0xffff0000,dacSample);
|
||||||
}
|
}
|
||||||
dacPos=0;
|
dacPos=0;
|
||||||
dacPeriod=0;
|
dacPeriod=0;
|
||||||
|
@ -309,6 +386,18 @@ int DivPlatformNES::dispatch(DivCommand c) {
|
||||||
chan[c.chan].active=true;
|
chan[c.chan].active=true;
|
||||||
chan[c.chan].keyOn=true;
|
chan[c.chan].keyOn=true;
|
||||||
chan[c.chan].furnaceDac=true;
|
chan[c.chan].furnaceDac=true;
|
||||||
|
if (dpcmMode && !skipRegisterWrites) {
|
||||||
|
unsigned int dpcmAddr=parent->getSample(dacSample)->offDPCM;
|
||||||
|
unsigned int dpcmLen=(parent->getSample(dacSample)->lengthDPCM+15)>>4;
|
||||||
|
if (dpcmLen>255) dpcmLen=255;
|
||||||
|
// write DPCM
|
||||||
|
rWrite(0x4015,15);
|
||||||
|
rWrite(0x4010,calcDPCMRate(chan[c.chan].baseFreq));
|
||||||
|
rWrite(0x4012,(dpcmAddr>>6)&0xff);
|
||||||
|
rWrite(0x4013,dpcmLen&0xff);
|
||||||
|
rWrite(0x4015,31);
|
||||||
|
dpcmBank=dpcmAddr>>14;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (c.value!=DIV_NOTE_NULL) {
|
if (c.value!=DIV_NOTE_NULL) {
|
||||||
chan[c.chan].note=c.value;
|
chan[c.chan].note=c.value;
|
||||||
|
@ -316,16 +405,28 @@ int DivPlatformNES::dispatch(DivCommand c) {
|
||||||
dacSample=12*sampleBank+chan[c.chan].note%12;
|
dacSample=12*sampleBank+chan[c.chan].note%12;
|
||||||
if (dacSample>=parent->song.sampleLen) {
|
if (dacSample>=parent->song.sampleLen) {
|
||||||
dacSample=-1;
|
dacSample=-1;
|
||||||
if (dumpWrites) addWrite(0xffff0002,0);
|
if (dumpWrites && !dpcmMode) addWrite(0xffff0002,0);
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
if (dumpWrites) addWrite(0xffff0000,dacSample);
|
if (dumpWrites && !dpcmMode) addWrite(0xffff0000,dacSample);
|
||||||
}
|
}
|
||||||
dacPos=0;
|
dacPos=0;
|
||||||
dacPeriod=0;
|
dacPeriod=0;
|
||||||
dacRate=parent->getSample(dacSample)->rate;
|
dacRate=parent->getSample(dacSample)->rate;
|
||||||
if (dumpWrites) addWrite(0xffff0001,dacRate);
|
if (dumpWrites && !dpcmMode) addWrite(0xffff0001,dacRate);
|
||||||
chan[c.chan].furnaceDac=false;
|
chan[c.chan].furnaceDac=false;
|
||||||
|
if (dpcmMode && !skipRegisterWrites) {
|
||||||
|
unsigned int dpcmAddr=parent->getSample(dacSample)->offDPCM;
|
||||||
|
unsigned int dpcmLen=(parent->getSample(dacSample)->lengthDPCM+15)>>4;
|
||||||
|
if (dpcmLen>255) dpcmLen=255;
|
||||||
|
// write DPCM
|
||||||
|
rWrite(0x4015,15);
|
||||||
|
rWrite(0x4010,calcDPCMRate(dacRate));
|
||||||
|
rWrite(0x4012,(dpcmAddr>>6)&0xff);
|
||||||
|
rWrite(0x4013,dpcmLen&0xff);
|
||||||
|
rWrite(0x4015,31);
|
||||||
|
dpcmBank=dpcmAddr>>14;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
} else if (c.chan==3) { // noise
|
} else if (c.chan==3) { // noise
|
||||||
|
@ -354,6 +455,7 @@ int DivPlatformNES::dispatch(DivCommand c) {
|
||||||
if (c.chan==4) {
|
if (c.chan==4) {
|
||||||
dacSample=-1;
|
dacSample=-1;
|
||||||
if (dumpWrites) addWrite(0xffff0002,0);
|
if (dumpWrites) addWrite(0xffff0002,0);
|
||||||
|
if (dpcmMode && !skipRegisterWrites) rWrite(0x4015,15);
|
||||||
}
|
}
|
||||||
chan[c.chan].active=false;
|
chan[c.chan].active=false;
|
||||||
chan[c.chan].keyOff=true;
|
chan[c.chan].keyOff=true;
|
||||||
|
@ -435,6 +537,16 @@ int DivPlatformNES::dispatch(DivCommand c) {
|
||||||
case DIV_CMD_NES_DMC:
|
case DIV_CMD_NES_DMC:
|
||||||
rWrite(0x4011,c.value&0x7f);
|
rWrite(0x4011,c.value&0x7f);
|
||||||
break;
|
break;
|
||||||
|
case DIV_CMD_SAMPLE_MODE:
|
||||||
|
dpcmMode=c.value;
|
||||||
|
if (dumpWrites && dpcmMode) addWrite(0xffff0002,0);
|
||||||
|
dacSample=-1;
|
||||||
|
rWrite(0x4015,15);
|
||||||
|
rWrite(0x4010,0);
|
||||||
|
rWrite(0x4012,0);
|
||||||
|
rWrite(0x4013,0);
|
||||||
|
rWrite(0x4015,31);
|
||||||
|
break;
|
||||||
case DIV_CMD_SAMPLE_BANK:
|
case DIV_CMD_SAMPLE_BANK:
|
||||||
sampleBank=c.value;
|
sampleBank=c.value;
|
||||||
if (sampleBank>(parent->song.sample.size()/12)) {
|
if (sampleBank>(parent->song.sample.size()/12)) {
|
||||||
|
@ -467,8 +579,13 @@ int DivPlatformNES::dispatch(DivCommand c) {
|
||||||
|
|
||||||
void DivPlatformNES::muteChannel(int ch, bool mute) {
|
void DivPlatformNES::muteChannel(int ch, bool mute) {
|
||||||
isMuted[ch]=mute;
|
isMuted[ch]=mute;
|
||||||
|
if (useNP) {
|
||||||
|
nes1_NP->SetMask(((int)isMuted[0])|(isMuted[1]<<1));
|
||||||
|
nes2_NP->SetMask(((int)isMuted[2])|(isMuted[3]<<1)|(isMuted[4]<<2));
|
||||||
|
} else {
|
||||||
nes->muted[ch]=mute;
|
nes->muted[ch]=mute;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void DivPlatformNES::forceIns() {
|
void DivPlatformNES::forceIns() {
|
||||||
for (int i=0; i<5; i++) {
|
for (int i=0; i<5; i++) {
|
||||||
|
@ -513,11 +630,20 @@ void DivPlatformNES::reset() {
|
||||||
dacRate=0;
|
dacRate=0;
|
||||||
dacSample=-1;
|
dacSample=-1;
|
||||||
sampleBank=0;
|
sampleBank=0;
|
||||||
|
dpcmBank=0;
|
||||||
|
dpcmMode=false;
|
||||||
|
|
||||||
|
if (useNP) {
|
||||||
|
nes1_NP->Reset();
|
||||||
|
nes2_NP->Reset();
|
||||||
|
nes1_NP->SetMask(((int)isMuted[0])|(isMuted[1]<<1));
|
||||||
|
nes2_NP->SetMask(((int)isMuted[2])|(isMuted[3]<<1)|(isMuted[4]<<2));
|
||||||
|
} else {
|
||||||
apu_turn_on(nes,apuType);
|
apu_turn_on(nes,apuType);
|
||||||
memset(regPool,0,128);
|
|
||||||
nes->apu.cpu_cycles=0;
|
nes->apu.cpu_cycles=0;
|
||||||
nes->apu.cpu_opcode_cycle=0;
|
nes->apu.cpu_opcode_cycle=0;
|
||||||
|
}
|
||||||
|
memset(regPool,0,128);
|
||||||
|
|
||||||
rWrite(0x4015,0x1f);
|
rWrite(0x4015,0x1f);
|
||||||
rWrite(0x4001,chan[0].sweep);
|
rWrite(0x4001,chan[0].sweep);
|
||||||
|
@ -535,14 +661,20 @@ void DivPlatformNES::setFlags(unsigned int flags) {
|
||||||
if (flags==2) { // Dendy
|
if (flags==2) { // Dendy
|
||||||
rate=COLOR_PAL*2.0/5.0;
|
rate=COLOR_PAL*2.0/5.0;
|
||||||
apuType=2;
|
apuType=2;
|
||||||
nes->apu.type=apuType;
|
|
||||||
} else if (flags==1) { // PAL
|
} else if (flags==1) { // PAL
|
||||||
rate=COLOR_PAL*3.0/8.0;
|
rate=COLOR_PAL*3.0/8.0;
|
||||||
apuType=1;
|
apuType=1;
|
||||||
nes->apu.type=apuType;
|
|
||||||
} else { // NTSC
|
} else { // NTSC
|
||||||
rate=COLOR_NTSC/2.0;
|
rate=COLOR_NTSC/2.0;
|
||||||
apuType=0;
|
apuType=0;
|
||||||
|
}
|
||||||
|
if (useNP) {
|
||||||
|
nes1_NP->SetClock(rate);
|
||||||
|
nes1_NP->SetRate(rate);
|
||||||
|
nes2_NP->SetClock(rate);
|
||||||
|
nes2_NP->SetRate(rate);
|
||||||
|
nes2_NP->SetPal(apuType==1);
|
||||||
|
} else {
|
||||||
nes->apu.type=apuType;
|
nes->apu.type=apuType;
|
||||||
}
|
}
|
||||||
chipClock=rate;
|
chipClock=rate;
|
||||||
|
@ -565,20 +697,87 @@ void DivPlatformNES::poke(std::vector<DivRegWrite>& wlist) {
|
||||||
for (DivRegWrite& i: wlist) rWrite(i.addr,i.val);
|
for (DivRegWrite& i: wlist) rWrite(i.addr,i.val);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DivPlatformNES::setNSFPlay(bool use) {
|
||||||
|
useNP=use;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned char DivPlatformNES::readDMC(unsigned short addr) {
|
||||||
|
return dpcmMem[(addr&0x3fff)|((dpcmBank&15)<<14)];
|
||||||
|
}
|
||||||
|
|
||||||
|
const void* DivPlatformNES::getSampleMem(int index) {
|
||||||
|
return index==0?dpcmMem:NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t DivPlatformNES::getSampleMemCapacity(int index) {
|
||||||
|
return index==0?262144:0;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t DivPlatformNES::getSampleMemUsage(int index) {
|
||||||
|
return index==0?dpcmMemLen:0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DivPlatformNES::renderSamples() {
|
||||||
|
memset(dpcmMem,0,getSampleMemCapacity(0));
|
||||||
|
|
||||||
|
size_t memPos=0;
|
||||||
|
for (int i=0; i<parent->song.sampleLen; i++) {
|
||||||
|
DivSample* s=parent->song.sample[i];
|
||||||
|
unsigned int paddedLen=(s->lengthDPCM+63)&(~0x3f);
|
||||||
|
logV("%d padded length: %d",i,paddedLen);
|
||||||
|
if ((memPos&(~0x3fff))!=((memPos+paddedLen)&(~0x3fff))) {
|
||||||
|
memPos=(memPos+0x3fff)&(~0x3fff);
|
||||||
|
}
|
||||||
|
if (paddedLen>4081) {
|
||||||
|
paddedLen=4096;
|
||||||
|
}
|
||||||
|
if (memPos>=getSampleMemCapacity(0)) {
|
||||||
|
logW("out of DPCM memory for sample %d!",i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (memPos+paddedLen>=getSampleMemCapacity(0)) {
|
||||||
|
memcpy(dpcmMem+memPos,s->dataDPCM,getSampleMemCapacity(0)-memPos);
|
||||||
|
logW("out of DPCM memory for sample %d!",i);
|
||||||
|
} else {
|
||||||
|
memcpy(dpcmMem+memPos,s->dataDPCM,MIN(s->lengthDPCM,paddedLen));
|
||||||
|
}
|
||||||
|
s->offDPCM=memPos;
|
||||||
|
memPos+=paddedLen;
|
||||||
|
}
|
||||||
|
dpcmMemLen=memPos;
|
||||||
|
}
|
||||||
|
|
||||||
int DivPlatformNES::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
|
int DivPlatformNES::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
|
||||||
parent=p;
|
parent=p;
|
||||||
apuType=flags;
|
apuType=flags;
|
||||||
dumpWrites=false;
|
dumpWrites=false;
|
||||||
skipRegisterWrites=false;
|
skipRegisterWrites=false;
|
||||||
|
if (useNP) {
|
||||||
|
nes1_NP=new xgm::NES_APU;
|
||||||
|
nes1_NP->SetOption(xgm::NES_APU::OPT_NONLINEAR_MIXER,1);
|
||||||
|
nes2_NP=new xgm::NES_DMC;
|
||||||
|
nes2_NP->SetOption(xgm::NES_DMC::OPT_NONLINEAR_MIXER,1);
|
||||||
|
nes2_NP->SetMemory([this](unsigned short addr, unsigned int& data) {
|
||||||
|
data=readDMC(addr);
|
||||||
|
});
|
||||||
|
nes2_NP->SetAPU(nes1_NP);
|
||||||
|
} else {
|
||||||
nes=new struct NESAPU;
|
nes=new struct NESAPU;
|
||||||
|
nes->readDMC=_readDMC;
|
||||||
|
nes->readDMCUser=this;
|
||||||
|
}
|
||||||
writeOscBuf=0;
|
writeOscBuf=0;
|
||||||
for (int i=0; i<5; i++) {
|
for (int i=0; i<5; i++) {
|
||||||
isMuted[i]=false;
|
isMuted[i]=false;
|
||||||
nes->muted[i]=false;
|
if (!useNP) nes->muted[i]=false;
|
||||||
oscBuf[i]=new DivDispatchOscBuffer;
|
oscBuf[i]=new DivDispatchOscBuffer;
|
||||||
}
|
}
|
||||||
setFlags(flags);
|
setFlags(flags);
|
||||||
|
|
||||||
|
dpcmMem=new unsigned char[262144];
|
||||||
|
dpcmMemLen=0;
|
||||||
|
dpcmBank=0;
|
||||||
|
|
||||||
init_nla_table(500,500);
|
init_nla_table(500,500);
|
||||||
reset();
|
reset();
|
||||||
return 5;
|
return 5;
|
||||||
|
@ -588,8 +787,13 @@ void DivPlatformNES::quit() {
|
||||||
for (int i=0; i<5; i++) {
|
for (int i=0; i<5; i++) {
|
||||||
delete oscBuf[i];
|
delete oscBuf[i];
|
||||||
}
|
}
|
||||||
|
if (useNP) {
|
||||||
|
delete nes1_NP;
|
||||||
|
delete nes2_NP;
|
||||||
|
} else {
|
||||||
delete nes;
|
delete nes;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
DivPlatformNES::~DivPlatformNES() {
|
DivPlatformNES::~DivPlatformNES() {
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,8 @@
|
||||||
#include "../dispatch.h"
|
#include "../dispatch.h"
|
||||||
#include "../macroInt.h"
|
#include "../macroInt.h"
|
||||||
|
|
||||||
|
#include "sound/nes_nsfplay/nes_apu.h"
|
||||||
|
|
||||||
class DivPlatformNES: public DivDispatch {
|
class DivPlatformNES: public DivDispatch {
|
||||||
struct Channel {
|
struct Channel {
|
||||||
int freq, baseFreq, pitch, pitch2, prevFreq, note, ins;
|
int freq, baseFreq, pitch, pitch2, prevFreq, note, ins;
|
||||||
|
@ -62,15 +64,27 @@ class DivPlatformNES: public DivDispatch {
|
||||||
int dacPeriod, dacRate;
|
int dacPeriod, dacRate;
|
||||||
unsigned int dacPos, dacAntiClick;
|
unsigned int dacPos, dacAntiClick;
|
||||||
int dacSample;
|
int dacSample;
|
||||||
|
unsigned char* dpcmMem;
|
||||||
|
size_t dpcmMemLen;
|
||||||
|
unsigned char dpcmBank;
|
||||||
unsigned char sampleBank;
|
unsigned char sampleBank;
|
||||||
unsigned char writeOscBuf;
|
unsigned char writeOscBuf;
|
||||||
unsigned char apuType;
|
unsigned char apuType;
|
||||||
|
bool dpcmMode;
|
||||||
bool dacAntiClickOn;
|
bool dacAntiClickOn;
|
||||||
|
bool useNP;
|
||||||
struct NESAPU* nes;
|
struct NESAPU* nes;
|
||||||
|
xgm::NES_APU* nes1_NP;
|
||||||
|
xgm::NES_DMC* nes2_NP;
|
||||||
unsigned char regPool[128];
|
unsigned char regPool[128];
|
||||||
|
|
||||||
friend void putDispatchChan(void*,int,int);
|
friend void putDispatchChan(void*,int,int);
|
||||||
|
|
||||||
|
void doWrite(unsigned short addr, unsigned char data);
|
||||||
|
unsigned char calcDPCMRate(int inRate);
|
||||||
|
void acquire_puNES(short* bufL, short* bufR, size_t start, size_t len);
|
||||||
|
void acquire_NSFPlay(short* bufL, short* bufR, size_t start, size_t len);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void acquire(short* bufL, short* bufR, size_t start, size_t len);
|
void acquire(short* bufL, short* bufR, size_t start, size_t len);
|
||||||
int dispatch(DivCommand c);
|
int dispatch(DivCommand c);
|
||||||
|
@ -84,12 +98,18 @@ class DivPlatformNES: public DivDispatch {
|
||||||
void muteChannel(int ch, bool mute);
|
void muteChannel(int ch, bool mute);
|
||||||
bool keyOffAffectsArp(int ch);
|
bool keyOffAffectsArp(int ch);
|
||||||
float getPostAmp();
|
float getPostAmp();
|
||||||
|
unsigned char readDMC(unsigned short addr);
|
||||||
|
void setNSFPlay(bool use);
|
||||||
void setFlags(unsigned int flags);
|
void setFlags(unsigned int flags);
|
||||||
void notifyInsDeletion(void* ins);
|
void notifyInsDeletion(void* ins);
|
||||||
void poke(unsigned int addr, unsigned short val);
|
void poke(unsigned int addr, unsigned short val);
|
||||||
void poke(std::vector<DivRegWrite>& wlist);
|
void poke(std::vector<DivRegWrite>& wlist);
|
||||||
const char** getRegisterSheet();
|
const char** getRegisterSheet();
|
||||||
const char* getEffectName(unsigned char effect);
|
const char* getEffectName(unsigned char effect);
|
||||||
|
const void* getSampleMem(int index);
|
||||||
|
size_t getSampleMemCapacity(int index);
|
||||||
|
size_t getSampleMemUsage(int index);
|
||||||
|
void renderSamples();
|
||||||
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
||||||
void quit();
|
void quit();
|
||||||
~DivPlatformNES();
|
~DivPlatformNES();
|
||||||
|
|
|
@ -705,7 +705,7 @@ int DivPlatformOPL::dispatch(DivCommand c) {
|
||||||
if (c.value==0 && c.value2==0) {
|
if (c.value==0 && c.value2==0) {
|
||||||
chan[c.chan].pan=3;
|
chan[c.chan].pan=3;
|
||||||
} else {
|
} else {
|
||||||
chan[c.chan].pan=(c.value2>0)|((c.value>0)<<1);
|
chan[c.chan].pan=(c.value>0)|((c.value2>0)<<1);
|
||||||
}
|
}
|
||||||
int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2;
|
int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2;
|
||||||
if (isMuted[c.chan]) {
|
if (isMuted[c.chan]) {
|
||||||
|
|
|
@ -268,8 +268,6 @@ const char* DivPlatformQSound::getEffectName(unsigned char effect) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
void DivPlatformQSound::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
void DivPlatformQSound::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||||
chip.rom_data = parent->qsoundMem;
|
|
||||||
chip.rom_mask = 0xffffff;
|
|
||||||
for (size_t h=start; h<start+len; h++) {
|
for (size_t h=start; h<start+len; h++) {
|
||||||
qsound_update(&chip);
|
qsound_update(&chip);
|
||||||
bufL[h]=chip.out[0];
|
bufL[h]=chip.out[0];
|
||||||
|
@ -638,6 +636,51 @@ int DivPlatformQSound::getRegisterPoolDepth() {
|
||||||
return 16;
|
return 16;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const void* DivPlatformQSound::getSampleMem(int index) {
|
||||||
|
return index == 0 ? sampleMem : NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t DivPlatformQSound::getSampleMemCapacity(int index) {
|
||||||
|
return index == 0 ? 16777216 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t DivPlatformQSound::getSampleMemUsage(int index) {
|
||||||
|
return index == 0 ? sampleMemLen : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DivPlatformQSound::renderSamples() {
|
||||||
|
memset(sampleMem,0,getSampleMemCapacity());
|
||||||
|
|
||||||
|
size_t memPos=0;
|
||||||
|
for (int i=0; i<parent->song.sampleLen; i++) {
|
||||||
|
DivSample* s=parent->song.sample[i];
|
||||||
|
int length=s->length8;
|
||||||
|
if (length>65536-16) {
|
||||||
|
length=65536-16;
|
||||||
|
}
|
||||||
|
if ((memPos&0xff0000)!=((memPos+length)&0xff0000)) {
|
||||||
|
memPos=(memPos+0xffff)&0xff0000;
|
||||||
|
}
|
||||||
|
if (memPos>=getSampleMemCapacity()) {
|
||||||
|
logW("out of QSound PCM memory for sample %d!",i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (memPos+length>=getSampleMemCapacity()) {
|
||||||
|
for (unsigned int i=0; i<getSampleMemCapacity()-(memPos+length); i++) {
|
||||||
|
sampleMem[(memPos+i)^0x8000]=s->data8[i];
|
||||||
|
}
|
||||||
|
logW("out of QSound PCM memory for sample %d!",i);
|
||||||
|
} else {
|
||||||
|
for (int i=0; i<length; i++) {
|
||||||
|
sampleMem[(memPos+i)^0x8000]=s->data8[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s->offQSound=memPos^0x8000;
|
||||||
|
memPos+=length+16;
|
||||||
|
}
|
||||||
|
sampleMemLen=memPos+256;
|
||||||
|
}
|
||||||
|
|
||||||
int DivPlatformQSound::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
|
int DivPlatformQSound::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
|
||||||
parent=p;
|
parent=p;
|
||||||
dumpWrites=false;
|
dumpWrites=false;
|
||||||
|
@ -651,8 +694,10 @@ int DivPlatformQSound::init(DivEngine* p, int channels, int sugRate, unsigned in
|
||||||
|
|
||||||
chipClock=60000000;
|
chipClock=60000000;
|
||||||
rate = qsound_start(&chip, chipClock);
|
rate = qsound_start(&chip, chipClock);
|
||||||
chip.rom_data = (unsigned char*)&chip.rom_mask;
|
sampleMem=new unsigned char[getSampleMemCapacity()];
|
||||||
chip.rom_mask = 0;
|
sampleMemLen=0;
|
||||||
|
chip.rom_data=sampleMem;
|
||||||
|
chip.rom_mask=0xffffff;
|
||||||
reset();
|
reset();
|
||||||
|
|
||||||
for (int i=0; i<19; i++) {
|
for (int i=0; i<19; i++) {
|
||||||
|
@ -662,6 +707,7 @@ int DivPlatformQSound::init(DivEngine* p, int channels, int sugRate, unsigned in
|
||||||
}
|
}
|
||||||
|
|
||||||
void DivPlatformQSound::quit() {
|
void DivPlatformQSound::quit() {
|
||||||
|
delete[] sampleMem;
|
||||||
for (int i=0; i<19; i++) {
|
for (int i=0; i<19; i++) {
|
||||||
delete oscBuf[i];
|
delete oscBuf[i];
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,6 +67,8 @@ class DivPlatformQSound: public DivDispatch {
|
||||||
int echoDelay;
|
int echoDelay;
|
||||||
int echoFeedback;
|
int echoFeedback;
|
||||||
|
|
||||||
|
unsigned char* sampleMem;
|
||||||
|
size_t sampleMemLen;
|
||||||
struct qsound_chip chip;
|
struct qsound_chip chip;
|
||||||
unsigned short regPool[512];
|
unsigned short regPool[512];
|
||||||
|
|
||||||
|
@ -94,6 +96,10 @@ class DivPlatformQSound: public DivDispatch {
|
||||||
void poke(std::vector<DivRegWrite>& wlist);
|
void poke(std::vector<DivRegWrite>& wlist);
|
||||||
const char** getRegisterSheet();
|
const char** getRegisterSheet();
|
||||||
const char* getEffectName(unsigned char effect);
|
const char* getEffectName(unsigned char effect);
|
||||||
|
const void* getSampleMem(int index = 0);
|
||||||
|
size_t getSampleMemCapacity(int index = 0);
|
||||||
|
size_t getSampleMemUsage(int index = 0);
|
||||||
|
void renderSamples();
|
||||||
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
||||||
void quit();
|
void quit();
|
||||||
};
|
};
|
||||||
|
|
|
@ -207,7 +207,7 @@ enum apu_mode { APU_60HZ, APU_48HZ };
|
||||||
break;\
|
break;\
|
||||||
}\
|
}\
|
||||||
{\
|
{\
|
||||||
a->DMC.buffer = 0;\
|
a->DMC.buffer = a->readDMC(a->readDMCUser,a->DMC.address);\
|
||||||
}\
|
}\
|
||||||
/* incremento gli hwtick da compiere */\
|
/* incremento gli hwtick da compiere */\
|
||||||
if (hwtick) { hwtick[0] += tick; }\
|
if (hwtick) { hwtick[0] += tick; }\
|
||||||
|
@ -525,6 +525,8 @@ EXTERNC struct NESAPU {
|
||||||
_apuTriangle TR;
|
_apuTriangle TR;
|
||||||
_apuNoise NS;
|
_apuNoise NS;
|
||||||
_apuDMC DMC;
|
_apuDMC DMC;
|
||||||
|
void* readDMCUser;
|
||||||
|
unsigned char (*readDMC)(void*,unsigned short);
|
||||||
unsigned char muted[5];
|
unsigned char muted[5];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
4
src/engine/platform/sound/nes_nsfplay/common.h
Normal file
4
src/engine/platform/sound/nes_nsfplay/common.h
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
namespace xgm {
|
||||||
|
const unsigned int DEFAULT_CLOCK=1789773;
|
||||||
|
const unsigned int DEFAULT_RATE=1789773;
|
||||||
|
};
|
380
src/engine/platform/sound/nes_nsfplay/nes_apu.cpp
Normal file
380
src/engine/platform/sound/nes_nsfplay/nes_apu.cpp
Normal file
|
@ -0,0 +1,380 @@
|
||||||
|
//
|
||||||
|
// NES 2A03
|
||||||
|
//
|
||||||
|
#include <assert.h>
|
||||||
|
#include "nes_apu.h"
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
namespace xgm
|
||||||
|
{
|
||||||
|
void NES_APU::sweep_sqr (int i)
|
||||||
|
{
|
||||||
|
int shifted = freq[i] >> sweep_amount[i];
|
||||||
|
if (i == 0 && sweep_mode[i]) shifted += 1;
|
||||||
|
sfreq[i] = freq[i] + (sweep_mode[i] ? -shifted : shifted);
|
||||||
|
//DEBUG_OUT("shifted[%d] = %d (%d >> %d)¥n",i,shifted,freq[i],sweep_amount[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NES_APU::FrameSequence(int s)
|
||||||
|
{
|
||||||
|
//DEBUG_OUT("FrameSequence(%d)¥n",s);
|
||||||
|
|
||||||
|
if (s > 3) return; // no operation in step 4
|
||||||
|
|
||||||
|
// 240hz clock
|
||||||
|
for (int i=0; i < 2; ++i)
|
||||||
|
{
|
||||||
|
bool divider = false;
|
||||||
|
if (envelope_write[i])
|
||||||
|
{
|
||||||
|
envelope_write[i] = false;
|
||||||
|
envelope_counter[i] = 15;
|
||||||
|
envelope_div[i] = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
++envelope_div[i];
|
||||||
|
if (envelope_div[i] > envelope_div_period[i])
|
||||||
|
{
|
||||||
|
divider = true;
|
||||||
|
envelope_div[i] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (divider)
|
||||||
|
{
|
||||||
|
if (envelope_loop[i] && envelope_counter[i] == 0)
|
||||||
|
envelope_counter[i] = 15;
|
||||||
|
else if (envelope_counter[i] > 0)
|
||||||
|
--envelope_counter[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 120hz clock
|
||||||
|
if ((s&1) == 0)
|
||||||
|
for (int i=0; i < 2; ++i)
|
||||||
|
{
|
||||||
|
if (!envelope_loop[i] && (length_counter[i] > 0))
|
||||||
|
--length_counter[i];
|
||||||
|
|
||||||
|
if (sweep_enable[i])
|
||||||
|
{
|
||||||
|
//DEBUG_OUT("Clock sweep: %d¥n", i);
|
||||||
|
|
||||||
|
--sweep_div[i];
|
||||||
|
if (sweep_div[i] <= 0)
|
||||||
|
{
|
||||||
|
sweep_sqr(i); // calculate new sweep target
|
||||||
|
|
||||||
|
//DEBUG_OUT("sweep_div[%d] (0/%d)¥n",i,sweep_div_period[i]);
|
||||||
|
//DEBUG_OUT("freq[%d]=%d > sfreq[%d]=%d¥n",i,freq[i],i,sfreq[i]);
|
||||||
|
|
||||||
|
if (freq[i] >= 8 && sfreq[i] < 0x800 && sweep_amount[i] > 0) // update frequency if appropriate
|
||||||
|
{
|
||||||
|
freq[i] = sfreq[i] < 0 ? 0 : sfreq[i];
|
||||||
|
}
|
||||||
|
sweep_div[i] = sweep_div_period[i] + 1;
|
||||||
|
|
||||||
|
//DEBUG_OUT("freq[%d]=%d¥n",i,freq[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sweep_write[i])
|
||||||
|
{
|
||||||
|
sweep_div[i] = sweep_div_period[i] + 1;
|
||||||
|
sweep_write[i] = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
int NES_APU::calc_sqr (int i, unsigned int clocks)
|
||||||
|
{
|
||||||
|
static const short sqrtbl[4][16] = {
|
||||||
|
{0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||||
|
{0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||||
|
{0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0},
|
||||||
|
{1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
|
||||||
|
};
|
||||||
|
|
||||||
|
scounter[i] -= clocks;
|
||||||
|
while (scounter[i] < 0)
|
||||||
|
{
|
||||||
|
sphase[i] = (sphase[i] + 1) & 15;
|
||||||
|
scounter[i] += freq[i] + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ret = 0;
|
||||||
|
if (length_counter[i] > 0 &&
|
||||||
|
freq[i] >= 8 &&
|
||||||
|
sfreq[i] < 0x800
|
||||||
|
)
|
||||||
|
{
|
||||||
|
int v = envelope_disable[i] ? volume[i] : envelope_counter[i];
|
||||||
|
ret = sqrtbl[duty[i]][sphase[i]] ? v : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NES_APU::Read (unsigned int adr, unsigned int & val, unsigned int id)
|
||||||
|
{
|
||||||
|
if (0x4000 <= adr && adr < 0x4008)
|
||||||
|
{
|
||||||
|
val |= reg[adr&0x7];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if(adr==0x4015)
|
||||||
|
{
|
||||||
|
val |= (length_counter[1]?2:0)|(length_counter[0]?1:0);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NES_APU::Tick (unsigned int clocks)
|
||||||
|
{
|
||||||
|
out[0] = calc_sqr(0, clocks);
|
||||||
|
out[1] = calc_sqr(1, clocks);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ツ青カツ青ャツつウツづェツづゥツ波ツ形ツづ個振ツ閉敖づ0-8191
|
||||||
|
unsigned int NES_APU::Render (int b[2])
|
||||||
|
{
|
||||||
|
out[0] = (mask & 1) ? 0 : out[0];
|
||||||
|
out[1] = (mask & 2) ? 0 : out[1];
|
||||||
|
|
||||||
|
int m[2];
|
||||||
|
|
||||||
|
if(option[OPT_NONLINEAR_MIXER])
|
||||||
|
{
|
||||||
|
int voltage = square_table[out[0] + out[1]];
|
||||||
|
m[0] = out[0] << 6;
|
||||||
|
m[1] = out[1] << 6;
|
||||||
|
int ref = m[0] + m[1];
|
||||||
|
if (ref > 0)
|
||||||
|
{
|
||||||
|
m[0] = (m[0] * voltage) / ref;
|
||||||
|
m[1] = (m[1] * voltage) / ref;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m[0] = voltage;
|
||||||
|
m[1] = voltage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m[0] = (out[0] * square_linear) / 15;
|
||||||
|
m[1] = (out[1] * square_linear) / 15;
|
||||||
|
}
|
||||||
|
|
||||||
|
b[0] = m[0] * sm[0][0];
|
||||||
|
b[0] += m[1] * sm[0][1];
|
||||||
|
b[0] >>= 7;
|
||||||
|
|
||||||
|
b[1] = m[0] * sm[1][0];
|
||||||
|
b[1] += m[1] * sm[1][1];
|
||||||
|
b[1] >>= 7;
|
||||||
|
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
NES_APU::NES_APU ()
|
||||||
|
{
|
||||||
|
SetClock (DEFAULT_CLOCK);
|
||||||
|
SetRate (DEFAULT_RATE);
|
||||||
|
option[OPT_UNMUTE_ON_RESET] = true;
|
||||||
|
option[OPT_PHASE_REFRESH] = true;
|
||||||
|
option[OPT_NONLINEAR_MIXER] = true;
|
||||||
|
option[OPT_DUTY_SWAP] = false;
|
||||||
|
option[OPT_NEGATE_SWEEP_INIT] = false;
|
||||||
|
|
||||||
|
square_table[0] = 0;
|
||||||
|
for(int i=1;i<32;i++)
|
||||||
|
square_table[i]=(int)((8192.0*95.88)/(8128.0/i+100));
|
||||||
|
|
||||||
|
square_linear = square_table[15]; // match linear scale to one full volume square of nonlinear
|
||||||
|
|
||||||
|
for(int c=0;c<2;++c)
|
||||||
|
for(int t=0;t<2;++t)
|
||||||
|
sm[c][t] = 128;
|
||||||
|
}
|
||||||
|
|
||||||
|
NES_APU::~NES_APU ()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void NES_APU::Reset ()
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
gclock = 0;
|
||||||
|
mask = 0;
|
||||||
|
|
||||||
|
for (int i=0; i<2; ++i)
|
||||||
|
{
|
||||||
|
scounter[i] = 0;
|
||||||
|
sphase[i] = 0;
|
||||||
|
duty[i] = 0;
|
||||||
|
volume[i] = 0;
|
||||||
|
freq[i] = 0;
|
||||||
|
sfreq[i] = 0;
|
||||||
|
sweep_enable[i] = 0;
|
||||||
|
sweep_mode[i] = 0;
|
||||||
|
sweep_write[i] = 0;
|
||||||
|
sweep_div_period[i] = 0;
|
||||||
|
sweep_div[i] = 1;
|
||||||
|
sweep_amount[i] = 0;
|
||||||
|
envelope_disable[i] = 0;
|
||||||
|
envelope_loop[i] = 0;
|
||||||
|
envelope_write[i] = 0;
|
||||||
|
envelope_div_period[i] = 0;
|
||||||
|
envelope_div[0] = 0;
|
||||||
|
envelope_counter[i] = 0;
|
||||||
|
length_counter[i] = 0;
|
||||||
|
enable[i] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0x4000; i < 0x4008; i++)
|
||||||
|
Write (i, 0);
|
||||||
|
|
||||||
|
Write (0x4015, 0);
|
||||||
|
if (option[OPT_UNMUTE_ON_RESET])
|
||||||
|
Write (0x4015, 0x0f);
|
||||||
|
if (option[OPT_NEGATE_SWEEP_INIT])
|
||||||
|
{
|
||||||
|
Write (0x4001, 0x08);
|
||||||
|
Write (0x4005, 0x08);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < 2; i++)
|
||||||
|
out[i] = 0;
|
||||||
|
|
||||||
|
SetRate(rate);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NES_APU::SetOption (int id, int val)
|
||||||
|
{
|
||||||
|
if(id<OPT_END) option[id] = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NES_APU::SetClock (double c)
|
||||||
|
{
|
||||||
|
clock = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NES_APU::SetRate (double r)
|
||||||
|
{
|
||||||
|
rate = r ? r : DEFAULT_RATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NES_APU::SetStereoMix(int trk, short mixl, short mixr)
|
||||||
|
{
|
||||||
|
if (trk < 0) return;
|
||||||
|
if (trk > 1) return;
|
||||||
|
sm[0][trk] = mixl;
|
||||||
|
sm[1][trk] = mixr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NES_APU::Write (unsigned int adr, unsigned int val, unsigned int id)
|
||||||
|
{
|
||||||
|
int ch;
|
||||||
|
|
||||||
|
static const unsigned char length_table[32] = {
|
||||||
|
0x0A, 0xFE,
|
||||||
|
0x14, 0x02,
|
||||||
|
0x28, 0x04,
|
||||||
|
0x50, 0x06,
|
||||||
|
0xA0, 0x08,
|
||||||
|
0x3C, 0x0A,
|
||||||
|
0x0E, 0x0C,
|
||||||
|
0x1A, 0x0E,
|
||||||
|
0x0C, 0x10,
|
||||||
|
0x18, 0x12,
|
||||||
|
0x30, 0x14,
|
||||||
|
0x60, 0x16,
|
||||||
|
0xC0, 0x18,
|
||||||
|
0x48, 0x1A,
|
||||||
|
0x10, 0x1C,
|
||||||
|
0x20, 0x1E
|
||||||
|
};
|
||||||
|
|
||||||
|
if (0x4000 <= adr && adr < 0x4008)
|
||||||
|
{
|
||||||
|
//DEBUG_OUT("$%04X = %02X¥n",adr,val);
|
||||||
|
|
||||||
|
adr &= 0xf;
|
||||||
|
ch = adr >> 2;
|
||||||
|
switch (adr)
|
||||||
|
{
|
||||||
|
case 0x0:
|
||||||
|
case 0x4:
|
||||||
|
volume[ch] = val & 15;
|
||||||
|
envelope_disable[ch] = (val >> 4) & 1;
|
||||||
|
envelope_loop[ch] = (val >> 5) & 1;
|
||||||
|
envelope_div_period[ch] = (val & 15);
|
||||||
|
duty[ch] = (val >> 6) & 3;
|
||||||
|
if (option[OPT_DUTY_SWAP])
|
||||||
|
{
|
||||||
|
if (duty[ch] == 1) duty[ch] = 2;
|
||||||
|
else if (duty[ch] == 2) duty[ch] = 1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x1:
|
||||||
|
case 0x5:
|
||||||
|
sweep_enable[ch] = (val >> 7) & 1;
|
||||||
|
sweep_div_period[ch] = (((val >> 4) & 7));
|
||||||
|
sweep_mode[ch] = (val >> 3) & 1;
|
||||||
|
sweep_amount[ch] = val & 7;
|
||||||
|
sweep_write[ch] = true;
|
||||||
|
sweep_sqr(ch);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x2:
|
||||||
|
case 0x6:
|
||||||
|
freq[ch] = val | (freq[ch] & 0x700) ;
|
||||||
|
sweep_sqr(ch);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x3:
|
||||||
|
case 0x7:
|
||||||
|
freq[ch] = (freq[ch] & 0xFF) | ((val & 0x7) << 8) ;
|
||||||
|
if (option[OPT_PHASE_REFRESH])
|
||||||
|
sphase[ch] = 0;
|
||||||
|
envelope_write[ch] = true;
|
||||||
|
if (enable[ch])
|
||||||
|
{
|
||||||
|
length_counter[ch] = length_table[(val >> 3) & 0x1f];
|
||||||
|
}
|
||||||
|
sweep_sqr(ch);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
reg[adr] = val;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (adr == 0x4015)
|
||||||
|
{
|
||||||
|
enable[0] = (val & 1) ? true : false;
|
||||||
|
enable[1] = (val & 2) ? true : false;
|
||||||
|
|
||||||
|
if (!enable[0])
|
||||||
|
length_counter[0] = 0;
|
||||||
|
if (!enable[1])
|
||||||
|
length_counter[1] = 0;
|
||||||
|
|
||||||
|
reg[adr-0x4000] = val;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4017 is handled in nes_dmc.cpp
|
||||||
|
//else if (adr == 0x4017)
|
||||||
|
//{
|
||||||
|
//}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} // namespace xgm;
|
85
src/engine/platform/sound/nes_nsfplay/nes_apu.h
Normal file
85
src/engine/platform/sound/nes_nsfplay/nes_apu.h
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
#ifndef _NES_APU_H_
|
||||||
|
#define _NES_APU_H_
|
||||||
|
#include "nes_dmc.h"
|
||||||
|
|
||||||
|
namespace xgm
|
||||||
|
{
|
||||||
|
/** Upper half of APU **/
|
||||||
|
class NES_APU
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
OPT_UNMUTE_ON_RESET=0,
|
||||||
|
OPT_PHASE_REFRESH,
|
||||||
|
OPT_NONLINEAR_MIXER,
|
||||||
|
OPT_DUTY_SWAP,
|
||||||
|
OPT_NEGATE_SWEEP_INIT,
|
||||||
|
OPT_END };
|
||||||
|
|
||||||
|
enum
|
||||||
|
{ SQR0_MASK = 1, SQR1_MASK = 2, };
|
||||||
|
|
||||||
|
protected:
|
||||||
|
int option[OPT_END]; // 各種オプション
|
||||||
|
int mask;
|
||||||
|
int sm[2][2];
|
||||||
|
|
||||||
|
unsigned int gclock;
|
||||||
|
unsigned char reg[0x20];
|
||||||
|
double rate, clock;
|
||||||
|
|
||||||
|
int square_table[32]; // nonlinear mixer
|
||||||
|
int square_linear; // linear mix approximation
|
||||||
|
|
||||||
|
int scounter[2]; // frequency divider
|
||||||
|
int sphase[2]; // phase counter
|
||||||
|
|
||||||
|
int duty[2];
|
||||||
|
int volume[2];
|
||||||
|
int freq[2];
|
||||||
|
int sfreq[2];
|
||||||
|
|
||||||
|
bool sweep_enable[2];
|
||||||
|
bool sweep_mode[2];
|
||||||
|
bool sweep_write[2];
|
||||||
|
int sweep_div_period[2];
|
||||||
|
int sweep_div[2];
|
||||||
|
int sweep_amount[2];
|
||||||
|
|
||||||
|
bool envelope_disable[2];
|
||||||
|
bool envelope_loop[2];
|
||||||
|
bool envelope_write[2];
|
||||||
|
int envelope_div_period[2];
|
||||||
|
int envelope_div[2];
|
||||||
|
int envelope_counter[2];
|
||||||
|
|
||||||
|
int length_counter[2];
|
||||||
|
|
||||||
|
bool enable[2];
|
||||||
|
|
||||||
|
void sweep_sqr (int ch); // calculates target sweep frequency
|
||||||
|
int calc_sqr (int ch, unsigned int clocks);
|
||||||
|
|
||||||
|
public:
|
||||||
|
int out[2];
|
||||||
|
NES_APU ();
|
||||||
|
~NES_APU ();
|
||||||
|
|
||||||
|
void FrameSequence(int s);
|
||||||
|
|
||||||
|
void Reset ();
|
||||||
|
void Tick (unsigned int clocks);
|
||||||
|
unsigned int Render (int b[2]);
|
||||||
|
bool Read (unsigned int adr, unsigned int & val, unsigned int id=0);
|
||||||
|
bool Write (unsigned int adr, unsigned int val, unsigned int id=0);
|
||||||
|
void SetRate (double rate);
|
||||||
|
void SetClock (double clock);
|
||||||
|
void SetOption (int id, int b);
|
||||||
|
void SetMask(int m){ mask = m; }
|
||||||
|
void SetStereoMix (int trk, short mixl, short mixr);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
#endif
|
717
src/engine/platform/sound/nes_nsfplay/nes_dmc.cpp
Normal file
717
src/engine/platform/sound/nes_nsfplay/nes_dmc.cpp
Normal file
|
@ -0,0 +1,717 @@
|
||||||
|
#include "nes_dmc.h"
|
||||||
|
#include "nes_apu.h"
|
||||||
|
#include "common.h"
|
||||||
|
#include <assert.h>
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
|
namespace xgm
|
||||||
|
{
|
||||||
|
const unsigned int NES_DMC::wavlen_table[2][16] = {
|
||||||
|
{ // NTSC
|
||||||
|
4, 8, 16, 32, 64, 96, 128, 160, 202, 254, 380, 508, 762, 1016, 2034, 4068
|
||||||
|
},
|
||||||
|
{ // PAL
|
||||||
|
4, 8, 14, 30, 60, 88, 118, 148, 188, 236, 354, 472, 708, 944, 1890, 3778
|
||||||
|
}};
|
||||||
|
|
||||||
|
const unsigned int NES_DMC::freq_table[2][16] = {
|
||||||
|
{ // NTSC
|
||||||
|
428, 380, 340, 320, 286, 254, 226, 214, 190, 160, 142, 128, 106, 84, 72, 54
|
||||||
|
},
|
||||||
|
{ // PAL
|
||||||
|
398, 354, 316, 298, 276, 236, 210, 198, 176, 148, 132, 118, 98, 78, 66, 50
|
||||||
|
}};
|
||||||
|
|
||||||
|
const unsigned int BITREVERSE[256] = {
|
||||||
|
0x00, 0x80, 0x40, 0xC0, 0x20, 0xA0, 0x60, 0xE0, 0x10, 0x90, 0x50, 0xD0, 0x30, 0xB0, 0x70, 0xF0,
|
||||||
|
0x08, 0x88, 0x48, 0xC8, 0x28, 0xA8, 0x68, 0xE8, 0x18, 0x98, 0x58, 0xD8, 0x38, 0xB8, 0x78, 0xF8,
|
||||||
|
0x04, 0x84, 0x44, 0xC4, 0x24, 0xA4, 0x64, 0xE4, 0x14, 0x94, 0x54, 0xD4, 0x34, 0xB4, 0x74, 0xF4,
|
||||||
|
0x0C, 0x8C, 0x4C, 0xCC, 0x2C, 0xAC, 0x6C, 0xEC, 0x1C, 0x9C, 0x5C, 0xDC, 0x3C, 0xBC, 0x7C, 0xFC,
|
||||||
|
0x02, 0x82, 0x42, 0xC2, 0x22, 0xA2, 0x62, 0xE2, 0x12, 0x92, 0x52, 0xD2, 0x32, 0xB2, 0x72, 0xF2,
|
||||||
|
0x0A, 0x8A, 0x4A, 0xCA, 0x2A, 0xAA, 0x6A, 0xEA, 0x1A, 0x9A, 0x5A, 0xDA, 0x3A, 0xBA, 0x7A, 0xFA,
|
||||||
|
0x06, 0x86, 0x46, 0xC6, 0x26, 0xA6, 0x66, 0xE6, 0x16, 0x96, 0x56, 0xD6, 0x36, 0xB6, 0x76, 0xF6,
|
||||||
|
0x0E, 0x8E, 0x4E, 0xCE, 0x2E, 0xAE, 0x6E, 0xEE, 0x1E, 0x9E, 0x5E, 0xDE, 0x3E, 0xBE, 0x7E, 0xFE,
|
||||||
|
0x01, 0x81, 0x41, 0xC1, 0x21, 0xA1, 0x61, 0xE1, 0x11, 0x91, 0x51, 0xD1, 0x31, 0xB1, 0x71, 0xF1,
|
||||||
|
0x09, 0x89, 0x49, 0xC9, 0x29, 0xA9, 0x69, 0xE9, 0x19, 0x99, 0x59, 0xD9, 0x39, 0xB9, 0x79, 0xF9,
|
||||||
|
0x05, 0x85, 0x45, 0xC5, 0x25, 0xA5, 0x65, 0xE5, 0x15, 0x95, 0x55, 0xD5, 0x35, 0xB5, 0x75, 0xF5,
|
||||||
|
0x0D, 0x8D, 0x4D, 0xCD, 0x2D, 0xAD, 0x6D, 0xED, 0x1D, 0x9D, 0x5D, 0xDD, 0x3D, 0xBD, 0x7D, 0xFD,
|
||||||
|
0x03, 0x83, 0x43, 0xC3, 0x23, 0xA3, 0x63, 0xE3, 0x13, 0x93, 0x53, 0xD3, 0x33, 0xB3, 0x73, 0xF3,
|
||||||
|
0x0B, 0x8B, 0x4B, 0xCB, 0x2B, 0xAB, 0x6B, 0xEB, 0x1B, 0x9B, 0x5B, 0xDB, 0x3B, 0xBB, 0x7B, 0xFB,
|
||||||
|
0x07, 0x87, 0x47, 0xC7, 0x27, 0xA7, 0x67, 0xE7, 0x17, 0x97, 0x57, 0xD7, 0x37, 0xB7, 0x77, 0xF7,
|
||||||
|
0x0F, 0x8F, 0x4F, 0xCF, 0x2F, 0xAF, 0x6F, 0xEF, 0x1F, 0x9F, 0x5F, 0xDF, 0x3F, 0xBF, 0x7F, 0xFF,
|
||||||
|
};
|
||||||
|
|
||||||
|
NES_DMC::NES_DMC () : GETA_BITS (20)
|
||||||
|
{
|
||||||
|
SetClock (DEFAULT_CLOCK);
|
||||||
|
SetRate (DEFAULT_RATE);
|
||||||
|
SetPal (false);
|
||||||
|
option[OPT_ENABLE_4011] = 1;
|
||||||
|
option[OPT_ENABLE_PNOISE] = 1;
|
||||||
|
option[OPT_UNMUTE_ON_RESET] = 1;
|
||||||
|
option[OPT_DPCM_ANTI_CLICK] = 0;
|
||||||
|
option[OPT_NONLINEAR_MIXER] = 1;
|
||||||
|
option[OPT_RANDOMIZE_NOISE] = 1;
|
||||||
|
option[OPT_RANDOMIZE_TRI] = 1;
|
||||||
|
option[OPT_TRI_MUTE] = 1;
|
||||||
|
option[OPT_DPCM_REVERSE] = 0;
|
||||||
|
tnd_table[0][0][0][0] = 0;
|
||||||
|
tnd_table[1][0][0][0] = 0;
|
||||||
|
|
||||||
|
apu = NULL;
|
||||||
|
frame_sequence_count = 0;
|
||||||
|
frame_sequence_length = 7458;
|
||||||
|
frame_sequence_steps = 4;
|
||||||
|
|
||||||
|
for(int c=0;c<2;++c)
|
||||||
|
for(int t=0;t<3;++t)
|
||||||
|
sm[c][t] = 128;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
NES_DMC::~NES_DMC ()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void NES_DMC::SetStereoMix(int trk, short mixl, short mixr)
|
||||||
|
{
|
||||||
|
if (trk < 0) return;
|
||||||
|
if (trk > 2) return;
|
||||||
|
sm[0][trk] = mixl;
|
||||||
|
sm[1][trk] = mixr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NES_DMC::FrameSequence(int s)
|
||||||
|
{
|
||||||
|
//DEBUG_OUT("FrameSequence: %d¥n",s);
|
||||||
|
|
||||||
|
if (s > 3) return; // no operation in step 4
|
||||||
|
|
||||||
|
if (apu)
|
||||||
|
{
|
||||||
|
apu->FrameSequence(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s == 0 && (frame_sequence_steps == 4))
|
||||||
|
{
|
||||||
|
if (frame_irq_enable) frame_irq = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 240hz clock
|
||||||
|
{
|
||||||
|
// triangle linear counter
|
||||||
|
if (linear_counter_halt)
|
||||||
|
{
|
||||||
|
linear_counter = linear_counter_reload;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (linear_counter > 0) --linear_counter;
|
||||||
|
}
|
||||||
|
if (!linear_counter_control)
|
||||||
|
{
|
||||||
|
linear_counter_halt = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// noise envelope
|
||||||
|
bool divider = false;
|
||||||
|
if (envelope_write)
|
||||||
|
{
|
||||||
|
envelope_write = false;
|
||||||
|
envelope_counter = 15;
|
||||||
|
envelope_div = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
++envelope_div;
|
||||||
|
if (envelope_div > envelope_div_period)
|
||||||
|
{
|
||||||
|
divider = true;
|
||||||
|
envelope_div = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (divider)
|
||||||
|
{
|
||||||
|
if (envelope_loop && envelope_counter == 0)
|
||||||
|
envelope_counter = 15;
|
||||||
|
else if (envelope_counter > 0)
|
||||||
|
--envelope_counter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 120hz clock
|
||||||
|
if ((s&1) == 0)
|
||||||
|
{
|
||||||
|
// triangle length counter
|
||||||
|
if (!linear_counter_control && (length_counter[0] > 0))
|
||||||
|
--length_counter[0];
|
||||||
|
|
||||||
|
// noise length counter
|
||||||
|
if (!envelope_loop && (length_counter[1] > 0))
|
||||||
|
--length_counter[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// 三角波チャンネルの計算 戻り値は0-15
|
||||||
|
unsigned int NES_DMC::calc_tri (unsigned int clocks)
|
||||||
|
{
|
||||||
|
static unsigned int tritbl[32] =
|
||||||
|
{
|
||||||
|
15,14,13,12,11,10, 9, 8,
|
||||||
|
7, 6, 5, 4, 3, 2, 1, 0,
|
||||||
|
0, 1, 2, 3, 4, 5, 6, 7,
|
||||||
|
8, 9,10,11,12,13,14,15,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (linear_counter > 0 && length_counter[0] > 0
|
||||||
|
&& (!option[OPT_TRI_MUTE] || tri_freq > 0))
|
||||||
|
{
|
||||||
|
counter[0] -= clocks;
|
||||||
|
while (counter[0] < 0)
|
||||||
|
{
|
||||||
|
tphase = (tphase + 1) & 31;
|
||||||
|
counter[0] += (tri_freq + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int ret = tritbl[tphase];
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ノイズチャンネルの計算 戻り値は0-127
|
||||||
|
// 低サンプリングレートで合成するとエイリアスノイズが激しいので
|
||||||
|
// ノイズだけはこの関数内で高クロック合成し、簡易なサンプリングレート
|
||||||
|
// 変換を行っている。
|
||||||
|
unsigned int NES_DMC::calc_noise(unsigned int clocks)
|
||||||
|
{
|
||||||
|
unsigned int env = envelope_disable ? noise_volume : envelope_counter;
|
||||||
|
if (length_counter[1] < 1) env = 0;
|
||||||
|
|
||||||
|
unsigned int last = (noise & 0x4000) ? 0 : env;
|
||||||
|
if (clocks < 1) return last;
|
||||||
|
|
||||||
|
// simple anti-aliasing (noise requires it, even when oversampling is off)
|
||||||
|
unsigned int count = 0;
|
||||||
|
unsigned int accum = counter[1] * last; // samples pending from previous calc
|
||||||
|
unsigned int accum_clocks = counter[1];
|
||||||
|
#ifdef _DEBUG
|
||||||
|
int start_clocks = counter[1];
|
||||||
|
#endif
|
||||||
|
if (counter[1] < 0) // only happens on startup when using the randomize noise option
|
||||||
|
{
|
||||||
|
accum = 0;
|
||||||
|
accum_clocks = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
counter[1] -= clocks;
|
||||||
|
assert (nfreq > 0); // prevent infinite loop
|
||||||
|
while (counter[1] < 0)
|
||||||
|
{
|
||||||
|
// tick the noise generator
|
||||||
|
unsigned int feedback = (noise&1) ^ ((noise&noise_tap)?1:0);
|
||||||
|
noise = (noise>>1) | (feedback<<14);
|
||||||
|
|
||||||
|
last = (noise & 0x4000) ? 0 : env;
|
||||||
|
accum += (last * nfreq);
|
||||||
|
counter[1] += nfreq;
|
||||||
|
++count;
|
||||||
|
accum_clocks += nfreq;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count < 1) // no change over interval, don't anti-alias
|
||||||
|
{
|
||||||
|
return last;
|
||||||
|
}
|
||||||
|
|
||||||
|
accum -= (last * counter[1]); // remove these samples which belong in the next calc
|
||||||
|
accum_clocks -= counter[1];
|
||||||
|
#ifdef _DEBUG
|
||||||
|
if (start_clocks >= 0) assert(accum_clocks == clocks); // these should be equal
|
||||||
|
#endif
|
||||||
|
|
||||||
|
unsigned int average = accum / accum_clocks;
|
||||||
|
assert(average <= 15); // above this would indicate overflow
|
||||||
|
return average;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tick the DMC for the number of clocks, and return output counter;
|
||||||
|
unsigned int NES_DMC::calc_dmc (unsigned int clocks)
|
||||||
|
{
|
||||||
|
counter[2] -= clocks;
|
||||||
|
assert (dfreq > 0); // prevent infinite loop
|
||||||
|
while (counter[2] < 0)
|
||||||
|
{
|
||||||
|
counter[2] += dfreq;
|
||||||
|
|
||||||
|
if ( data > 0x100 ) // data = 0x100 when shift register is empty
|
||||||
|
{
|
||||||
|
if (!empty)
|
||||||
|
{
|
||||||
|
if ((data & 1) && (damp < 63))
|
||||||
|
damp++;
|
||||||
|
else if (!(data & 1) && (0 < damp))
|
||||||
|
damp--;
|
||||||
|
}
|
||||||
|
data >>=1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( data <= 0x100 ) // shift register is empty
|
||||||
|
{
|
||||||
|
if (dlength > 0)
|
||||||
|
{
|
||||||
|
memory (daddress, data);
|
||||||
|
// (checking for the 3-cycle case would require sub-instruction emulation)
|
||||||
|
data &= 0xFF; // read 8 bits
|
||||||
|
if (option[OPT_DPCM_REVERSE]) data = BITREVERSE[data];
|
||||||
|
data |= 0x10000; // use an extra bit to signal end of data
|
||||||
|
empty = false;
|
||||||
|
daddress = ((daddress+1)&0xFFFF)|0x8000 ;
|
||||||
|
--dlength;
|
||||||
|
if (dlength == 0)
|
||||||
|
{
|
||||||
|
if (mode & 1) // looped DPCM = auto-reload
|
||||||
|
{
|
||||||
|
daddress = ((adr_reg<<6)|0xC000);
|
||||||
|
dlength = (len_reg<<4)+1;
|
||||||
|
}
|
||||||
|
else if (mode & 2) // IRQ and not looped
|
||||||
|
{
|
||||||
|
irq = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
data = 0x10000; // DMC will do nothing
|
||||||
|
empty = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (damp<<1) + dac_lsb;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NES_DMC::TickFrameSequence (unsigned int clocks)
|
||||||
|
{
|
||||||
|
frame_sequence_count += clocks;
|
||||||
|
while (frame_sequence_count > frame_sequence_length)
|
||||||
|
{
|
||||||
|
FrameSequence(frame_sequence_step);
|
||||||
|
frame_sequence_count -= frame_sequence_length;
|
||||||
|
++frame_sequence_step;
|
||||||
|
if(frame_sequence_step >= frame_sequence_steps)
|
||||||
|
frame_sequence_step = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NES_DMC::Tick (unsigned int clocks)
|
||||||
|
{
|
||||||
|
out[0] = calc_tri(clocks);
|
||||||
|
out[1] = calc_noise(clocks);
|
||||||
|
out[2] = calc_dmc(clocks);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int NES_DMC::Render (int b[2])
|
||||||
|
{
|
||||||
|
out[0] = (mask & 1) ? 0 : out[0];
|
||||||
|
out[1] = (mask & 2) ? 0 : out[1];
|
||||||
|
out[2] = (mask & 4) ? 0 : out[2];
|
||||||
|
|
||||||
|
int m[3];
|
||||||
|
m[0] = tnd_table[0][out[0]][0][0];
|
||||||
|
m[1] = tnd_table[0][0][out[1]][0];
|
||||||
|
m[2] = tnd_table[0][0][0][out[2]];
|
||||||
|
|
||||||
|
if (option[OPT_NONLINEAR_MIXER])
|
||||||
|
{
|
||||||
|
int ref = m[0] + m[1] + m[2];
|
||||||
|
int voltage = tnd_table[1][out[0]][out[1]][out[2]];
|
||||||
|
if (ref)
|
||||||
|
{
|
||||||
|
for (int i=0; i < 3; ++i)
|
||||||
|
m[i] = (m[i] * voltage) / ref;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (int i=0; i < 3; ++i)
|
||||||
|
m[i] = voltage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// anti-click nullifies any 4011 write but preserves nonlinearity
|
||||||
|
if (option[OPT_DPCM_ANTI_CLICK])
|
||||||
|
{
|
||||||
|
if (dmc_pop) // $4011 will cause pop this frame
|
||||||
|
{
|
||||||
|
// adjust offset to counteract pop
|
||||||
|
dmc_pop_offset += dmc_pop_follow - m[2];
|
||||||
|
dmc_pop = false;
|
||||||
|
|
||||||
|
// prevent overflow, keep headspace at edges
|
||||||
|
const int OFFSET_MAX = (1 << 30) - (4 << 16);
|
||||||
|
if (dmc_pop_offset > OFFSET_MAX) dmc_pop_offset = OFFSET_MAX;
|
||||||
|
if (dmc_pop_offset < -OFFSET_MAX) dmc_pop_offset = -OFFSET_MAX;
|
||||||
|
}
|
||||||
|
dmc_pop_follow = m[2]; // remember previous position
|
||||||
|
|
||||||
|
m[2] += dmc_pop_offset; // apply offset
|
||||||
|
|
||||||
|
// TODO implement this in a better way
|
||||||
|
// roll off offset (not ideal, but prevents overflow)
|
||||||
|
if (dmc_pop_offset > 0) --dmc_pop_offset;
|
||||||
|
else if (dmc_pop_offset < 0) ++dmc_pop_offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
b[0] = m[0] * sm[0][0];
|
||||||
|
b[0] += m[1] * sm[0][1];
|
||||||
|
b[0] += m[2] * sm[0][2];
|
||||||
|
b[0] >>= 7;
|
||||||
|
|
||||||
|
b[1] = m[0] * sm[1][0];
|
||||||
|
b[1] += m[1] * sm[1][1];
|
||||||
|
b[1] += m[2] * sm[1][2];
|
||||||
|
b[1] >>= 7;
|
||||||
|
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NES_DMC::SetClock (double c)
|
||||||
|
{
|
||||||
|
clock = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NES_DMC::SetRate (double r)
|
||||||
|
{
|
||||||
|
rate = (unsigned int)(r?r:DEFAULT_RATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NES_DMC::SetPal (bool is_pal)
|
||||||
|
{
|
||||||
|
pal = (is_pal ? 1 : 0);
|
||||||
|
// set CPU cycles in frame_sequence
|
||||||
|
frame_sequence_length = is_pal ? 8314 : 7458;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NES_DMC::SetAPU (NES_APU* apu_)
|
||||||
|
{
|
||||||
|
apu = apu_;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initializing TRI, NOISE, DPCM mixing table
|
||||||
|
void NES_DMC::InitializeTNDTable(double wt, double wn, double wd) {
|
||||||
|
|
||||||
|
// volume adjusted by 0.95 based on empirical measurements
|
||||||
|
const double MASTER = 8192.0 * 0.95;
|
||||||
|
// truthfully, the nonlinear curve does not appear to match well
|
||||||
|
// with my tests. Do more testing of the APU/DMC DAC later.
|
||||||
|
// this value keeps the triangle consistent with measured levels,
|
||||||
|
// but not necessarily the rest of this APU channel,
|
||||||
|
// because of the lack of a good DAC model, currently.
|
||||||
|
|
||||||
|
{ // Linear Mixer
|
||||||
|
for(int t=0; t<16 ; t++) {
|
||||||
|
for(int n=0; n<16; n++) {
|
||||||
|
for(int d=0; d<128; d++) {
|
||||||
|
tnd_table[0][t][n][d] = (unsigned int)(MASTER*(3.0*t+2.0*n+d)/208.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{ // Non-Linear Mixer
|
||||||
|
tnd_table[1][0][0][0] = 0;
|
||||||
|
for(int t=0; t<16 ; t++) {
|
||||||
|
for(int n=0; n<16; n++) {
|
||||||
|
for(int d=0; d<128; d++) {
|
||||||
|
if(t!=0||n!=0||d!=0)
|
||||||
|
tnd_table[1][t][n][d] = (unsigned int)((MASTER*159.79)/(100.0+1.0/((double)t/wt+(double)n/wn+(double)d/wd)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void NES_DMC::Reset ()
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
mask = 0;
|
||||||
|
|
||||||
|
InitializeTNDTable(8227,12241,22638);
|
||||||
|
|
||||||
|
counter[0] = 0;
|
||||||
|
counter[1] = 0;
|
||||||
|
counter[2] = 0;
|
||||||
|
tphase = 0;
|
||||||
|
nfreq = wavlen_table[0][0];
|
||||||
|
dfreq = freq_table[0][0];
|
||||||
|
tri_freq = 0;
|
||||||
|
linear_counter = 0;
|
||||||
|
linear_counter_reload = 0;
|
||||||
|
linear_counter_halt = 0;
|
||||||
|
linear_counter_control = 0;
|
||||||
|
noise_volume = 0;
|
||||||
|
noise = 0;
|
||||||
|
noise_tap = 0;
|
||||||
|
envelope_loop = 0;
|
||||||
|
envelope_disable = 0;
|
||||||
|
envelope_write = 0;
|
||||||
|
envelope_div_period = 0;
|
||||||
|
envelope_div = 0;
|
||||||
|
envelope_counter = 0;
|
||||||
|
enable[0] = 0;
|
||||||
|
enable[1] = 0;
|
||||||
|
length_counter[0] = 0;
|
||||||
|
length_counter[1] = 0;
|
||||||
|
frame_irq = false;
|
||||||
|
frame_irq_enable = false;
|
||||||
|
frame_sequence_count = 0;
|
||||||
|
frame_sequence_steps = 4;
|
||||||
|
frame_sequence_step = 0;
|
||||||
|
|
||||||
|
for (i = 0; i < 0x0F; i++)
|
||||||
|
Write (0x4008 + i, 0);
|
||||||
|
Write (0x4017, 0x40);
|
||||||
|
|
||||||
|
irq = false;
|
||||||
|
Write (0x4015, 0x00);
|
||||||
|
if (option[OPT_UNMUTE_ON_RESET])
|
||||||
|
Write (0x4015, 0x0f);
|
||||||
|
|
||||||
|
out[0] = out[1] = out[2] = 0;
|
||||||
|
damp = 0;
|
||||||
|
dmc_pop = false;
|
||||||
|
dmc_pop_offset = 0;
|
||||||
|
dmc_pop_follow = 0;
|
||||||
|
dac_lsb = 0;
|
||||||
|
data = 0x100;
|
||||||
|
empty = true;
|
||||||
|
adr_reg = 0;
|
||||||
|
dlength = 0;
|
||||||
|
len_reg = 0;
|
||||||
|
daddress = 0;
|
||||||
|
noise = 1;
|
||||||
|
noise_tap = (1<<1);
|
||||||
|
|
||||||
|
if (option[OPT_RANDOMIZE_NOISE])
|
||||||
|
{
|
||||||
|
noise |= ::rand();
|
||||||
|
counter[1] = -(rand() & 511);
|
||||||
|
}
|
||||||
|
if (option[OPT_RANDOMIZE_TRI])
|
||||||
|
{
|
||||||
|
tphase = ::rand() & 31;
|
||||||
|
counter[0] = -(rand() & 2047);
|
||||||
|
}
|
||||||
|
|
||||||
|
SetRate(rate);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NES_DMC::SetMemory (std::function<void(unsigned short, unsigned int&)> r)
|
||||||
|
{
|
||||||
|
memory = r;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NES_DMC::SetOption (int id, int val)
|
||||||
|
{
|
||||||
|
if(id<OPT_END)
|
||||||
|
{
|
||||||
|
option[id] = val;
|
||||||
|
if(id==OPT_NONLINEAR_MIXER)
|
||||||
|
InitializeTNDTable(8227,12241,22638);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NES_DMC::Write (unsigned int adr, unsigned int val, unsigned int id)
|
||||||
|
{
|
||||||
|
static const unsigned char length_table[32] = {
|
||||||
|
0x0A, 0xFE,
|
||||||
|
0x14, 0x02,
|
||||||
|
0x28, 0x04,
|
||||||
|
0x50, 0x06,
|
||||||
|
0xA0, 0x08,
|
||||||
|
0x3C, 0x0A,
|
||||||
|
0x0E, 0x0C,
|
||||||
|
0x1A, 0x0E,
|
||||||
|
0x0C, 0x10,
|
||||||
|
0x18, 0x12,
|
||||||
|
0x30, 0x14,
|
||||||
|
0x60, 0x16,
|
||||||
|
0xC0, 0x18,
|
||||||
|
0x48, 0x1A,
|
||||||
|
0x10, 0x1C,
|
||||||
|
0x20, 0x1E
|
||||||
|
};
|
||||||
|
|
||||||
|
if (adr == 0x4015)
|
||||||
|
{
|
||||||
|
enable[0] = (val & 4) ? true : false;
|
||||||
|
enable[1] = (val & 8) ? true : false;
|
||||||
|
|
||||||
|
if (!enable[0])
|
||||||
|
{
|
||||||
|
length_counter[0] = 0;
|
||||||
|
}
|
||||||
|
if (!enable[1])
|
||||||
|
{
|
||||||
|
length_counter[1] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((val & 16) && dlength == 0)
|
||||||
|
{
|
||||||
|
daddress = (0xC000 | (adr_reg << 6));
|
||||||
|
dlength = (len_reg << 4) + 1;
|
||||||
|
}
|
||||||
|
else if (!(val & 16))
|
||||||
|
{
|
||||||
|
dlength = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
irq = false;
|
||||||
|
|
||||||
|
reg[adr-0x4008] = val;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (adr == 0x4017)
|
||||||
|
{
|
||||||
|
//DEBUG_OUT("4017 = %02X¥n", val);
|
||||||
|
frame_irq_enable = ((val & 0x40) != 0x40);
|
||||||
|
if (frame_irq_enable) frame_irq = false;
|
||||||
|
|
||||||
|
frame_sequence_count = 0;
|
||||||
|
if (val & 0x80)
|
||||||
|
{
|
||||||
|
frame_sequence_steps = 5;
|
||||||
|
frame_sequence_step = 0;
|
||||||
|
FrameSequence(frame_sequence_step);
|
||||||
|
++frame_sequence_step;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
frame_sequence_steps = 4;
|
||||||
|
frame_sequence_step = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (adr<0x4008||0x4013<adr)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
reg[adr-0x4008] = val&0xff;
|
||||||
|
|
||||||
|
//DEBUG_OUT("$%04X %02X¥n", adr, val);
|
||||||
|
|
||||||
|
switch (adr)
|
||||||
|
{
|
||||||
|
|
||||||
|
// tri
|
||||||
|
|
||||||
|
case 0x4008:
|
||||||
|
linear_counter_control = (val >> 7) & 1;
|
||||||
|
linear_counter_reload = val & 0x7F;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x4009:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x400a:
|
||||||
|
tri_freq = val | (tri_freq & 0x700) ;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x400b:
|
||||||
|
tri_freq = (tri_freq & 0xff) | ((val & 0x7) << 8) ;
|
||||||
|
linear_counter_halt = true;
|
||||||
|
if (enable[0])
|
||||||
|
{
|
||||||
|
length_counter[0] = length_table[(val >> 3) & 0x1f];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
// noise
|
||||||
|
|
||||||
|
case 0x400c:
|
||||||
|
noise_volume = val & 15;
|
||||||
|
envelope_div_period = val & 15;
|
||||||
|
envelope_disable = (val >> 4) & 1;
|
||||||
|
envelope_loop = (val >> 5) & 1;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x400d:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x400e:
|
||||||
|
if (option[OPT_ENABLE_PNOISE])
|
||||||
|
noise_tap = (val & 0x80) ? (1<<6) : (1<<1);
|
||||||
|
else
|
||||||
|
noise_tap = (1<<1);
|
||||||
|
nfreq = wavlen_table[pal][val&15];
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x400f:
|
||||||
|
if (enable[1])
|
||||||
|
{
|
||||||
|
length_counter[1] = length_table[(val >> 3) & 0x1f];
|
||||||
|
}
|
||||||
|
envelope_write = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
// dmc
|
||||||
|
|
||||||
|
case 0x4010:
|
||||||
|
mode = (val >> 6) & 3;
|
||||||
|
if (!(mode & 2))
|
||||||
|
{
|
||||||
|
irq = false;
|
||||||
|
}
|
||||||
|
dfreq = freq_table[pal][val&15];
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x4011:
|
||||||
|
if (option[OPT_ENABLE_4011])
|
||||||
|
{
|
||||||
|
damp = (val >> 1) & 0x3f;
|
||||||
|
dac_lsb = val & 1;
|
||||||
|
dmc_pop = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x4012:
|
||||||
|
adr_reg = val&0xff;
|
||||||
|
// ここでdaddressは更新されない
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x4013:
|
||||||
|
len_reg = val&0xff;
|
||||||
|
// ここでlengthは更新されない
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NES_DMC::Read (unsigned int adr, unsigned int & val, unsigned int id)
|
||||||
|
{
|
||||||
|
if (adr == 0x4015)
|
||||||
|
{
|
||||||
|
val |=(irq ? 0x80 : 0)
|
||||||
|
| (frame_irq ? 0x40 : 0)
|
||||||
|
| ((dlength>0) ? 0x10 : 0)
|
||||||
|
| (length_counter[1] ? 0x08 : 0)
|
||||||
|
| (length_counter[0] ? 0x04 : 0)
|
||||||
|
;
|
||||||
|
|
||||||
|
frame_irq = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (0x4008<=adr&&adr<=0x4014)
|
||||||
|
{
|
||||||
|
val |= reg[adr-0x4008];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} // namespace
|
119
src/engine/platform/sound/nes_nsfplay/nes_dmc.h
Normal file
119
src/engine/platform/sound/nes_nsfplay/nes_dmc.h
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
#ifndef _NES_DMC_H_
|
||||||
|
#define _NES_DMC_H_
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
namespace xgm
|
||||||
|
{
|
||||||
|
class NES_APU; // forward declaration
|
||||||
|
|
||||||
|
/** Bottom Half of APU **/
|
||||||
|
class NES_DMC
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
OPT_ENABLE_4011=0,
|
||||||
|
OPT_ENABLE_PNOISE,
|
||||||
|
OPT_UNMUTE_ON_RESET,
|
||||||
|
OPT_DPCM_ANTI_CLICK,
|
||||||
|
OPT_NONLINEAR_MIXER,
|
||||||
|
OPT_RANDOMIZE_NOISE,
|
||||||
|
OPT_TRI_MUTE,
|
||||||
|
OPT_RANDOMIZE_TRI,
|
||||||
|
OPT_DPCM_REVERSE,
|
||||||
|
OPT_END
|
||||||
|
};
|
||||||
|
protected:
|
||||||
|
const int GETA_BITS;
|
||||||
|
static const unsigned int freq_table[2][16];
|
||||||
|
static const unsigned int wavlen_table[2][16];
|
||||||
|
unsigned int tnd_table[2][16][16][128];
|
||||||
|
|
||||||
|
int option[OPT_END];
|
||||||
|
int mask;
|
||||||
|
int sm[2][3];
|
||||||
|
unsigned int reg[0x10];
|
||||||
|
unsigned int len_reg;
|
||||||
|
unsigned int adr_reg;
|
||||||
|
std::function<void(unsigned short, unsigned int&)> memory;
|
||||||
|
unsigned int daddress;
|
||||||
|
unsigned int dlength;
|
||||||
|
unsigned int data;
|
||||||
|
bool empty;
|
||||||
|
short damp;
|
||||||
|
int dac_lsb;
|
||||||
|
bool dmc_pop;
|
||||||
|
int dmc_pop_offset;
|
||||||
|
int dmc_pop_follow;
|
||||||
|
double clock;
|
||||||
|
unsigned int rate;
|
||||||
|
int pal;
|
||||||
|
int mode;
|
||||||
|
bool irq;
|
||||||
|
|
||||||
|
int counter[3]; // frequency dividers
|
||||||
|
int tphase; // triangle phase
|
||||||
|
unsigned int nfreq; // noise frequency
|
||||||
|
unsigned int dfreq; // DPCM frequency
|
||||||
|
|
||||||
|
unsigned int tri_freq;
|
||||||
|
int linear_counter;
|
||||||
|
int linear_counter_reload;
|
||||||
|
bool linear_counter_halt;
|
||||||
|
bool linear_counter_control;
|
||||||
|
|
||||||
|
int noise_volume;
|
||||||
|
unsigned int noise, noise_tap;
|
||||||
|
|
||||||
|
// noise envelope
|
||||||
|
bool envelope_loop;
|
||||||
|
bool envelope_disable;
|
||||||
|
bool envelope_write;
|
||||||
|
int envelope_div_period;
|
||||||
|
int envelope_div;
|
||||||
|
int envelope_counter;
|
||||||
|
|
||||||
|
bool enable[2]; // tri/noise enable
|
||||||
|
int length_counter[2]; // 0=tri, 1=noise
|
||||||
|
|
||||||
|
// frame sequencer
|
||||||
|
NES_APU* apu; // apu is clocked by DMC's frame sequencer
|
||||||
|
int frame_sequence_count; // current cycle count
|
||||||
|
int frame_sequence_length; // CPU cycles per FrameSequence
|
||||||
|
int frame_sequence_step; // current step of frame sequence
|
||||||
|
int frame_sequence_steps; // 4/5 steps per frame
|
||||||
|
bool frame_irq;
|
||||||
|
bool frame_irq_enable;
|
||||||
|
|
||||||
|
inline unsigned int calc_tri (unsigned int clocks);
|
||||||
|
inline unsigned int calc_dmc (unsigned int clocks);
|
||||||
|
inline unsigned int calc_noise (unsigned int clocks);
|
||||||
|
|
||||||
|
public:
|
||||||
|
unsigned int out[3];
|
||||||
|
NES_DMC ();
|
||||||
|
~NES_DMC ();
|
||||||
|
|
||||||
|
void InitializeTNDTable(double wt, double wn, double wd);
|
||||||
|
void SetPal (bool is_pal);
|
||||||
|
void SetAPU (NES_APU* apu_);
|
||||||
|
void SetMemory (std::function<void(unsigned short, unsigned int&)> r);
|
||||||
|
void FrameSequence(int s);
|
||||||
|
int GetDamp(){ return (damp<<1)|dac_lsb ; }
|
||||||
|
void TickFrameSequence (unsigned int clocks);
|
||||||
|
|
||||||
|
void Reset ();
|
||||||
|
void Tick (unsigned int clocks);
|
||||||
|
unsigned int Render (int b[2]);
|
||||||
|
bool Write (unsigned int adr, unsigned int val, unsigned int id=0);
|
||||||
|
bool Read (unsigned int adr, unsigned int & val, unsigned int id=0);
|
||||||
|
void SetRate (double rate);
|
||||||
|
void SetClock (double rate);
|
||||||
|
void SetOption (int, int);
|
||||||
|
void SetMask(int m){ mask = m; }
|
||||||
|
void SetStereoMix (int trk, short mixl, short mixr);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
385
src/engine/platform/sound/nes_nsfplay/nes_fds.cpp
Normal file
385
src/engine/platform/sound/nes_nsfplay/nes_fds.cpp
Normal file
|
@ -0,0 +1,385 @@
|
||||||
|
#include <cstring>
|
||||||
|
#include <math.h>
|
||||||
|
#include "nes_fds.h"
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
namespace xgm {
|
||||||
|
|
||||||
|
const int RC_BITS = 12;
|
||||||
|
|
||||||
|
NES_FDS::NES_FDS ()
|
||||||
|
{
|
||||||
|
option[OPT_CUTOFF] = 2000;
|
||||||
|
option[OPT_4085_RESET] = 0;
|
||||||
|
option[OPT_WRITE_PROTECT] = 0; // not used here, see nsfplay.cpp
|
||||||
|
|
||||||
|
rc_k = 0;
|
||||||
|
rc_l = (1<<RC_BITS);
|
||||||
|
|
||||||
|
SetClock (DEFAULT_CLOCK);
|
||||||
|
SetRate (DEFAULT_RATE);
|
||||||
|
sm[0] = 128;
|
||||||
|
sm[1] = 128;
|
||||||
|
|
||||||
|
Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
NES_FDS::~NES_FDS ()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void NES_FDS::SetStereoMix(int trk, short mixl, short mixr)
|
||||||
|
{
|
||||||
|
if (trk < 0) return;
|
||||||
|
if (trk > 1) return;
|
||||||
|
sm[0] = mixl;
|
||||||
|
sm[1] = mixr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NES_FDS::SetClock (double c)
|
||||||
|
{
|
||||||
|
clock = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NES_FDS::SetRate (double r)
|
||||||
|
{
|
||||||
|
rate = r;
|
||||||
|
|
||||||
|
// configure lowpass filter
|
||||||
|
double cutoff = double(option[OPT_CUTOFF]);
|
||||||
|
double leak = 0.0;
|
||||||
|
if (cutoff > 0)
|
||||||
|
leak = exp(-2.0 * 3.14159 * cutoff / rate);
|
||||||
|
rc_k = int(leak * double(1<<RC_BITS));
|
||||||
|
rc_l = (1<<RC_BITS) - rc_k;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NES_FDS::SetOption (int id, int val)
|
||||||
|
{
|
||||||
|
if(id<OPT_END) option[id] = val;
|
||||||
|
|
||||||
|
// update cutoff immediately
|
||||||
|
if (id == OPT_CUTOFF) SetRate(rate);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NES_FDS::Reset ()
|
||||||
|
{
|
||||||
|
master_io = true;
|
||||||
|
master_vol = 0;
|
||||||
|
last_freq = 0;
|
||||||
|
last_vol = 0;
|
||||||
|
|
||||||
|
rc_accum = 0;
|
||||||
|
|
||||||
|
for (int i=0; i<2; ++i)
|
||||||
|
{
|
||||||
|
::memset(wave[i], 0, sizeof(wave[i]));
|
||||||
|
freq[i] = 0;
|
||||||
|
phase[i] = 0;
|
||||||
|
}
|
||||||
|
wav_write = false;
|
||||||
|
wav_halt = true;
|
||||||
|
env_halt = true;
|
||||||
|
mod_halt = true;
|
||||||
|
mod_pos = 0;
|
||||||
|
mod_write_pos = 0;
|
||||||
|
|
||||||
|
for (int i=0; i<2; ++i)
|
||||||
|
{
|
||||||
|
env_mode[i] = false;
|
||||||
|
env_disable[i] = true;
|
||||||
|
env_timer[i] = 0;
|
||||||
|
env_speed[i] = 0;
|
||||||
|
env_out[i] = 0;
|
||||||
|
}
|
||||||
|
master_env_speed = 0xFF;
|
||||||
|
|
||||||
|
// NOTE: the FDS BIOS reset only does the following related to audio:
|
||||||
|
// $4023 = $00
|
||||||
|
// $4023 = $83 enables master_io
|
||||||
|
// $4080 = $80 output volume = 0, envelope disabled
|
||||||
|
// $408A = $E8 master envelope speed
|
||||||
|
Write(0x4023, 0x00);
|
||||||
|
Write(0x4023, 0x83);
|
||||||
|
Write(0x4080, 0x80);
|
||||||
|
Write(0x408A, 0xE8);
|
||||||
|
|
||||||
|
// reset other stuff
|
||||||
|
Write(0x4082, 0x00); // wav freq 0
|
||||||
|
Write(0x4083, 0x80); // wav disable
|
||||||
|
Write(0x4084, 0x80); // mod strength 0
|
||||||
|
Write(0x4085, 0x00); // mod position 0
|
||||||
|
Write(0x4086, 0x00); // mod freq 0
|
||||||
|
Write(0x4087, 0x80); // mod disable
|
||||||
|
Write(0x4089, 0x00); // wav write disable, max global volume}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NES_FDS::Tick (unsigned int clocks)
|
||||||
|
{
|
||||||
|
// clock envelopes
|
||||||
|
if (!env_halt && !wav_halt && (master_env_speed != 0))
|
||||||
|
{
|
||||||
|
for (int i=0; i<2; ++i)
|
||||||
|
{
|
||||||
|
if (!env_disable[i])
|
||||||
|
{
|
||||||
|
env_timer[i] += clocks;
|
||||||
|
unsigned int period = ((env_speed[i]+1) * master_env_speed) << 3;
|
||||||
|
while (env_timer[i] >= period)
|
||||||
|
{
|
||||||
|
// clock the envelope
|
||||||
|
if (env_mode[i])
|
||||||
|
{
|
||||||
|
if (env_out[i] < 32) ++env_out[i];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (env_out[i] > 0 ) --env_out[i];
|
||||||
|
}
|
||||||
|
env_timer[i] -= period;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// clock the mod table
|
||||||
|
if (!mod_halt)
|
||||||
|
{
|
||||||
|
// advance phase, adjust for modulator
|
||||||
|
unsigned int start_pos = phase[TMOD] >> 16;
|
||||||
|
phase[TMOD] += (clocks * freq[TMOD]);
|
||||||
|
unsigned int end_pos = phase[TMOD] >> 16;
|
||||||
|
|
||||||
|
// wrap the phase to the 64-step table (+ 16 bit accumulator)
|
||||||
|
phase[TMOD] = phase[TMOD] & 0x3FFFFF;
|
||||||
|
|
||||||
|
// execute all clocked steps
|
||||||
|
for (unsigned int p = start_pos; p < end_pos; ++p)
|
||||||
|
{
|
||||||
|
int wv = wave[TMOD][p & 0x3F];
|
||||||
|
if (wv == 4) // 4 resets mod position
|
||||||
|
mod_pos = 0;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const int BIAS[8] = { 0, 1, 2, 4, 0, -4, -2, -1 };
|
||||||
|
mod_pos += BIAS[wv];
|
||||||
|
mod_pos &= 0x7F; // 7-bit clamp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// clock the wav table
|
||||||
|
if (!wav_halt)
|
||||||
|
{
|
||||||
|
// complex mod calculation
|
||||||
|
int mod = 0;
|
||||||
|
if (env_out[EMOD] != 0) // skip if modulator off
|
||||||
|
{
|
||||||
|
// convert mod_pos to 7-bit signed
|
||||||
|
int pos = (mod_pos < 64) ? mod_pos : (mod_pos-128);
|
||||||
|
|
||||||
|
// multiply pos by gain,
|
||||||
|
// shift off 4 bits but with odd "rounding" behaviour
|
||||||
|
int temp = pos * env_out[EMOD];
|
||||||
|
int rem = temp & 0x0F;
|
||||||
|
temp >>= 4;
|
||||||
|
if ((rem > 0) && ((temp & 0x80) == 0))
|
||||||
|
{
|
||||||
|
if (pos < 0) temp -= 1;
|
||||||
|
else temp += 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// wrap if range is exceeded
|
||||||
|
while (temp >= 192) temp -= 256;
|
||||||
|
while (temp < -64) temp += 256;
|
||||||
|
|
||||||
|
// multiply result by pitch,
|
||||||
|
// shift off 6 bits, round to nearest
|
||||||
|
temp = freq[TWAV] * temp;
|
||||||
|
rem = temp & 0x3F;
|
||||||
|
temp >>= 6;
|
||||||
|
if (rem >= 32) temp += 1;
|
||||||
|
|
||||||
|
mod = temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
// advance wavetable position
|
||||||
|
int f = freq[TWAV] + mod;
|
||||||
|
phase[TWAV] = phase[TWAV] + (clocks * f);
|
||||||
|
phase[TWAV] = phase[TWAV] & 0x3FFFFF; // wrap
|
||||||
|
|
||||||
|
// store for trackinfo
|
||||||
|
last_freq = f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// output volume caps at 32
|
||||||
|
int vol_out = env_out[EVOL];
|
||||||
|
if (vol_out > 32) vol_out = 32;
|
||||||
|
|
||||||
|
// final output
|
||||||
|
if (!wav_write)
|
||||||
|
fout = wave[TWAV][(phase[TWAV]>>16)&0x3F] * vol_out;
|
||||||
|
|
||||||
|
// NOTE: during wav_halt, the unit still outputs (at phase 0)
|
||||||
|
// and volume can affect it if the first sample is nonzero.
|
||||||
|
// haven't worked out 100% of the conditions for volume to
|
||||||
|
// effect (vol envelope does not seem to run, but am unsure)
|
||||||
|
// but this implementation is very close to correct
|
||||||
|
|
||||||
|
// store for trackinfo
|
||||||
|
last_vol = vol_out;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int NES_FDS::Render (int b[2])
|
||||||
|
{
|
||||||
|
// 8 bit approximation of master volume
|
||||||
|
const double MASTER_VOL = 2.4 * 1223.0; // max FDS vol vs max APU square (arbitrarily 1223)
|
||||||
|
const double MAX_OUT = 32.0f * 63.0f; // value that should map to master vol
|
||||||
|
const int MASTER[4] = {
|
||||||
|
int((MASTER_VOL / MAX_OUT) * 256.0 * 2.0f / 2.0f),
|
||||||
|
int((MASTER_VOL / MAX_OUT) * 256.0 * 2.0f / 3.0f),
|
||||||
|
int((MASTER_VOL / MAX_OUT) * 256.0 * 2.0f / 4.0f),
|
||||||
|
int((MASTER_VOL / MAX_OUT) * 256.0 * 2.0f / 5.0f) };
|
||||||
|
|
||||||
|
int v = fout * MASTER[master_vol] >> 8;
|
||||||
|
|
||||||
|
// lowpass RC filter
|
||||||
|
int rc_out = ((rc_accum * rc_k) + (v * rc_l)) >> RC_BITS;
|
||||||
|
rc_accum = rc_out;
|
||||||
|
v = rc_out;
|
||||||
|
|
||||||
|
// output mix
|
||||||
|
int m = mask ? 0 : v;
|
||||||
|
b[0] = (m * sm[0]) >> 7;
|
||||||
|
b[1] = (m * sm[1]) >> 7;
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NES_FDS::Write (unsigned int adr, unsigned int val, unsigned int id)
|
||||||
|
{
|
||||||
|
// $4023 master I/O enable/disable
|
||||||
|
if (adr == 0x4023)
|
||||||
|
{
|
||||||
|
master_io = ((val & 2) != 0);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!master_io)
|
||||||
|
return false;
|
||||||
|
if (adr < 0x4040 || adr > 0x408A)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (adr < 0x4080) // $4040-407F wave table write
|
||||||
|
{
|
||||||
|
if (wav_write)
|
||||||
|
wave[TWAV][adr - 0x4040] = val & 0x3F;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (adr & 0x00FF)
|
||||||
|
{
|
||||||
|
case 0x80: // $4080 volume envelope
|
||||||
|
env_disable[EVOL] = ((val & 0x80) != 0);
|
||||||
|
env_mode[EVOL] = ((val & 0x40) != 0);
|
||||||
|
env_timer[EVOL] = 0;
|
||||||
|
env_speed[EVOL] = val & 0x3F;
|
||||||
|
if (env_disable[EVOL])
|
||||||
|
env_out[EVOL] = env_speed[EVOL];
|
||||||
|
return true;
|
||||||
|
case 0x81: // $4081 ---
|
||||||
|
return false;
|
||||||
|
case 0x82: // $4082 wave frequency low
|
||||||
|
freq[TWAV] = (freq[TWAV] & 0xF00) | val;
|
||||||
|
return true;
|
||||||
|
case 0x83: // $4083 wave frequency high / enables
|
||||||
|
freq[TWAV] = (freq[TWAV] & 0x0FF) | ((val & 0x0F) << 8);
|
||||||
|
wav_halt = ((val & 0x80) != 0);
|
||||||
|
env_halt = ((val & 0x40) != 0);
|
||||||
|
if (wav_halt)
|
||||||
|
phase[TWAV] = 0;
|
||||||
|
if (env_halt)
|
||||||
|
{
|
||||||
|
env_timer[EMOD] = 0;
|
||||||
|
env_timer[EVOL] = 0;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
case 0x84: // $4084 mod envelope
|
||||||
|
env_disable[EMOD] = ((val & 0x80) != 0);
|
||||||
|
env_mode[EMOD] = ((val & 0x40) != 0);
|
||||||
|
env_timer[EMOD] = 0;
|
||||||
|
env_speed[EMOD] = val & 0x3F;
|
||||||
|
if (env_disable[EMOD])
|
||||||
|
env_out[EMOD] = env_speed[EMOD];
|
||||||
|
return true;
|
||||||
|
case 0x85: // $4085 mod position
|
||||||
|
mod_pos = val & 0x7F;
|
||||||
|
// not hardware accurate., but prevents detune due to cycle inaccuracies
|
||||||
|
// (notably in Bio Miracle Bokutte Upa)
|
||||||
|
if (option[OPT_4085_RESET])
|
||||||
|
phase[TMOD] = mod_write_pos << 16;
|
||||||
|
return true;
|
||||||
|
case 0x86: // $4086 mod frequency low
|
||||||
|
freq[TMOD] = (freq[TMOD] & 0xF00) | val;
|
||||||
|
return true;
|
||||||
|
case 0x87: // $4087 mod frequency high / enable
|
||||||
|
freq[TMOD] = (freq[TMOD] & 0x0FF) | ((val & 0x0F) << 8);
|
||||||
|
mod_halt = ((val & 0x80) != 0);
|
||||||
|
if (mod_halt)
|
||||||
|
phase[TMOD] = phase[TMOD] & 0x3F0000; // reset accumulator phase
|
||||||
|
return true;
|
||||||
|
case 0x88: // $4088 mod table write
|
||||||
|
if (mod_halt)
|
||||||
|
{
|
||||||
|
// writes to current playback position (there is no direct way to set phase)
|
||||||
|
wave[TMOD][(phase[TMOD] >> 16) & 0x3F] = val & 0x07;
|
||||||
|
phase[TMOD] = (phase[TMOD] + 0x010000) & 0x3FFFFF;
|
||||||
|
wave[TMOD][(phase[TMOD] >> 16) & 0x3F] = val & 0x07;
|
||||||
|
phase[TMOD] = (phase[TMOD] + 0x010000) & 0x3FFFFF;
|
||||||
|
mod_write_pos = phase[TMOD] >> 16; // used by OPT_4085_RESET
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
case 0x89: // $4089 wave write enable, master volume
|
||||||
|
wav_write = ((val & 0x80) != 0);
|
||||||
|
master_vol = val & 0x03;
|
||||||
|
return true;
|
||||||
|
case 0x8A: // $408A envelope speed
|
||||||
|
master_env_speed = val;
|
||||||
|
// haven't tested whether this register resets phase on hardware,
|
||||||
|
// but this ensures my inplementation won't spam envelope clocks
|
||||||
|
// if this value suddenly goes low.
|
||||||
|
env_timer[EMOD] = 0;
|
||||||
|
env_timer[EVOL] = 0;
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NES_FDS::Read (unsigned int adr, unsigned int & val, unsigned int id)
|
||||||
|
{
|
||||||
|
if (adr >= 0x4040 && adr <= 0x407F)
|
||||||
|
{
|
||||||
|
// TODO: if wav_write is not enabled, the
|
||||||
|
// read address may not be reliable? need
|
||||||
|
// to test this on hardware.
|
||||||
|
val = wave[TWAV][adr - 0x4040];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (adr == 0x4090) // $4090 read volume envelope
|
||||||
|
{
|
||||||
|
val = env_out[EVOL] | 0x40;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (adr == 0x4092) // $4092 read mod envelope
|
||||||
|
{
|
||||||
|
val = env_out[EMOD] | 0x40;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
73
src/engine/platform/sound/nes_nsfplay/nes_fds.h
Normal file
73
src/engine/platform/sound/nes_nsfplay/nes_fds.h
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
#ifndef _NES_FDS_H_
|
||||||
|
#define _NES_FDS_H_
|
||||||
|
|
||||||
|
namespace xgm {
|
||||||
|
|
||||||
|
class NES_FDS
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
OPT_CUTOFF=0,
|
||||||
|
OPT_4085_RESET,
|
||||||
|
OPT_WRITE_PROTECT,
|
||||||
|
OPT_END
|
||||||
|
};
|
||||||
|
|
||||||
|
protected:
|
||||||
|
double rate, clock;
|
||||||
|
int mask;
|
||||||
|
int sm[2]; // stereo mix
|
||||||
|
int fout; // current output
|
||||||
|
int option[OPT_END];
|
||||||
|
|
||||||
|
bool master_io;
|
||||||
|
unsigned int master_vol;
|
||||||
|
unsigned int last_freq; // for trackinfo
|
||||||
|
unsigned int last_vol; // for trackinfo
|
||||||
|
|
||||||
|
// two wavetables
|
||||||
|
enum { TMOD=0, TWAV=1 };
|
||||||
|
int wave[2][64];
|
||||||
|
unsigned int freq[2];
|
||||||
|
unsigned int phase[2];
|
||||||
|
bool wav_write;
|
||||||
|
bool wav_halt;
|
||||||
|
bool env_halt;
|
||||||
|
bool mod_halt;
|
||||||
|
unsigned int mod_pos;
|
||||||
|
unsigned int mod_write_pos;
|
||||||
|
|
||||||
|
// two ramp envelopes
|
||||||
|
enum { EMOD=0, EVOL=1 };
|
||||||
|
bool env_mode[2];
|
||||||
|
bool env_disable[2];
|
||||||
|
unsigned int env_timer[2];
|
||||||
|
unsigned int env_speed[2];
|
||||||
|
unsigned int env_out[2];
|
||||||
|
unsigned int master_env_speed;
|
||||||
|
|
||||||
|
// 1-pole RC lowpass filter
|
||||||
|
int rc_accum;
|
||||||
|
int rc_k;
|
||||||
|
int rc_l;
|
||||||
|
|
||||||
|
public:
|
||||||
|
NES_FDS ();
|
||||||
|
~NES_FDS ();
|
||||||
|
|
||||||
|
void Reset ();
|
||||||
|
void Tick (unsigned int clocks);
|
||||||
|
unsigned int Render (int b[2]);
|
||||||
|
bool Write (unsigned int adr, unsigned int val, unsigned int id=0);
|
||||||
|
bool Read (unsigned int adr, unsigned int & val, unsigned int id=0);
|
||||||
|
void SetRate (double);
|
||||||
|
void SetClock (double);
|
||||||
|
void SetOption (int, int);
|
||||||
|
void SetMask(int m){ mask = m&1; }
|
||||||
|
void SetStereoMix (int trk, short mixl, short mixr);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace xgm
|
||||||
|
|
||||||
|
#endif
|
372
src/engine/platform/sound/nes_nsfplay/nes_mmc5.cpp
Normal file
372
src/engine/platform/sound/nes_nsfplay/nes_mmc5.cpp
Normal file
|
@ -0,0 +1,372 @@
|
||||||
|
#include "nes_mmc5.h"
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
namespace xgm
|
||||||
|
{
|
||||||
|
|
||||||
|
NES_MMC5::NES_MMC5 ()
|
||||||
|
{
|
||||||
|
SetClock (DEFAULT_CLOCK);
|
||||||
|
SetRate (DEFAULT_RATE);
|
||||||
|
option[OPT_NONLINEAR_MIXER] = true;
|
||||||
|
option[OPT_PHASE_REFRESH] = true;
|
||||||
|
frame_sequence_count = 0;
|
||||||
|
|
||||||
|
// square nonlinear mix, same as 2A03
|
||||||
|
square_table[0] = 0;
|
||||||
|
for(int i=1;i<32;i++)
|
||||||
|
square_table[i]=(int)((8192.0*95.88)/(8128.0/i+100));
|
||||||
|
|
||||||
|
// 2A03 style nonlinear pcm mix with double the bits
|
||||||
|
//pcm_table[0] = 0;
|
||||||
|
//int wd = 22638;
|
||||||
|
//for(int d=1;d<256; ++d)
|
||||||
|
// pcm_table[d] = (int)((8192.0*159.79)/(100.0+1.0/((double)d/wd)));
|
||||||
|
|
||||||
|
// linear pcm mix (actual hardware seems closer to this)
|
||||||
|
pcm_table[0] = 0;
|
||||||
|
double pcm_scale = 32.0;
|
||||||
|
for (int d=1; d<256; ++d)
|
||||||
|
pcm_table[d] = (int)(double(d) * pcm_scale);
|
||||||
|
|
||||||
|
// stereo mix
|
||||||
|
for(int c=0;c<2;++c)
|
||||||
|
for(int t=0;t<3;++t)
|
||||||
|
sm[c][t] = 128;
|
||||||
|
}
|
||||||
|
|
||||||
|
NES_MMC5::~NES_MMC5 ()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void NES_MMC5::Reset ()
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
scounter[0] = 0;
|
||||||
|
scounter[1] = 0;
|
||||||
|
sphase[0] = 0;
|
||||||
|
sphase[1] = 0;
|
||||||
|
|
||||||
|
envelope_div[0] = 0;
|
||||||
|
envelope_div[1] = 0;
|
||||||
|
length_counter[0] = 0;
|
||||||
|
length_counter[1] = 0;
|
||||||
|
envelope_counter[0] = 0;
|
||||||
|
envelope_counter[1] = 0;
|
||||||
|
frame_sequence_count = 0;
|
||||||
|
|
||||||
|
for (i = 0; i < 8; i++)
|
||||||
|
Write (0x5000 + i, 0);
|
||||||
|
|
||||||
|
Write(0x5015, 0);
|
||||||
|
|
||||||
|
for (i = 0; i < 3; ++i)
|
||||||
|
out[i] = 0;
|
||||||
|
|
||||||
|
mask = 0;
|
||||||
|
pcm = 0; // PCM channel
|
||||||
|
pcm_mode = false; // write mode
|
||||||
|
|
||||||
|
SetRate(rate);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NES_MMC5::SetOption (int id, int val)
|
||||||
|
{
|
||||||
|
if(id<OPT_END) option[id] = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NES_MMC5::SetClock (double c)
|
||||||
|
{
|
||||||
|
this->clock = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NES_MMC5::SetRate (double r)
|
||||||
|
{
|
||||||
|
rate = r ? r : DEFAULT_RATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NES_MMC5::FrameSequence ()
|
||||||
|
{
|
||||||
|
// 240hz clock
|
||||||
|
for (int i=0; i < 2; ++i)
|
||||||
|
{
|
||||||
|
bool divider = false;
|
||||||
|
if (envelope_write[i])
|
||||||
|
{
|
||||||
|
envelope_write[i] = false;
|
||||||
|
envelope_counter[i] = 15;
|
||||||
|
envelope_div[i] = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
++envelope_div[i];
|
||||||
|
if (envelope_div[i] > envelope_div_period[i])
|
||||||
|
{
|
||||||
|
divider = true;
|
||||||
|
envelope_div[i] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (divider)
|
||||||
|
{
|
||||||
|
if (envelope_loop[i] && envelope_counter[i] == 0)
|
||||||
|
envelope_counter[i] = 15;
|
||||||
|
else if (envelope_counter[i] > 0)
|
||||||
|
--envelope_counter[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MMC5 length counter is clocked at 240hz, unlike 2A03
|
||||||
|
for (int i=0; i < 2; ++i)
|
||||||
|
{
|
||||||
|
if (!envelope_loop[i] && (length_counter[i] > 0))
|
||||||
|
--length_counter[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int NES_MMC5::calc_sqr (int i, unsigned int clocks)
|
||||||
|
{
|
||||||
|
static const short sqrtbl[4][16] = {
|
||||||
|
{0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||||
|
{0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||||
|
{0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0},
|
||||||
|
{1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
|
||||||
|
};
|
||||||
|
|
||||||
|
scounter[i] += clocks;
|
||||||
|
while (scounter[i] > freq[i])
|
||||||
|
{
|
||||||
|
sphase[i] = (sphase[i] + 1) & 15;
|
||||||
|
scounter[i] -= (freq[i] + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int ret = 0;
|
||||||
|
if (length_counter[i] > 0)
|
||||||
|
{
|
||||||
|
// note MMC5 does not silence the highest 8 frequencies like APU,
|
||||||
|
// because this is done by the sweep unit.
|
||||||
|
|
||||||
|
int v = envelope_disable[i] ? volume[i] : envelope_counter[i];
|
||||||
|
ret = sqrtbl[duty[i]][sphase[i]] ? v : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NES_MMC5::TickFrameSequence (unsigned int clocks)
|
||||||
|
{
|
||||||
|
frame_sequence_count += clocks;
|
||||||
|
while (frame_sequence_count > 7458)
|
||||||
|
{
|
||||||
|
FrameSequence();
|
||||||
|
frame_sequence_count -= 7458;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NES_MMC5::Tick (unsigned int clocks)
|
||||||
|
{
|
||||||
|
out[0] = calc_sqr(0, clocks);
|
||||||
|
out[1] = calc_sqr(1, clocks);
|
||||||
|
out[2] = pcm;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int NES_MMC5::Render (int b[2])
|
||||||
|
{
|
||||||
|
out[0] = (mask & 1) ? 0 : out[0];
|
||||||
|
out[1] = (mask & 2) ? 0 : out[1];
|
||||||
|
out[2] = (mask & 4) ? 0 : out[2];
|
||||||
|
|
||||||
|
int m[3];
|
||||||
|
|
||||||
|
if(option[OPT_NONLINEAR_MIXER])
|
||||||
|
{
|
||||||
|
// squares nonlinear
|
||||||
|
int voltage = square_table[out[0] + out[1]];
|
||||||
|
m[0] = out[0] << 6;
|
||||||
|
m[1] = out[1] << 6;
|
||||||
|
int ref = m[0] + m[1];
|
||||||
|
if (ref > 0)
|
||||||
|
{
|
||||||
|
m[0] = (m[0] * voltage) / ref;
|
||||||
|
m[1] = (m[1] * voltage) / ref;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m[0] = voltage;
|
||||||
|
m[1] = voltage;
|
||||||
|
}
|
||||||
|
|
||||||
|
// pcm nonlinear
|
||||||
|
m[2] = pcm_table[out[2]];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// squares
|
||||||
|
m[0] = out[0] << 6;
|
||||||
|
m[1] = out[1] << 6;
|
||||||
|
|
||||||
|
// pcm channel
|
||||||
|
m[2] = out[2] << 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
// note polarity is flipped on output
|
||||||
|
|
||||||
|
b[0] = m[0] * -sm[0][0];
|
||||||
|
b[0] += m[1] * -sm[0][1];
|
||||||
|
b[0] += m[2] * -sm[0][2];
|
||||||
|
b[0] >>= 7;
|
||||||
|
|
||||||
|
b[1] = m[0] * -sm[1][0];
|
||||||
|
b[1] += m[1] * -sm[1][1];
|
||||||
|
b[1] += m[2] * -sm[1][2];
|
||||||
|
b[1] >>= 7;
|
||||||
|
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NES_MMC5::Write (unsigned int adr, unsigned int val, unsigned int id)
|
||||||
|
{
|
||||||
|
int ch;
|
||||||
|
|
||||||
|
static const unsigned char length_table[32] = {
|
||||||
|
0x0A, 0xFE,
|
||||||
|
0x14, 0x02,
|
||||||
|
0x28, 0x04,
|
||||||
|
0x50, 0x06,
|
||||||
|
0xA0, 0x08,
|
||||||
|
0x3C, 0x0A,
|
||||||
|
0x0E, 0x0C,
|
||||||
|
0x1A, 0x0E,
|
||||||
|
0x0C, 0x10,
|
||||||
|
0x18, 0x12,
|
||||||
|
0x30, 0x14,
|
||||||
|
0x60, 0x16,
|
||||||
|
0xC0, 0x18,
|
||||||
|
0x48, 0x1A,
|
||||||
|
0x10, 0x1C,
|
||||||
|
0x20, 0x1E
|
||||||
|
};
|
||||||
|
|
||||||
|
if ((0x5c00 <= adr) && (adr < 0x5ff0))
|
||||||
|
{
|
||||||
|
ram[adr & 0x3ff] = val;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if ((0x5000 <= adr) && (adr < 0x5008))
|
||||||
|
{
|
||||||
|
reg[adr & 0x7] = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (adr)
|
||||||
|
{
|
||||||
|
case 0x5000:
|
||||||
|
case 0x5004:
|
||||||
|
ch = (adr >> 2) & 1;
|
||||||
|
volume[ch] = val & 15;
|
||||||
|
envelope_disable[ch] = (val >> 4) & 1;
|
||||||
|
envelope_loop[ch] = (val >> 5) & 1;
|
||||||
|
envelope_div_period[ch] = (val & 15);
|
||||||
|
duty[ch] = (val >> 6) & 3;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x5002:
|
||||||
|
case 0x5006:
|
||||||
|
ch = (adr >> 2) & 1;
|
||||||
|
freq[ch] = val + (freq[ch] & 0x700);
|
||||||
|
if (scounter[ch] > freq[ch]) scounter[ch] = freq[ch];
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x5003:
|
||||||
|
case 0x5007:
|
||||||
|
ch = (adr >> 2) & 1;
|
||||||
|
freq[ch] = (freq[ch] & 0xff) + ((val & 7) << 8);
|
||||||
|
if (scounter[ch] > freq[ch]) scounter[ch] = freq[ch];
|
||||||
|
// phase reset
|
||||||
|
if (option[OPT_PHASE_REFRESH])
|
||||||
|
sphase[ch] = 0;
|
||||||
|
envelope_write[ch] = true;
|
||||||
|
if (enable[ch])
|
||||||
|
{
|
||||||
|
length_counter[ch] = length_table[(val >> 3) & 0x1f];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
// PCM channel control
|
||||||
|
case 0x5010:
|
||||||
|
pcm_mode = ((val & 1) != 0); // 0 = write, 1 = read
|
||||||
|
break;
|
||||||
|
|
||||||
|
// PCM channel control
|
||||||
|
case 0x5011:
|
||||||
|
if (!pcm_mode)
|
||||||
|
{
|
||||||
|
val &= 0xFF;
|
||||||
|
if (val != 0) pcm = val;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x5015:
|
||||||
|
enable[0] = (val & 1) ? true : false;
|
||||||
|
enable[1] = (val & 2) ? true : false;
|
||||||
|
if (!enable[0])
|
||||||
|
length_counter[0] = 0;
|
||||||
|
if (!enable[1])
|
||||||
|
length_counter[1] = 0;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x5205:
|
||||||
|
mreg[0] = val;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x5206:
|
||||||
|
mreg[1] = val;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NES_MMC5::Read (unsigned int adr, unsigned int & val, unsigned int id)
|
||||||
|
{
|
||||||
|
|
||||||
|
if ((0x5000 <= adr) && (adr < 0x5008))
|
||||||
|
{
|
||||||
|
val = reg[adr&0x7];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if(adr == 0x5015)
|
||||||
|
{
|
||||||
|
val = (enable[1]?2:0)|(enable[0]?1:0);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((0x5c00 <= adr) && (adr < 0x5ff0))
|
||||||
|
{
|
||||||
|
val = ram[adr & 0x3ff];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (adr == 0x5205)
|
||||||
|
{
|
||||||
|
val = (mreg[0] * mreg[1]) & 0xff;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (adr == 0x5206)
|
||||||
|
{
|
||||||
|
val = (mreg[0] * mreg[1]) >> 8;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NES_MMC5::SetStereoMix(int trk, short mixl, short mixr)
|
||||||
|
{
|
||||||
|
if (trk < 0) return;
|
||||||
|
if (trk > 2) return;
|
||||||
|
sm[0][trk] = mixl;
|
||||||
|
sm[1][trk] = mixr;
|
||||||
|
}
|
||||||
|
}// namespace
|
67
src/engine/platform/sound/nes_nsfplay/nes_mmc5.h
Normal file
67
src/engine/platform/sound/nes_nsfplay/nes_mmc5.h
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
#ifndef _NES_MMC5_H_
|
||||||
|
#define _NES_MMC5_H_
|
||||||
|
|
||||||
|
namespace xgm
|
||||||
|
{
|
||||||
|
class NES_MMC5
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum
|
||||||
|
{ OPT_NONLINEAR_MIXER=0, OPT_PHASE_REFRESH, OPT_END };
|
||||||
|
|
||||||
|
protected:
|
||||||
|
int option[OPT_END];
|
||||||
|
int mask;
|
||||||
|
int sm[2][3]; // stereo panning
|
||||||
|
unsigned char ram[0x6000 - 0x5c00];
|
||||||
|
unsigned char reg[8];
|
||||||
|
unsigned char mreg[2];
|
||||||
|
unsigned char pcm; // PCM channel
|
||||||
|
bool pcm_mode; // PCM channel
|
||||||
|
|
||||||
|
unsigned int scounter[2]; // frequency divider
|
||||||
|
unsigned int sphase[2]; // phase counter
|
||||||
|
|
||||||
|
unsigned int duty[2];
|
||||||
|
unsigned int volume[2];
|
||||||
|
unsigned int freq[2];
|
||||||
|
int out[3];
|
||||||
|
bool enable[2];
|
||||||
|
|
||||||
|
bool envelope_disable[2]; // エンベロープ有効フラグ
|
||||||
|
bool envelope_loop[2]; // エンベロープループ
|
||||||
|
bool envelope_write[2];
|
||||||
|
int envelope_div_period[2];
|
||||||
|
int envelope_div[2];
|
||||||
|
int envelope_counter[2];
|
||||||
|
|
||||||
|
int length_counter[2];
|
||||||
|
|
||||||
|
int frame_sequence_count;
|
||||||
|
|
||||||
|
double clock, rate;
|
||||||
|
int calc_sqr (int i, unsigned int clocks);
|
||||||
|
int square_table[32];
|
||||||
|
int pcm_table[256];
|
||||||
|
public:
|
||||||
|
NES_MMC5 ();
|
||||||
|
~NES_MMC5 ();
|
||||||
|
|
||||||
|
void FrameSequence ();
|
||||||
|
void TickFrameSequence (unsigned int clocks);
|
||||||
|
|
||||||
|
void Reset ();
|
||||||
|
void Tick (unsigned int clocks);
|
||||||
|
unsigned int Render (int b[2]);
|
||||||
|
bool Write (unsigned int adr, unsigned int val, unsigned int id=0);
|
||||||
|
bool Read (unsigned int adr, unsigned int & val, unsigned int id=0);
|
||||||
|
void SetOption (int id, int b);
|
||||||
|
void SetClock (double);
|
||||||
|
void SetRate (double);
|
||||||
|
void SetMask (int m){ mask = m; }
|
||||||
|
void SetStereoMix (int trk, short mixl, short mixr);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
332
src/engine/platform/sound/nes_nsfplay/nes_n106.cpp
Normal file
332
src/engine/platform/sound/nes_nsfplay/nes_n106.cpp
Normal file
|
@ -0,0 +1,332 @@
|
||||||
|
#include <cstring>
|
||||||
|
#include "nes_n106.h"
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
namespace xgm {
|
||||||
|
|
||||||
|
NES_N106::NES_N106 ()
|
||||||
|
{
|
||||||
|
option[OPT_SERIAL] = 0;
|
||||||
|
option[OPT_PHASE_READ_ONLY] = 0;
|
||||||
|
option[OPT_LIMIT_WAVELENGTH] = 0;
|
||||||
|
SetClock (DEFAULT_CLOCK);
|
||||||
|
SetRate (DEFAULT_RATE);
|
||||||
|
for (int i=0; i < 8; ++i)
|
||||||
|
{
|
||||||
|
sm[0][i] = 128;
|
||||||
|
sm[1][i] = 128;
|
||||||
|
}
|
||||||
|
Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
NES_N106::~NES_N106 ()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void NES_N106::SetStereoMix (int trk, short mixl, short mixr)
|
||||||
|
{
|
||||||
|
if (trk < 0 || trk >= 8) return;
|
||||||
|
trk = 7-trk; // displayed channels are inverted
|
||||||
|
sm[0][trk] = mixl;
|
||||||
|
sm[1][trk] = mixr;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void NES_N106::SetClock (double c)
|
||||||
|
{
|
||||||
|
clock = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NES_N106::SetRate (double r)
|
||||||
|
{
|
||||||
|
rate = r;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NES_N106::SetMask (int m)
|
||||||
|
{
|
||||||
|
// bit reverse the mask,
|
||||||
|
// N163 waves are displayed in reverse order
|
||||||
|
mask = 0
|
||||||
|
| ((m & (1<<0)) ? (1<<7) : 0)
|
||||||
|
| ((m & (1<<1)) ? (1<<6) : 0)
|
||||||
|
| ((m & (1<<2)) ? (1<<5) : 0)
|
||||||
|
| ((m & (1<<3)) ? (1<<4) : 0)
|
||||||
|
| ((m & (1<<4)) ? (1<<3) : 0)
|
||||||
|
| ((m & (1<<5)) ? (1<<2) : 0)
|
||||||
|
| ((m & (1<<6)) ? (1<<1) : 0)
|
||||||
|
| ((m & (1<<7)) ? (1<<0) : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NES_N106::SetOption (int id, int val)
|
||||||
|
{
|
||||||
|
if (id<OPT_END) option[id] = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NES_N106::Reset ()
|
||||||
|
{
|
||||||
|
master_disable = false;
|
||||||
|
::memset(reg, 0, sizeof(reg));
|
||||||
|
reg_select = 0;
|
||||||
|
reg_advance = false;
|
||||||
|
tick_channel = 0;
|
||||||
|
tick_clock = 0;
|
||||||
|
render_channel = 0;
|
||||||
|
render_clock = 0;
|
||||||
|
render_subclock = 0;
|
||||||
|
|
||||||
|
for (int i=0; i<8; ++i) fout[i] = 0;
|
||||||
|
|
||||||
|
Write(0xE000, 0x00); // master disable off
|
||||||
|
Write(0xF800, 0x80); // select $00 with auto-increment
|
||||||
|
for (unsigned int i=0; i<0x80; ++i) // set all regs to 0
|
||||||
|
{
|
||||||
|
Write(0x4800, 0x00);
|
||||||
|
}
|
||||||
|
Write(0xF800, 0x00); // select $00 without auto-increment
|
||||||
|
}
|
||||||
|
|
||||||
|
void NES_N106::Tick (unsigned int clocks)
|
||||||
|
{
|
||||||
|
if (master_disable) return;
|
||||||
|
|
||||||
|
int channels = get_channels();
|
||||||
|
|
||||||
|
tick_clock += clocks;
|
||||||
|
render_clock += clocks; // keep render in sync
|
||||||
|
while (tick_clock > 0)
|
||||||
|
{
|
||||||
|
int channel = 7-tick_channel;
|
||||||
|
|
||||||
|
unsigned int phase = get_phase(channel);
|
||||||
|
unsigned int freq = get_freq(channel);
|
||||||
|
unsigned int len = get_len(channel);
|
||||||
|
unsigned int off = get_off(channel);
|
||||||
|
int vol = get_vol(channel);
|
||||||
|
|
||||||
|
// accumulate 24-bit phase
|
||||||
|
phase = (phase + freq) & 0x00FFFFFF;
|
||||||
|
|
||||||
|
// wrap phase if wavelength exceeded
|
||||||
|
unsigned int hilen = len << 16;
|
||||||
|
while (phase >= hilen) phase -= hilen;
|
||||||
|
|
||||||
|
// write back phase
|
||||||
|
set_phase(phase, channel);
|
||||||
|
|
||||||
|
// fetch sample (note: N163 output is centred at 8, and inverted w.r.t 2A03)
|
||||||
|
int sample = 8 - get_sample(((phase >> 16) + off) & 0xFF);
|
||||||
|
fout[channel] = sample * vol;
|
||||||
|
|
||||||
|
// cycle to next channel every 15 clocks
|
||||||
|
tick_clock -= 15;
|
||||||
|
++tick_channel;
|
||||||
|
if (tick_channel >= channels)
|
||||||
|
tick_channel = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int NES_N106::Render (int b[2])
|
||||||
|
{
|
||||||
|
b[0] = 0;
|
||||||
|
b[1] = 0;
|
||||||
|
if (master_disable) return 2;
|
||||||
|
|
||||||
|
int channels = get_channels();
|
||||||
|
|
||||||
|
if (option[OPT_SERIAL]) // hardware accurate serial multiplexing
|
||||||
|
{
|
||||||
|
// this could be made more efficient than going clock-by-clock
|
||||||
|
// but this way is simpler
|
||||||
|
int clocks = render_clock;
|
||||||
|
while (clocks > 0)
|
||||||
|
{
|
||||||
|
int c = 7-render_channel;
|
||||||
|
if (0 == ((mask >> c) & 1))
|
||||||
|
{
|
||||||
|
b[0] += fout[c] * sm[0][c];
|
||||||
|
b[1] += fout[c] * sm[1][c];
|
||||||
|
}
|
||||||
|
|
||||||
|
++render_subclock;
|
||||||
|
if (render_subclock >= 15) // each channel gets a 15-cycle slice
|
||||||
|
{
|
||||||
|
render_subclock = 0;
|
||||||
|
++render_channel;
|
||||||
|
if (render_channel >= channels)
|
||||||
|
render_channel = 0;
|
||||||
|
}
|
||||||
|
--clocks;
|
||||||
|
}
|
||||||
|
|
||||||
|
// increase output level by 1 bits (7 bits already added from sm)
|
||||||
|
b[0] <<= 1;
|
||||||
|
b[1] <<= 1;
|
||||||
|
|
||||||
|
// average the output
|
||||||
|
if (render_clock > 0)
|
||||||
|
{
|
||||||
|
b[0] /= render_clock;
|
||||||
|
b[1] /= render_clock;
|
||||||
|
}
|
||||||
|
render_clock = 0;
|
||||||
|
}
|
||||||
|
else // just mix all channels
|
||||||
|
{
|
||||||
|
for (int i = (8-channels); i<8; ++i)
|
||||||
|
{
|
||||||
|
if (0 == ((mask >> i) & 1))
|
||||||
|
{
|
||||||
|
b[0] += fout[i] * sm[0][i];
|
||||||
|
b[1] += fout[i] * sm[1][i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// mix together, increase output level by 8 bits, roll off 7 bits from sm
|
||||||
|
int MIX[9] = { 256/1, 256/1, 256/2, 256/3, 256/4, 256/5, 256/6, 256/6, 256/6 };
|
||||||
|
b[0] = (b[0] * MIX[channels]) >> 7;
|
||||||
|
b[1] = (b[1] * MIX[channels]) >> 7;
|
||||||
|
// when approximating the serial multiplex as a straight mix, once the
|
||||||
|
// multiplex frequency gets below the nyquist frequency an average mix
|
||||||
|
// begins to sound too quiet. To approximate this effect, I don't attenuate
|
||||||
|
// any further after 6 channels are active.
|
||||||
|
}
|
||||||
|
|
||||||
|
// 8 bit approximation of master volume
|
||||||
|
// max N163 vol vs max APU square
|
||||||
|
// unfortunately, games have been measured as low as 3.4x and as high as 8.5x
|
||||||
|
// with higher volumes on Erika, King of Kings, and Rolling Thunder
|
||||||
|
// and lower volumes on others. Using 6.0x as a rough "one size fits all".
|
||||||
|
const double MASTER_VOL = 6.0 * 1223.0;
|
||||||
|
const double MAX_OUT = 15.0 * 15.0 * 256.0; // max digital value
|
||||||
|
const int GAIN = int((MASTER_VOL / MAX_OUT) * 256.0f);
|
||||||
|
b[0] = (b[0] * GAIN) >> 8;
|
||||||
|
b[1] = (b[1] * GAIN) >> 8;
|
||||||
|
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NES_N106::Write (unsigned int adr, unsigned int val, unsigned int id)
|
||||||
|
{
|
||||||
|
if (adr == 0xE000) // master disable
|
||||||
|
{
|
||||||
|
master_disable = ((val & 0x40) != 0);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (adr == 0xF800) // register select
|
||||||
|
{
|
||||||
|
reg_select = (val & 0x7F);
|
||||||
|
reg_advance = (val & 0x80) != 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (adr == 0x4800) // register write
|
||||||
|
{
|
||||||
|
if (option[OPT_PHASE_READ_ONLY]) // old emulators didn't know phase was stored here
|
||||||
|
{
|
||||||
|
int c = 15 - (reg_select/8);
|
||||||
|
int r = reg_select & 7;
|
||||||
|
if (c < get_channels() &&
|
||||||
|
(r == 1 ||
|
||||||
|
r == 3 ||
|
||||||
|
r == 5))
|
||||||
|
{
|
||||||
|
if (reg_advance)
|
||||||
|
reg_select = (reg_select + 1) & 0x7F;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (option[OPT_LIMIT_WAVELENGTH]) // old emulators ignored top 3 bits of length
|
||||||
|
{
|
||||||
|
int c = 15 - (reg_select/8);
|
||||||
|
int r = reg_select & 7;
|
||||||
|
if (c < get_channels() && r == 4)
|
||||||
|
{
|
||||||
|
val |= 0xE0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reg[reg_select] = val;
|
||||||
|
if (reg_advance)
|
||||||
|
reg_select = (reg_select + 1) & 0x7F;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NES_N106::Read (unsigned int adr, unsigned int & val, unsigned int id)
|
||||||
|
{
|
||||||
|
if (adr == 0x4800) // register read
|
||||||
|
{
|
||||||
|
val = reg[reg_select];
|
||||||
|
if (reg_advance)
|
||||||
|
reg_select = (reg_select + 1) & 0x7F;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// register decoding/encoding functions
|
||||||
|
//
|
||||||
|
|
||||||
|
inline unsigned int NES_N106::get_phase (int channel)
|
||||||
|
{
|
||||||
|
// 24-bit phase stored in channel regs 1/3/5
|
||||||
|
channel = channel << 3;
|
||||||
|
return (reg[0x41 + channel] )
|
||||||
|
+ (reg[0x43 + channel] << 8 )
|
||||||
|
+ (reg[0x45 + channel] << 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline unsigned int NES_N106::get_freq (int channel)
|
||||||
|
{
|
||||||
|
// 19-bit frequency stored in channel regs 0/2/4
|
||||||
|
channel = channel << 3;
|
||||||
|
return ( reg[0x40 + channel] )
|
||||||
|
+ ( reg[0x42 + channel] << 8 )
|
||||||
|
+ ((reg[0x44 + channel] & 0x03) << 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline unsigned int NES_N106::get_off (int channel)
|
||||||
|
{
|
||||||
|
// 8-bit offset stored in channel reg 6
|
||||||
|
channel = channel << 3;
|
||||||
|
return reg[0x46 + channel];
|
||||||
|
}
|
||||||
|
|
||||||
|
inline unsigned int NES_N106::get_len (int channel)
|
||||||
|
{
|
||||||
|
// 6-bit<<3 length stored obscurely in channel reg 4
|
||||||
|
channel = channel << 3;
|
||||||
|
return 256 - (reg[0x44 + channel] & 0xFC);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline int NES_N106::get_vol (int channel)
|
||||||
|
{
|
||||||
|
// 4-bit volume stored in channel reg 7
|
||||||
|
channel = channel << 3;
|
||||||
|
return reg[0x47 + channel] & 0x0F;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline int NES_N106::get_sample (unsigned int index)
|
||||||
|
{
|
||||||
|
// every sample becomes 2 samples in regs
|
||||||
|
return (index&1) ?
|
||||||
|
((reg[index>>1] >> 4) & 0x0F) :
|
||||||
|
( reg[index>>1] & 0x0F) ;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline int NES_N106::get_channels ()
|
||||||
|
{
|
||||||
|
// 3-bit channel count stored in reg 0x7F
|
||||||
|
return ((reg[0x7F] >> 4) & 0x07) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void NES_N106::set_phase (unsigned int phase, int channel)
|
||||||
|
{
|
||||||
|
// 24-bit phase stored in channel regs 1/3/5
|
||||||
|
channel = channel << 3;
|
||||||
|
reg[0x41 + channel] = phase & 0xFF;
|
||||||
|
reg[0x43 + channel] = (phase >> 8 ) & 0xFF;
|
||||||
|
reg[0x45 + channel] = (phase >> 16) & 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
} //namespace
|
64
src/engine/platform/sound/nes_nsfplay/nes_n106.h
Normal file
64
src/engine/platform/sound/nes_nsfplay/nes_n106.h
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
#ifndef _NES_N106_H_
|
||||||
|
#define _NES_N106_H_
|
||||||
|
|
||||||
|
namespace xgm {
|
||||||
|
|
||||||
|
|
||||||
|
class NES_N106
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
OPT_SERIAL = 0,
|
||||||
|
OPT_PHASE_READ_ONLY = 1,
|
||||||
|
OPT_LIMIT_WAVELENGTH = 2,
|
||||||
|
OPT_END
|
||||||
|
};
|
||||||
|
|
||||||
|
protected:
|
||||||
|
double rate, clock;
|
||||||
|
int mask;
|
||||||
|
int sm[2][8]; // stereo mix
|
||||||
|
int fout[8]; // current output
|
||||||
|
int option[OPT_END];
|
||||||
|
|
||||||
|
bool master_disable;
|
||||||
|
unsigned int reg[0x80]; // all state is contained here
|
||||||
|
unsigned int reg_select;
|
||||||
|
bool reg_advance;
|
||||||
|
int tick_channel;
|
||||||
|
int tick_clock;
|
||||||
|
int render_channel;
|
||||||
|
int render_clock;
|
||||||
|
int render_subclock;
|
||||||
|
|
||||||
|
// convenience functions to interact with regs
|
||||||
|
inline unsigned int get_phase (int channel);
|
||||||
|
inline unsigned int get_freq (int channel);
|
||||||
|
inline unsigned int get_off (int channel);
|
||||||
|
inline unsigned int get_len (int channel);
|
||||||
|
inline int get_vol (int channel);
|
||||||
|
inline int get_sample (unsigned int index);
|
||||||
|
inline int get_channels ();
|
||||||
|
// for storing back the phase after modifying
|
||||||
|
inline void set_phase (unsigned int phase, int channel);
|
||||||
|
|
||||||
|
public:
|
||||||
|
NES_N106 ();
|
||||||
|
~NES_N106 ();
|
||||||
|
|
||||||
|
void Reset ();
|
||||||
|
void Tick (unsigned int clocks);
|
||||||
|
unsigned int Render (int b[2]);
|
||||||
|
bool Write (unsigned int adr, unsigned int val, unsigned int id=0);
|
||||||
|
bool Read (unsigned int adr, unsigned int & val, unsigned int id=0);
|
||||||
|
void SetRate (double);
|
||||||
|
void SetClock (double);
|
||||||
|
void SetOption (int, int);
|
||||||
|
void SetMask (int m);
|
||||||
|
void SetStereoMix (int trk, short mixl, short mixr);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace xgm
|
||||||
|
|
||||||
|
#endif
|
239
src/engine/platform/sound/nes_nsfplay/nes_vrc6.cpp
Normal file
239
src/engine/platform/sound/nes_nsfplay/nes_vrc6.cpp
Normal file
|
@ -0,0 +1,239 @@
|
||||||
|
#include "nes_vrc6.h"
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
namespace xgm
|
||||||
|
{
|
||||||
|
|
||||||
|
NES_VRC6::NES_VRC6 ()
|
||||||
|
{
|
||||||
|
SetClock (DEFAULT_CLOCK);
|
||||||
|
SetRate (DEFAULT_RATE);
|
||||||
|
|
||||||
|
halt = false;
|
||||||
|
freq_shift = 0;
|
||||||
|
|
||||||
|
for(int c=0;c<2;++c)
|
||||||
|
for(int t=0;t<3;++t)
|
||||||
|
sm[c][t] = 128;
|
||||||
|
}
|
||||||
|
|
||||||
|
NES_VRC6::~NES_VRC6 ()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void NES_VRC6::SetStereoMix(int trk, short mixl, short mixr)
|
||||||
|
{
|
||||||
|
if (trk < 0) return;
|
||||||
|
if (trk > 2) return;
|
||||||
|
sm[0][trk] = mixl;
|
||||||
|
sm[1][trk] = mixr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NES_VRC6::SetClock (double c)
|
||||||
|
{
|
||||||
|
clock = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NES_VRC6::SetRate (double r)
|
||||||
|
{
|
||||||
|
rate = r ? r : DEFAULT_RATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NES_VRC6::SetOption (int id, int val)
|
||||||
|
{
|
||||||
|
if(id<OPT_END)
|
||||||
|
{
|
||||||
|
//option[id] = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NES_VRC6::Reset ()
|
||||||
|
{
|
||||||
|
Write (0x9003, 0);
|
||||||
|
for (int i = 0; i < 3; i++)
|
||||||
|
{
|
||||||
|
Write (0x9000 + i, 0);
|
||||||
|
Write (0xa000 + i, 0);
|
||||||
|
Write (0xb000 + i, 0);
|
||||||
|
}
|
||||||
|
count14 = 0;
|
||||||
|
mask = 0;
|
||||||
|
counter[0] = 0;
|
||||||
|
counter[1] = 0;
|
||||||
|
counter[2] = 0;
|
||||||
|
phase[0] = 0;
|
||||||
|
phase[0] = 1;
|
||||||
|
phase[0] = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
short NES_VRC6::calc_sqr (int i, unsigned int clocks)
|
||||||
|
{
|
||||||
|
static const short sqrtbl[8][16] = {
|
||||||
|
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
|
||||||
|
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1},
|
||||||
|
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1},
|
||||||
|
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1},
|
||||||
|
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1},
|
||||||
|
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1},
|
||||||
|
{0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1},
|
||||||
|
{0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!enable[i])
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (!halt)
|
||||||
|
{
|
||||||
|
counter[i] += clocks;
|
||||||
|
while(counter[i] > freq2[i])
|
||||||
|
{
|
||||||
|
phase[i] = (phase[i] + 1) & 15;
|
||||||
|
counter[i] -= (freq2[i] + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (gate[i]
|
||||||
|
|| sqrtbl[duty[i]][phase[i]])? volume[i] : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
short NES_VRC6::calc_saw (unsigned int clocks)
|
||||||
|
{
|
||||||
|
if (!enable[2])
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (!halt)
|
||||||
|
{
|
||||||
|
counter[2] += clocks;
|
||||||
|
while(counter[2] > freq2[2])
|
||||||
|
{
|
||||||
|
counter[2] -= (freq2[2] + 1);
|
||||||
|
|
||||||
|
// accumulate saw
|
||||||
|
++count14;
|
||||||
|
if (count14 >= 14)
|
||||||
|
{
|
||||||
|
count14 = 0;
|
||||||
|
phase[2] = 0;
|
||||||
|
}
|
||||||
|
else if (0 == (count14 & 1)) // only accumulate on even ticks
|
||||||
|
{
|
||||||
|
phase[2] = (phase[2] + volume[2]) & 0xFF; // note 8-bit wrapping behaviour
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// only top 5 bits of saw are output
|
||||||
|
return phase[2] >> 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NES_VRC6::Tick (unsigned int clocks)
|
||||||
|
{
|
||||||
|
out[0] = calc_sqr(0,clocks);
|
||||||
|
out[1] = calc_sqr(1,clocks);
|
||||||
|
out[2] = calc_saw(clocks);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int NES_VRC6::Render (int b[2])
|
||||||
|
{
|
||||||
|
int m[3];
|
||||||
|
m[0] = out[0];
|
||||||
|
m[1] = out[1];
|
||||||
|
m[2] = out[2];
|
||||||
|
|
||||||
|
// note: signal is inverted compared to 2A03
|
||||||
|
|
||||||
|
m[0] = (mask & 1) ? 0 : -m[0];
|
||||||
|
m[1] = (mask & 2) ? 0 : -m[1];
|
||||||
|
m[2] = (mask & 4) ? 0 : -m[2];
|
||||||
|
|
||||||
|
b[0] = m[0] * sm[0][0];
|
||||||
|
b[0] += m[1] * sm[0][1];
|
||||||
|
b[0] += m[2] * sm[0][2];
|
||||||
|
//b[0] >>= (7 - 7);
|
||||||
|
|
||||||
|
b[1] = m[0] * sm[1][0];
|
||||||
|
b[1] += m[1] * sm[1][1];
|
||||||
|
b[1] += m[2] * sm[1][2];
|
||||||
|
//b[1] >>= (7 - 7);
|
||||||
|
|
||||||
|
// master volume adjustment
|
||||||
|
const int MASTER = int(256.0 * 1223.0 / 1920.0);
|
||||||
|
b[0] = (b[0] * MASTER) >> 8;
|
||||||
|
b[1] = (b[1] * MASTER) >> 8;
|
||||||
|
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NES_VRC6::Write (unsigned int adr, unsigned int val, unsigned int id)
|
||||||
|
{
|
||||||
|
int ch, cmap[4] = { 0, 0, 1, 2 };
|
||||||
|
|
||||||
|
switch (adr)
|
||||||
|
{
|
||||||
|
case 0x9000:
|
||||||
|
case 0xa000:
|
||||||
|
ch = cmap[(adr >> 12) & 3];
|
||||||
|
volume[ch] = val & 15;
|
||||||
|
duty[ch] = (val >> 4) & 7;
|
||||||
|
gate[ch] = (val >> 7) & 1;
|
||||||
|
break;
|
||||||
|
case 0xb000:
|
||||||
|
volume[2] = val & 63;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x9001:
|
||||||
|
case 0xa001:
|
||||||
|
case 0xb001:
|
||||||
|
ch = cmap[(adr >> 12) & 3];
|
||||||
|
freq[ch] = (freq[ch] & 0xf00) | val;
|
||||||
|
freq2[ch] = (freq[ch] >> freq_shift);
|
||||||
|
if (counter[ch] > freq2[ch]) counter[ch] = freq2[ch];
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x9002:
|
||||||
|
case 0xa002:
|
||||||
|
case 0xb002:
|
||||||
|
ch = cmap[(adr >> 12) & 3];
|
||||||
|
freq[ch] = ((val & 0xf) << 8) + (freq[ch] & 0xff);
|
||||||
|
freq2[ch] = (freq[ch] >> freq_shift);
|
||||||
|
if (counter[ch] > freq2[ch]) counter[ch] = freq2[ch];
|
||||||
|
if (!enable[ch]) // if enable is being turned on, phase should be reset
|
||||||
|
{
|
||||||
|
if (ch == 2)
|
||||||
|
{
|
||||||
|
count14 = 0; // reset saw
|
||||||
|
}
|
||||||
|
phase[ch] = 0;
|
||||||
|
}
|
||||||
|
enable[ch] = (val >> 7) & 1;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x9003:
|
||||||
|
halt = val & 1;
|
||||||
|
freq_shift =
|
||||||
|
(val & 4) ? 8 :
|
||||||
|
(val & 2) ? 4 :
|
||||||
|
0;
|
||||||
|
freq2[0] = (freq[0] >> freq_shift);
|
||||||
|
freq2[1] = (freq[1] >> freq_shift);
|
||||||
|
freq2[2] = (freq[2] >> freq_shift);
|
||||||
|
if (counter[0] > freq2[0]) counter[0] = freq2[0];
|
||||||
|
if (counter[1] > freq2[1]) counter[1] = freq2[1];
|
||||||
|
if (counter[2] > freq2[2]) counter[2] = freq2[2];
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NES_VRC6::Read (unsigned int adr, unsigned int & val, unsigned int id)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} // namespace
|
53
src/engine/platform/sound/nes_nsfplay/nes_vrc6.h
Normal file
53
src/engine/platform/sound/nes_nsfplay/nes_vrc6.h
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
#ifndef _NES_VRC6_H_
|
||||||
|
#define _NES_VRC6_H_
|
||||||
|
|
||||||
|
namespace xgm
|
||||||
|
{
|
||||||
|
|
||||||
|
class NES_VRC6
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
OPT_END
|
||||||
|
};
|
||||||
|
protected:
|
||||||
|
unsigned int counter[3]; // frequency divider
|
||||||
|
unsigned int phase[3]; // phase counter
|
||||||
|
unsigned int freq2[3]; // adjusted frequency
|
||||||
|
int count14; // saw 14-stage counter
|
||||||
|
|
||||||
|
//int option[OPT_END];
|
||||||
|
int mask;
|
||||||
|
int sm[2][3]; // stereo mix
|
||||||
|
int duty[2];
|
||||||
|
int volume[3];
|
||||||
|
int enable[3];
|
||||||
|
int gate[3];
|
||||||
|
unsigned int freq[3];
|
||||||
|
short calc_sqr (int i, unsigned int clocks);
|
||||||
|
short calc_saw (unsigned int clocks);
|
||||||
|
bool halt;
|
||||||
|
int freq_shift;
|
||||||
|
double clock, rate;
|
||||||
|
int out[3];
|
||||||
|
|
||||||
|
public:
|
||||||
|
NES_VRC6 ();
|
||||||
|
~NES_VRC6 ();
|
||||||
|
|
||||||
|
void Reset ();
|
||||||
|
void Tick (unsigned int clocks);
|
||||||
|
unsigned int Render (int b[2]);
|
||||||
|
bool Read (unsigned int adr, unsigned int & val, unsigned int id=0);
|
||||||
|
bool Write (unsigned int adr, unsigned int val, unsigned int id=0);
|
||||||
|
void SetClock (double);
|
||||||
|
void SetRate (double);
|
||||||
|
void SetOption (int, int);
|
||||||
|
void SetMask (int m){ mask = m; }
|
||||||
|
void SetStereoMix (int trk, short mixl, short mixr);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
#endif
|
60
src/engine/platform/sound/nes_nsfplay/readme.txt
Normal file
60
src/engine/platform/sound/nes_nsfplay/readme.txt
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
MODIFIED
|
||||||
|
|
||||||
|
this is a modified version of the NES audio emulation core.
|
||||||
|
it converts the files to UTF-8 and Unix line endings.
|
||||||
|
|
||||||
|
XGM SOURCE ARCHIVE
|
||||||
|
|
||||||
|
This source archive is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY. You can reuse these source code freely. However,
|
||||||
|
we possibly change the structure and interface of the program code without
|
||||||
|
any advance notice.
|
||||||
|
|
||||||
|
HOW TO COMPILE
|
||||||
|
|
||||||
|
Open the workspace file top.sln with Visual C++ 7.0 or later
|
||||||
|
version. We can compile both KbMediaPlayer and Winamp version of
|
||||||
|
NSFplug on this workspace.
|
||||||
|
|
||||||
|
To make a KbMediaPlayer version of NSFplug, that is, in_nsf.kpi,
|
||||||
|
Please choose 'kbnsf' project as an active project. Then, you can
|
||||||
|
build in_nsf.kpi by build menu.
|
||||||
|
|
||||||
|
On the other hand, to make a Winamp version of NSFplug, activate
|
||||||
|
'wa2nsf' project and build it.
|
||||||
|
|
||||||
|
Note that after the build process, VC++ copies the plugin files to:
|
||||||
|
|
||||||
|
C:\Program Files\KbMediaPlayer\Plugins\OK\in_nsf\in_nsf.kpi
|
||||||
|
C:\Program Files\Windamp\Plugins\in_nsf.dll
|
||||||
|
|
||||||
|
If you don't need to have these copies, please remove or modify the
|
||||||
|
custom build settings.
|
||||||
|
|
||||||
|
ACKNOWLEDGEMENT
|
||||||
|
|
||||||
|
I thank Mamiya and Kobarin and Nullsoft for their great source code.
|
||||||
|
I thank Norix and Izumi for the fruitful discussions and the NSFplug
|
||||||
|
users for their comments and bug reports.
|
||||||
|
|
||||||
|
COPYRIGHTS
|
||||||
|
|
||||||
|
NSFplug is built on KM6502, KbMediaPlayer plugin SDK and Winamp2
|
||||||
|
plugin SDK.
|
||||||
|
|
||||||
|
NSFplug uses KM6502 in emulating a 6502 cpu. KM6502 code is stored in
|
||||||
|
devices\CPU\km6502 folder of this source archive. KM6502 is a public
|
||||||
|
domain software. See the document of KM6502 stored in the folder.
|
||||||
|
|
||||||
|
KbMediaPlayer Plugin SDK is provided by Kobarin. The SDK code is also
|
||||||
|
packed in the kbmedia\sdk folder of this archive. The copyright of
|
||||||
|
the source code remains with Kobarin.
|
||||||
|
|
||||||
|
The files in winamp/sdk folder of this archive are the header files
|
||||||
|
from Winamp2 Plugin SDK provided by Nullsoft. The copyright of these
|
||||||
|
header files remains with Nullsoft.
|
||||||
|
|
||||||
|
CONTACT
|
||||||
|
|
||||||
|
Digital Sound Antiques
|
||||||
|
http://dsa.sakura.ne.jp/
|
|
@ -498,7 +498,7 @@ uint32_t opz_registers::compute_phase_step(uint32_t choffs, uint32_t opoffs, opd
|
||||||
// additional 12 bits of resolution; this calculation gives us, for
|
// additional 12 bits of resolution; this calculation gives us, for
|
||||||
// example, a frequency of 8.0009Hz when 8Hz is requested
|
// example, a frequency of 8.0009Hz when 8Hz is requested
|
||||||
uint32_t substep = m_phase_substep[opoffs];
|
uint32_t substep = m_phase_substep[opoffs];
|
||||||
substep += 75 * freq;
|
substep += 75 * 1024 * freq;
|
||||||
phase_step = substep >> 12;
|
phase_step = substep >> 12;
|
||||||
m_phase_substep[opoffs] = substep & 0xfff;
|
m_phase_substep[opoffs] = substep & 0xfff;
|
||||||
|
|
||||||
|
|
|
@ -320,6 +320,9 @@ public:
|
||||||
// generate one sample of sound
|
// generate one sample of sound
|
||||||
void generate(output_data *output, uint32_t numsamples = 1);
|
void generate(output_data *output, uint32_t numsamples = 1);
|
||||||
|
|
||||||
|
// get the engine
|
||||||
|
fm_engine* debug_engine() { return &m_fm; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// internal state
|
// internal state
|
||||||
uint8_t m_address; // address register
|
uint8_t m_address; // address register
|
||||||
|
|
|
@ -147,6 +147,8 @@ const char* DivPlatformTX81Z::getEffectName(unsigned char effect) {
|
||||||
void DivPlatformTX81Z::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
void DivPlatformTX81Z::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||||
static int os[2];
|
static int os[2];
|
||||||
|
|
||||||
|
ymfm::ym2414::fm_engine* fme=fm_ymfm->debug_engine();
|
||||||
|
|
||||||
for (size_t h=start; h<start+len; h++) {
|
for (size_t h=start; h<start+len; h++) {
|
||||||
os[0]=0; os[1]=0;
|
os[0]=0; os[1]=0;
|
||||||
if (!writes.empty()) {
|
if (!writes.empty()) {
|
||||||
|
@ -162,6 +164,10 @@ void DivPlatformTX81Z::acquire(short* bufL, short* bufR, size_t start, size_t le
|
||||||
|
|
||||||
fm_ymfm->generate(&out_ymfm);
|
fm_ymfm->generate(&out_ymfm);
|
||||||
|
|
||||||
|
for (int i=0; i<8; i++) {
|
||||||
|
oscBuf[i]->data[oscBuf[i]->needle++]=(fme->debug_channel(i)->debug_output(0)+fme->debug_channel(i)->debug_output(1));
|
||||||
|
}
|
||||||
|
|
||||||
os[0]=out_ymfm.data[0];
|
os[0]=out_ymfm.data[0];
|
||||||
if (os[0]<-32768) os[0]=-32768;
|
if (os[0]<-32768) os[0]=-32768;
|
||||||
if (os[0]>32767) os[0]=32767;
|
if (os[0]>32767) os[0]=32767;
|
||||||
|
@ -192,6 +198,9 @@ void DivPlatformTX81Z::tick(bool sysTick) {
|
||||||
for (int j=0; j<4; j++) {
|
for (int j=0; j<4; j++) {
|
||||||
unsigned short baseAddr=chanOffs[i]|opOffs[j];
|
unsigned short baseAddr=chanOffs[i]|opOffs[j];
|
||||||
DivInstrumentFM::Operator& op=chan[i].state.op[j];
|
DivInstrumentFM::Operator& op=chan[i].state.op[j];
|
||||||
|
if (isMuted[i]) {
|
||||||
|
rWrite(baseAddr+ADDR_TL,127);
|
||||||
|
} else {
|
||||||
if (isOutput[chan[i].state.alg][j]) {
|
if (isOutput[chan[i].state.alg][j]) {
|
||||||
rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127));
|
rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127));
|
||||||
} else {
|
} else {
|
||||||
|
@ -199,6 +208,7 @@ void DivPlatformTX81Z::tick(bool sysTick) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (chan[i].std.arp.had) {
|
if (chan[i].std.arp.had) {
|
||||||
if (!chan[i].inPorta) {
|
if (!chan[i].inPorta) {
|
||||||
|
@ -260,11 +270,7 @@ void DivPlatformTX81Z::tick(bool sysTick) {
|
||||||
|
|
||||||
if (chan[i].std.alg.had) {
|
if (chan[i].std.alg.had) {
|
||||||
chan[i].state.alg=chan[i].std.alg.val;
|
chan[i].state.alg=chan[i].std.alg.val;
|
||||||
if (isMuted[i]) {
|
|
||||||
immWrite(chanOffs[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)|0x40);
|
|
||||||
} else {
|
|
||||||
immWrite(chanOffs[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)|(chan[i].active?0:0x40)|(chan[i].chVolR<<7));
|
immWrite(chanOffs[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)|(chan[i].active?0:0x40)|(chan[i].chVolR<<7));
|
||||||
}
|
|
||||||
if (!parent->song.algMacroBehavior) for (int j=0; j<4; j++) {
|
if (!parent->song.algMacroBehavior) for (int j=0; j<4; j++) {
|
||||||
unsigned short baseAddr=chanOffs[i]|opOffs[j];
|
unsigned short baseAddr=chanOffs[i]|opOffs[j];
|
||||||
DivInstrumentFM::Operator& op=chan[i].state.op[j];
|
DivInstrumentFM::Operator& op=chan[i].state.op[j];
|
||||||
|
@ -281,12 +287,8 @@ void DivPlatformTX81Z::tick(bool sysTick) {
|
||||||
}
|
}
|
||||||
if (chan[i].std.fb.had) {
|
if (chan[i].std.fb.had) {
|
||||||
chan[i].state.fb=chan[i].std.fb.val;
|
chan[i].state.fb=chan[i].std.fb.val;
|
||||||
if (isMuted[i]) {
|
|
||||||
immWrite(chanOffs[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)|0x40);
|
|
||||||
} else {
|
|
||||||
immWrite(chanOffs[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)|(chan[i].active?0:0x40)|(chan[i].chVolR<<7));
|
immWrite(chanOffs[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)|(chan[i].active?0:0x40)|(chan[i].chVolR<<7));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (chan[i].std.fms.had) {
|
if (chan[i].std.fms.had) {
|
||||||
chan[i].state.fms=chan[i].std.fms.val;
|
chan[i].state.fms=chan[i].std.fms.val;
|
||||||
rWrite(chanOffs[i]+ADDR_FMS_AMS,((chan[i].state.fms&7)<<4)|(chan[i].state.ams&3));
|
rWrite(chanOffs[i]+ADDR_FMS_AMS,((chan[i].state.fms&7)<<4)|(chan[i].state.ams&3));
|
||||||
|
@ -313,7 +315,7 @@ void DivPlatformTX81Z::tick(bool sysTick) {
|
||||||
}
|
}
|
||||||
if (m.mult.had) {
|
if (m.mult.had) {
|
||||||
op.mult=m.mult.val;
|
op.mult=m.mult.val;
|
||||||
rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4));
|
rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|((op.egt?(op.dt&7):dtTable[op.dt&7])<<4));
|
||||||
}
|
}
|
||||||
if (m.rr.had) {
|
if (m.rr.had) {
|
||||||
op.rr=m.rr.val;
|
op.rr=m.rr.val;
|
||||||
|
@ -325,19 +327,23 @@ void DivPlatformTX81Z::tick(bool sysTick) {
|
||||||
}
|
}
|
||||||
if (m.tl.had) {
|
if (m.tl.had) {
|
||||||
op.tl=127-m.tl.val;
|
op.tl=127-m.tl.val;
|
||||||
|
if (isMuted[i]) {
|
||||||
|
rWrite(baseAddr+ADDR_TL,127);
|
||||||
|
} else {
|
||||||
if (isOutput[chan[i].state.alg][j]) {
|
if (isOutput[chan[i].state.alg][j]) {
|
||||||
rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127));
|
rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127));
|
||||||
} else {
|
} else {
|
||||||
rWrite(baseAddr+ADDR_TL,op.tl);
|
rWrite(baseAddr+ADDR_TL,op.tl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (m.rs.had) {
|
if (m.rs.had) {
|
||||||
op.rs=m.rs.val;
|
op.rs=m.rs.val;
|
||||||
rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.egt<<5)|(op.rs<<6));
|
rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.egt<<5)|(op.rs<<6));
|
||||||
}
|
}
|
||||||
if (m.dt.had) {
|
if (m.dt.had) {
|
||||||
op.dt=m.dt.val;
|
op.dt=m.dt.val;
|
||||||
rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4));
|
rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|((op.egt?(op.dt&7):dtTable[op.dt&7])<<4));
|
||||||
}
|
}
|
||||||
if (m.d2r.had) {
|
if (m.d2r.had) {
|
||||||
op.d2r=m.d2r.val;
|
op.d2r=m.d2r.val;
|
||||||
|
@ -358,12 +364,8 @@ void DivPlatformTX81Z::tick(bool sysTick) {
|
||||||
oldWrites[baseAddr+ADDR_TL]=-1;
|
oldWrites[baseAddr+ADDR_TL]=-1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isMuted[i]) {
|
|
||||||
immWrite(chanOffs[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)|0x00);
|
|
||||||
} else {
|
|
||||||
//if (chan[i].keyOn) immWrite(0x08,i);
|
//if (chan[i].keyOn) immWrite(0x08,i);
|
||||||
immWrite(chanOffs[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)|0x00|(chan[i].chVolR<<7));
|
immWrite(chanOffs[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)|0x00|(chan[i].chVolR<<7));
|
||||||
}
|
|
||||||
if (chan[i].hardReset && chan[i].keyOn) {
|
if (chan[i].hardReset && chan[i].keyOn) {
|
||||||
for (int j=0; j<4; j++) {
|
for (int j=0; j<4; j++) {
|
||||||
unsigned short baseAddr=chanOffs[i]|opOffs[j];
|
unsigned short baseAddr=chanOffs[i]|opOffs[j];
|
||||||
|
@ -405,12 +407,8 @@ void DivPlatformTX81Z::tick(bool sysTick) {
|
||||||
chan[i].freqChanged=false;
|
chan[i].freqChanged=false;
|
||||||
}
|
}
|
||||||
if (chan[i].keyOn) {
|
if (chan[i].keyOn) {
|
||||||
if (isMuted[i]) {
|
|
||||||
immWrite(chanOffs[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3));
|
|
||||||
} else {
|
|
||||||
//immWrite(0x08,i);
|
//immWrite(0x08,i);
|
||||||
immWrite(chanOffs[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)|0x40|(chan[i].chVolR<<7));
|
immWrite(chanOffs[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)|0x40|(chan[i].chVolR<<7));
|
||||||
}
|
|
||||||
chan[i].keyOn=false;
|
chan[i].keyOn=false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -418,13 +416,19 @@ void DivPlatformTX81Z::tick(bool sysTick) {
|
||||||
|
|
||||||
void DivPlatformTX81Z::muteChannel(int ch, bool mute) {
|
void DivPlatformTX81Z::muteChannel(int ch, bool mute) {
|
||||||
isMuted[ch]=mute;
|
isMuted[ch]=mute;
|
||||||
// TODO: use volume registers!
|
for (int i=0; i<4; i++) {
|
||||||
/*
|
unsigned short baseAddr=chanOffs[ch]|opOffs[i];
|
||||||
|
DivInstrumentFM::Operator op=chan[ch].state.op[i];
|
||||||
if (isMuted[ch]) {
|
if (isMuted[ch]) {
|
||||||
immWrite(chanOffs[ch]+ADDR_LR_FB_ALG,(chan[ch].state.alg&7)|(chan[ch].state.fb<<3));
|
rWrite(baseAddr+ADDR_TL,127);
|
||||||
} else {
|
} else {
|
||||||
immWrite(chanOffs[ch]+ADDR_LR_FB_ALG,(chan[ch].state.alg&7)|(chan[ch].state.fb<<3)|((chan[ch].chVolL&1)<<6)|((chan[ch].chVolR&1)<<7));
|
if (isOutput[chan[ch].state.alg][i]) {
|
||||||
}*/
|
rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[ch].outVol&0x7f))/127));
|
||||||
|
} else {
|
||||||
|
rWrite(baseAddr+ADDR_TL,op.tl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int DivPlatformTX81Z::dispatch(DivCommand c) {
|
int DivPlatformTX81Z::dispatch(DivCommand c) {
|
||||||
|
@ -444,6 +448,9 @@ int DivPlatformTX81Z::dispatch(DivCommand c) {
|
||||||
for (int i=0; i<4; i++) {
|
for (int i=0; i<4; i++) {
|
||||||
unsigned short baseAddr=chanOffs[c.chan]|opOffs[i];
|
unsigned short baseAddr=chanOffs[c.chan]|opOffs[i];
|
||||||
DivInstrumentFM::Operator op=chan[c.chan].state.op[i];
|
DivInstrumentFM::Operator op=chan[c.chan].state.op[i];
|
||||||
|
if (isMuted[c.chan]) {
|
||||||
|
rWrite(baseAddr+ADDR_TL,127);
|
||||||
|
} else {
|
||||||
if (isOutput[chan[c.chan].state.alg][i]) {
|
if (isOutput[chan[c.chan].state.alg][i]) {
|
||||||
if (!chan[c.chan].active || chan[c.chan].insChanged) {
|
if (!chan[c.chan].active || chan[c.chan].insChanged) {
|
||||||
rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127));
|
rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127));
|
||||||
|
@ -453,8 +460,9 @@ int DivPlatformTX81Z::dispatch(DivCommand c) {
|
||||||
rWrite(baseAddr+ADDR_TL,op.tl);
|
rWrite(baseAddr+ADDR_TL,op.tl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (chan[c.chan].insChanged) {
|
if (chan[c.chan].insChanged) {
|
||||||
rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4));
|
rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|((op.egt?(op.dt&7):dtTable[op.dt&7])<<4));
|
||||||
rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.egt<<5)|(op.rs<<6));
|
rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.egt<<5)|(op.rs<<6));
|
||||||
rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7));
|
rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7));
|
||||||
rWrite(baseAddr+ADDR_DT2_D2R,(op.d2r&31)|(op.dt2<<6));
|
rWrite(baseAddr+ADDR_DT2_D2R,(op.d2r&31)|(op.dt2<<6));
|
||||||
|
@ -506,12 +514,16 @@ int DivPlatformTX81Z::dispatch(DivCommand c) {
|
||||||
for (int i=0; i<4; i++) {
|
for (int i=0; i<4; i++) {
|
||||||
unsigned short baseAddr=chanOffs[c.chan]|opOffs[i];
|
unsigned short baseAddr=chanOffs[c.chan]|opOffs[i];
|
||||||
DivInstrumentFM::Operator& op=chan[c.chan].state.op[i];
|
DivInstrumentFM::Operator& op=chan[c.chan].state.op[i];
|
||||||
|
if (isMuted[c.chan]) {
|
||||||
|
rWrite(baseAddr+ADDR_TL,127);
|
||||||
|
} else {
|
||||||
if (isOutput[chan[c.chan].state.alg][i]) {
|
if (isOutput[chan[c.chan].state.alg][i]) {
|
||||||
rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127));
|
rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127));
|
||||||
} else {
|
} else {
|
||||||
rWrite(baseAddr+ADDR_TL,op.tl);
|
rWrite(baseAddr+ADDR_TL,op.tl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case DIV_CMD_GET_VOLUME: {
|
case DIV_CMD_GET_VOLUME: {
|
||||||
|
@ -593,18 +605,22 @@ int DivPlatformTX81Z::dispatch(DivCommand c) {
|
||||||
unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]];
|
unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]];
|
||||||
DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]];
|
DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]];
|
||||||
op.mult=c.value2&15;
|
op.mult=c.value2&15;
|
||||||
rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4));
|
rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|((op.egt?(op.dt&7):dtTable[op.dt&7])<<4));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case DIV_CMD_FM_TL: {
|
case DIV_CMD_FM_TL: {
|
||||||
unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]];
|
unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]];
|
||||||
DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]];
|
DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]];
|
||||||
op.tl=c.value2;
|
op.tl=c.value2;
|
||||||
|
if (isMuted[c.chan]) {
|
||||||
|
rWrite(baseAddr+ADDR_TL,127);
|
||||||
|
} else {
|
||||||
if (isOutput[chan[c.chan].state.alg][c.value]) {
|
if (isOutput[chan[c.chan].state.alg][c.value]) {
|
||||||
rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127));
|
rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127));
|
||||||
} else {
|
} else {
|
||||||
rWrite(baseAddr+ADDR_TL,op.tl);
|
rWrite(baseAddr+ADDR_TL,op.tl);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case DIV_CMD_FM_AR: {
|
case DIV_CMD_FM_AR: {
|
||||||
|
@ -672,12 +688,16 @@ void DivPlatformTX81Z::forceIns() {
|
||||||
for (int j=0; j<4; j++) {
|
for (int j=0; j<4; j++) {
|
||||||
unsigned short baseAddr=chanOffs[i]|opOffs[j];
|
unsigned short baseAddr=chanOffs[i]|opOffs[j];
|
||||||
DivInstrumentFM::Operator op=chan[i].state.op[j];
|
DivInstrumentFM::Operator op=chan[i].state.op[j];
|
||||||
|
if (isMuted[i]) {
|
||||||
|
rWrite(baseAddr+ADDR_TL,127);
|
||||||
|
} else {
|
||||||
if (isOutput[chan[i].state.alg][j]) {
|
if (isOutput[chan[i].state.alg][j]) {
|
||||||
rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127));
|
rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127));
|
||||||
} else {
|
} else {
|
||||||
rWrite(baseAddr+ADDR_TL,op.tl);
|
rWrite(baseAddr+ADDR_TL,op.tl);
|
||||||
}
|
}
|
||||||
rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4));
|
}
|
||||||
|
rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|((op.egt?(op.dt&7):dtTable[op.dt&7])<<4));
|
||||||
rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.egt<<5)|(op.rs<<6));
|
rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.egt<<5)|(op.rs<<6));
|
||||||
rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7));
|
rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7));
|
||||||
rWrite(baseAddr+ADDR_DT2_D2R,(op.d2r&31)|(op.dt2<<6));
|
rWrite(baseAddr+ADDR_DT2_D2R,(op.d2r&31)|(op.dt2<<6));
|
||||||
|
@ -714,6 +734,10 @@ void* DivPlatformTX81Z::getChanState(int ch) {
|
||||||
return &chan[ch];
|
return &chan[ch];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DivDispatchOscBuffer* DivPlatformTX81Z::getOscBuffer(int ch) {
|
||||||
|
return oscBuf[ch];
|
||||||
|
}
|
||||||
|
|
||||||
unsigned char* DivPlatformTX81Z::getRegisterPool() {
|
unsigned char* DivPlatformTX81Z::getRegisterPool() {
|
||||||
return regPool;
|
return regPool;
|
||||||
}
|
}
|
||||||
|
@ -777,6 +801,9 @@ void DivPlatformTX81Z::setFlags(unsigned int flags) {
|
||||||
baseFreqOff=0;
|
baseFreqOff=0;
|
||||||
}
|
}
|
||||||
rate=chipClock/64;
|
rate=chipClock/64;
|
||||||
|
for (int i=0; i<8; i++) {
|
||||||
|
oscBuf[i]->rate=rate;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DivPlatformTX81Z::isStereo() {
|
bool DivPlatformTX81Z::isStereo() {
|
||||||
|
@ -789,6 +816,7 @@ int DivPlatformTX81Z::init(DivEngine* p, int channels, int sugRate, unsigned int
|
||||||
skipRegisterWrites=false;
|
skipRegisterWrites=false;
|
||||||
for (int i=0; i<8; i++) {
|
for (int i=0; i<8; i++) {
|
||||||
isMuted[i]=false;
|
isMuted[i]=false;
|
||||||
|
oscBuf[i]=new DivDispatchOscBuffer;
|
||||||
}
|
}
|
||||||
setFlags(flags);
|
setFlags(flags);
|
||||||
fm_ymfm=new ymfm::ym2414(iface);
|
fm_ymfm=new ymfm::ym2414(iface);
|
||||||
|
@ -798,6 +826,9 @@ int DivPlatformTX81Z::init(DivEngine* p, int channels, int sugRate, unsigned int
|
||||||
}
|
}
|
||||||
|
|
||||||
void DivPlatformTX81Z::quit() {
|
void DivPlatformTX81Z::quit() {
|
||||||
|
for (int i=0; i<8; i++) {
|
||||||
|
delete oscBuf[i];
|
||||||
|
}
|
||||||
delete fm_ymfm;
|
delete fm_ymfm;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -68,6 +68,7 @@ class DivPlatformTX81Z: public DivDispatch {
|
||||||
chVolR(127) {}
|
chVolR(127) {}
|
||||||
};
|
};
|
||||||
Channel chan[8];
|
Channel chan[8];
|
||||||
|
DivDispatchOscBuffer* oscBuf[8];
|
||||||
struct QueuedWrite {
|
struct QueuedWrite {
|
||||||
unsigned short addr;
|
unsigned short addr;
|
||||||
unsigned char val;
|
unsigned char val;
|
||||||
|
@ -102,6 +103,7 @@ class DivPlatformTX81Z: public DivDispatch {
|
||||||
void acquire(short* bufL, short* bufR, size_t start, size_t len);
|
void acquire(short* bufL, short* bufR, size_t start, size_t len);
|
||||||
int dispatch(DivCommand c);
|
int dispatch(DivCommand c);
|
||||||
void* getChanState(int chan);
|
void* getChanState(int chan);
|
||||||
|
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||||
unsigned char* getRegisterPool();
|
unsigned char* getRegisterPool();
|
||||||
int getRegisterPoolSize();
|
int getRegisterPoolSize();
|
||||||
void reset();
|
void reset();
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
|
|
||||||
#include "x1_010.h"
|
#include "x1_010.h"
|
||||||
#include "../engine.h"
|
#include "../engine.h"
|
||||||
|
#include "../../ta-log.h"
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
|
||||||
//#define rWrite(a,v) pendingWrites[a]=v;
|
//#define rWrite(a,v) pendingWrites[a]=v;
|
||||||
|
@ -909,6 +910,48 @@ void DivPlatformX1_010::poke(std::vector<DivRegWrite>& wlist) {
|
||||||
for (DivRegWrite& i: wlist) rWrite(i.addr,i.val);
|
for (DivRegWrite& i: wlist) rWrite(i.addr,i.val);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const void* DivPlatformX1_010::getSampleMem(int index) {
|
||||||
|
return index == 0 ? sampleMem : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t DivPlatformX1_010::getSampleMemCapacity(int index) {
|
||||||
|
return index == 0 ? 1048576 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t DivPlatformX1_010::getSampleMemUsage(int index) {
|
||||||
|
return index == 0 ? sampleMemLen : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DivPlatformX1_010::renderSamples() {
|
||||||
|
memset(sampleMem,0,getSampleMemCapacity());
|
||||||
|
|
||||||
|
size_t memPos=0;
|
||||||
|
for (int i=0; i<parent->song.sampleLen; i++) {
|
||||||
|
DivSample* s=parent->song.sample[i];
|
||||||
|
int paddedLen=(s->length8+4095)&(~0xfff);
|
||||||
|
// fit sample bank size to 128KB for Seta 2 external bankswitching logic (not emulated yet!)
|
||||||
|
if (paddedLen>131072) {
|
||||||
|
paddedLen=131072;
|
||||||
|
}
|
||||||
|
if ((memPos&0xfe0000)!=((memPos+paddedLen)&0xfe0000)) {
|
||||||
|
memPos=(memPos+0x1ffff)&0xfe0000;
|
||||||
|
}
|
||||||
|
if (memPos>=getSampleMemCapacity()) {
|
||||||
|
logW("out of X1-010 memory for sample %d!",i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (memPos+paddedLen>=getSampleMemCapacity()) {
|
||||||
|
memcpy(sampleMem+memPos,s->data8,getSampleMemCapacity()-memPos);
|
||||||
|
logW("out of X1-010 memory for sample %d!",i);
|
||||||
|
} else {
|
||||||
|
memcpy(sampleMem+memPos,s->data8,paddedLen);
|
||||||
|
}
|
||||||
|
s->offX1_010=memPos;
|
||||||
|
memPos+=paddedLen;
|
||||||
|
}
|
||||||
|
sampleMemLen=memPos+256;
|
||||||
|
}
|
||||||
|
|
||||||
int DivPlatformX1_010::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
|
int DivPlatformX1_010::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
|
||||||
parent=p;
|
parent=p;
|
||||||
dumpWrites=false;
|
dumpWrites=false;
|
||||||
|
@ -919,7 +962,9 @@ int DivPlatformX1_010::init(DivEngine* p, int channels, int sugRate, unsigned in
|
||||||
oscBuf[i]=new DivDispatchOscBuffer;
|
oscBuf[i]=new DivDispatchOscBuffer;
|
||||||
}
|
}
|
||||||
setFlags(flags);
|
setFlags(flags);
|
||||||
intf.parent=parent;
|
sampleMem=new unsigned char[getSampleMemCapacity()];
|
||||||
|
sampleMemLen=0;
|
||||||
|
intf.memory=sampleMem;
|
||||||
x1_010=new x1_010_core(intf);
|
x1_010=new x1_010_core(intf);
|
||||||
x1_010->reset();
|
x1_010->reset();
|
||||||
reset();
|
reset();
|
||||||
|
@ -931,6 +976,7 @@ void DivPlatformX1_010::quit() {
|
||||||
delete oscBuf[i];
|
delete oscBuf[i];
|
||||||
}
|
}
|
||||||
delete x1_010;
|
delete x1_010;
|
||||||
|
delete[] sampleMem;
|
||||||
}
|
}
|
||||||
|
|
||||||
DivPlatformX1_010::~DivPlatformX1_010() {
|
DivPlatformX1_010::~DivPlatformX1_010() {
|
||||||
|
|
|
@ -28,13 +28,13 @@
|
||||||
|
|
||||||
class DivX1_010Interface: public x1_010_mem_intf {
|
class DivX1_010Interface: public x1_010_mem_intf {
|
||||||
public:
|
public:
|
||||||
DivEngine* parent;
|
unsigned char* memory;
|
||||||
int sampleBank;
|
int sampleBank;
|
||||||
virtual u8 read_byte(u32 address) override {
|
virtual u8 read_byte(u32 address) override {
|
||||||
if (parent->x1_010Mem==NULL) return 0;
|
if (memory==NULL) return 0;
|
||||||
return parent->x1_010Mem[address & 0xfffff];
|
return memory[address & 0xfffff];
|
||||||
}
|
}
|
||||||
DivX1_010Interface(): parent(NULL), sampleBank(0) {}
|
DivX1_010Interface(): memory(NULL), sampleBank(0) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
class DivPlatformX1_010: public DivDispatch {
|
class DivPlatformX1_010: public DivDispatch {
|
||||||
|
@ -115,6 +115,8 @@ class DivPlatformX1_010: public DivDispatch {
|
||||||
DivDispatchOscBuffer* oscBuf[16];
|
DivDispatchOscBuffer* oscBuf[16];
|
||||||
bool isMuted[16];
|
bool isMuted[16];
|
||||||
bool stereo=false;
|
bool stereo=false;
|
||||||
|
unsigned char* sampleMem;
|
||||||
|
size_t sampleMemLen;
|
||||||
unsigned char sampleBank;
|
unsigned char sampleBank;
|
||||||
DivX1_010Interface intf;
|
DivX1_010Interface intf;
|
||||||
x1_010_core* x1_010;
|
x1_010_core* x1_010;
|
||||||
|
@ -141,6 +143,10 @@ class DivPlatformX1_010: public DivDispatch {
|
||||||
void notifyInsDeletion(void* ins);
|
void notifyInsDeletion(void* ins);
|
||||||
void poke(unsigned int addr, unsigned short val);
|
void poke(unsigned int addr, unsigned short val);
|
||||||
void poke(std::vector<DivRegWrite>& wlist);
|
void poke(std::vector<DivRegWrite>& wlist);
|
||||||
|
const void* getSampleMem(int index = 0);
|
||||||
|
size_t getSampleMemCapacity(int index = 0);
|
||||||
|
size_t getSampleMemUsage(int index = 0);
|
||||||
|
void renderSamples();
|
||||||
const char** getRegisterSheet();
|
const char** getRegisterSheet();
|
||||||
const char* getEffectName(unsigned char effect);
|
const char* getEffectName(unsigned char effect);
|
||||||
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
#include "ym2610.h"
|
#include "ym2610.h"
|
||||||
#include "sound/ymfm/ymfm.h"
|
#include "sound/ymfm/ymfm.h"
|
||||||
#include "../engine.h"
|
#include "../engine.h"
|
||||||
|
#include "../../ta-log.h"
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
|
||||||
|
@ -245,6 +246,85 @@ const char* regCheatSheetYM2610[]={
|
||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const void* DivPlatformYM2610Base::getSampleMem(int index) {
|
||||||
|
return index == 0 ? adpcmAMem : index == 1 ? adpcmBMem : NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t DivPlatformYM2610Base::getSampleMemCapacity(int index) {
|
||||||
|
return index == 0 ? 16777216 : index == 1 ? 16777216 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t DivPlatformYM2610Base::getSampleMemUsage(int index) {
|
||||||
|
return index == 0 ? adpcmAMemLen : index == 1 ? adpcmBMemLen : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DivPlatformYM2610Base::renderSamples() {
|
||||||
|
memset(adpcmAMem,0,getSampleMemCapacity(0));
|
||||||
|
|
||||||
|
size_t memPos=0;
|
||||||
|
for (int i=0; i<parent->song.sampleLen; i++) {
|
||||||
|
DivSample* s=parent->song.sample[i];
|
||||||
|
int paddedLen=(s->lengthA+255)&(~0xff);
|
||||||
|
if ((memPos&0xf00000)!=((memPos+paddedLen)&0xf00000)) {
|
||||||
|
memPos=(memPos+0xfffff)&0xf00000;
|
||||||
|
}
|
||||||
|
if (memPos>=getSampleMemCapacity(0)) {
|
||||||
|
logW("out of ADPCM-A memory for sample %d!",i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (memPos+paddedLen>=getSampleMemCapacity(0)) {
|
||||||
|
memcpy(adpcmAMem+memPos,s->dataA,getSampleMemCapacity(0)-memPos);
|
||||||
|
logW("out of ADPCM-A memory for sample %d!",i);
|
||||||
|
} else {
|
||||||
|
memcpy(adpcmAMem+memPos,s->dataA,paddedLen);
|
||||||
|
}
|
||||||
|
s->offA=memPos;
|
||||||
|
memPos+=paddedLen;
|
||||||
|
}
|
||||||
|
adpcmAMemLen=memPos+256;
|
||||||
|
|
||||||
|
memset(adpcmBMem,0,getSampleMemCapacity(1));
|
||||||
|
|
||||||
|
memPos=0;
|
||||||
|
for (int i=0; i<parent->song.sampleLen; i++) {
|
||||||
|
DivSample* s=parent->song.sample[i];
|
||||||
|
int paddedLen=(s->lengthB+255)&(~0xff);
|
||||||
|
if ((memPos&0xf00000)!=((memPos+paddedLen)&0xf00000)) {
|
||||||
|
memPos=(memPos+0xfffff)&0xf00000;
|
||||||
|
}
|
||||||
|
if (memPos>=getSampleMemCapacity(1)) {
|
||||||
|
logW("out of ADPCM-B memory for sample %d!",i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (memPos+paddedLen>=getSampleMemCapacity(1)) {
|
||||||
|
memcpy(adpcmBMem+memPos,s->dataB,getSampleMemCapacity(1)-memPos);
|
||||||
|
logW("out of ADPCM-B memory for sample %d!",i);
|
||||||
|
} else {
|
||||||
|
memcpy(adpcmBMem+memPos,s->dataB,paddedLen);
|
||||||
|
}
|
||||||
|
s->offB=memPos;
|
||||||
|
memPos+=paddedLen;
|
||||||
|
}
|
||||||
|
adpcmBMemLen=memPos+256;
|
||||||
|
}
|
||||||
|
|
||||||
|
int DivPlatformYM2610Base::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
|
||||||
|
parent=p;
|
||||||
|
adpcmAMem=new unsigned char[getSampleMemCapacity(0)];
|
||||||
|
adpcmAMemLen=0;
|
||||||
|
adpcmBMem=new unsigned char[getSampleMemCapacity(1)];
|
||||||
|
adpcmBMemLen=0;
|
||||||
|
iface.adpcmAMem=adpcmAMem;
|
||||||
|
iface.adpcmBMem=adpcmBMem;
|
||||||
|
iface.sampleBank=0;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DivPlatformYM2610Base::quit() {
|
||||||
|
delete[] adpcmAMem;
|
||||||
|
delete[] adpcmBMem;
|
||||||
|
}
|
||||||
|
|
||||||
const char** DivPlatformYM2610::getRegisterSheet() {
|
const char** DivPlatformYM2610::getRegisterSheet() {
|
||||||
return regCheatSheetYM2610;
|
return regCheatSheetYM2610;
|
||||||
}
|
}
|
||||||
|
@ -1185,7 +1265,7 @@ void DivPlatformYM2610::setSkipRegisterWrites(bool value) {
|
||||||
}
|
}
|
||||||
|
|
||||||
int DivPlatformYM2610::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
|
int DivPlatformYM2610::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
|
||||||
parent=p;
|
DivPlatformYM2610Base::init(p, channels, sugRate, flags);
|
||||||
dumpWrites=false;
|
dumpWrites=false;
|
||||||
skipRegisterWrites=false;
|
skipRegisterWrites=false;
|
||||||
for (int i=0; i<14; i++) {
|
for (int i=0; i<14; i++) {
|
||||||
|
@ -1197,8 +1277,6 @@ int DivPlatformYM2610::init(DivEngine* p, int channels, int sugRate, unsigned in
|
||||||
for (int i=0; i<14; i++) {
|
for (int i=0; i<14; i++) {
|
||||||
oscBuf[i]->rate=rate;
|
oscBuf[i]->rate=rate;
|
||||||
}
|
}
|
||||||
iface.parent=parent;
|
|
||||||
iface.sampleBank=0;
|
|
||||||
fm=new ymfm::ym2610(iface);
|
fm=new ymfm::ym2610(iface);
|
||||||
// YM2149, 2MHz
|
// YM2149, 2MHz
|
||||||
ay=new DivPlatformAY8910;
|
ay=new DivPlatformAY8910;
|
||||||
|
@ -1215,6 +1293,7 @@ void DivPlatformYM2610::quit() {
|
||||||
ay->quit();
|
ay->quit();
|
||||||
delete ay;
|
delete ay;
|
||||||
delete fm;
|
delete fm;
|
||||||
|
DivPlatformYM2610Base::quit();
|
||||||
}
|
}
|
||||||
|
|
||||||
DivPlatformYM2610::~DivPlatformYM2610() {
|
DivPlatformYM2610::~DivPlatformYM2610() {
|
||||||
|
|
|
@ -27,14 +27,32 @@
|
||||||
|
|
||||||
class DivYM2610Interface: public ymfm::ymfm_interface {
|
class DivYM2610Interface: public ymfm::ymfm_interface {
|
||||||
public:
|
public:
|
||||||
DivEngine* parent;
|
unsigned char* adpcmAMem;
|
||||||
|
unsigned char* adpcmBMem;
|
||||||
int sampleBank;
|
int sampleBank;
|
||||||
uint8_t ymfm_external_read(ymfm::access_class type, uint32_t address);
|
uint8_t ymfm_external_read(ymfm::access_class type, uint32_t address);
|
||||||
void ymfm_external_write(ymfm::access_class type, uint32_t address, uint8_t data);
|
void ymfm_external_write(ymfm::access_class type, uint32_t address, uint8_t data);
|
||||||
DivYM2610Interface(): parent(NULL), sampleBank(0) {}
|
DivYM2610Interface(): adpcmAMem(NULL), adpcmBMem(NULL), sampleBank(0) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
class DivPlatformYM2610: public DivDispatch {
|
class DivPlatformYM2610Base: public DivDispatch {
|
||||||
|
protected:
|
||||||
|
unsigned char* adpcmAMem;
|
||||||
|
size_t adpcmAMemLen;
|
||||||
|
unsigned char* adpcmBMem;
|
||||||
|
size_t adpcmBMemLen;
|
||||||
|
DivYM2610Interface iface;
|
||||||
|
|
||||||
|
public:
|
||||||
|
const void* getSampleMem(int index);
|
||||||
|
size_t getSampleMemCapacity(int index);
|
||||||
|
size_t getSampleMemUsage(int index);
|
||||||
|
void renderSamples();
|
||||||
|
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
||||||
|
void quit();
|
||||||
|
};
|
||||||
|
|
||||||
|
class DivPlatformYM2610: public DivPlatformYM2610Base {
|
||||||
protected:
|
protected:
|
||||||
const unsigned short chanOffs[4]={
|
const unsigned short chanOffs[4]={
|
||||||
0x01, 0x02, 0x101, 0x102
|
0x01, 0x02, 0x101, 0x102
|
||||||
|
@ -93,7 +111,6 @@ class DivPlatformYM2610: public DivDispatch {
|
||||||
std::queue<QueuedWrite> writes;
|
std::queue<QueuedWrite> writes;
|
||||||
ymfm::ym2610* fm;
|
ymfm::ym2610* fm;
|
||||||
ymfm::ym2610::output_data fmout;
|
ymfm::ym2610::output_data fmout;
|
||||||
DivYM2610Interface iface;
|
|
||||||
|
|
||||||
DivPlatformAY8910* ay;
|
DivPlatformAY8910* ay;
|
||||||
unsigned char regPool[512];
|
unsigned char regPool[512];
|
||||||
|
|
|
@ -24,13 +24,11 @@
|
||||||
uint8_t DivYM2610Interface::ymfm_external_read(ymfm::access_class type, uint32_t address) {
|
uint8_t DivYM2610Interface::ymfm_external_read(ymfm::access_class type, uint32_t address) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case ymfm::ACCESS_ADPCM_A:
|
case ymfm::ACCESS_ADPCM_A:
|
||||||
if (parent->adpcmAMem==NULL) return 0;
|
if (adpcmAMem==NULL) return 0;
|
||||||
if ((address&0xffffff)>=parent->adpcmAMemLen) return 0;
|
return adpcmAMem[address&0xffffff];
|
||||||
return parent->adpcmAMem[address&0xffffff];
|
|
||||||
case ymfm::ACCESS_ADPCM_B:
|
case ymfm::ACCESS_ADPCM_B:
|
||||||
if (parent->adpcmBMem==NULL) return 0;
|
if (adpcmBMem==NULL) return 0;
|
||||||
if ((address&0xffffff)>=parent->adpcmBMemLen) return 0;
|
return adpcmBMem[address&0xffffff];
|
||||||
return parent->adpcmBMem[address&0xffffff];
|
|
||||||
default:
|
default:
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1243,7 +1243,7 @@ void DivPlatformYM2610B::setSkipRegisterWrites(bool value) {
|
||||||
}
|
}
|
||||||
|
|
||||||
int DivPlatformYM2610B::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
|
int DivPlatformYM2610B::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
|
||||||
parent=p;
|
DivPlatformYM2610Base::init(p, channels, sugRate, flags);
|
||||||
dumpWrites=false;
|
dumpWrites=false;
|
||||||
skipRegisterWrites=false;
|
skipRegisterWrites=false;
|
||||||
for (int i=0; i<16; i++) {
|
for (int i=0; i<16; i++) {
|
||||||
|
@ -1255,8 +1255,6 @@ int DivPlatformYM2610B::init(DivEngine* p, int channels, int sugRate, unsigned i
|
||||||
for (int i=0; i<16; i++) {
|
for (int i=0; i<16; i++) {
|
||||||
oscBuf[i]->rate=rate;
|
oscBuf[i]->rate=rate;
|
||||||
}
|
}
|
||||||
iface.parent=parent;
|
|
||||||
iface.sampleBank=0;
|
|
||||||
fm=new ymfm::ym2610b(iface);
|
fm=new ymfm::ym2610b(iface);
|
||||||
// YM2149, 2MHz
|
// YM2149, 2MHz
|
||||||
ay=new DivPlatformAY8910;
|
ay=new DivPlatformAY8910;
|
||||||
|
@ -1273,6 +1271,7 @@ void DivPlatformYM2610B::quit() {
|
||||||
ay->quit();
|
ay->quit();
|
||||||
delete ay;
|
delete ay;
|
||||||
delete fm;
|
delete fm;
|
||||||
|
DivPlatformYM2610Base::quit();
|
||||||
}
|
}
|
||||||
|
|
||||||
DivPlatformYM2610B::~DivPlatformYM2610B() {
|
DivPlatformYM2610B::~DivPlatformYM2610B() {
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
|
|
||||||
#include "ym2610.h"
|
#include "ym2610.h"
|
||||||
|
|
||||||
class DivPlatformYM2610B: public DivDispatch {
|
class DivPlatformYM2610B: public DivPlatformYM2610Base {
|
||||||
protected:
|
protected:
|
||||||
const unsigned short chanOffs[6]={
|
const unsigned short chanOffs[6]={
|
||||||
0x00, 0x01, 0x02, 0x100, 0x101, 0x102
|
0x00, 0x01, 0x02, 0x100, 0x101, 0x102
|
||||||
|
@ -85,7 +85,6 @@ class DivPlatformYM2610B: public DivDispatch {
|
||||||
std::queue<QueuedWrite> writes;
|
std::queue<QueuedWrite> writes;
|
||||||
ymfm::ym2610b* fm;
|
ymfm::ym2610b* fm;
|
||||||
ymfm::ym2610b::output_data fmout;
|
ymfm::ym2610b::output_data fmout;
|
||||||
DivYM2610Interface iface;
|
|
||||||
unsigned char regPool[512];
|
unsigned char regPool[512];
|
||||||
unsigned char lastBusy;
|
unsigned char lastBusy;
|
||||||
|
|
||||||
|
|
|
@ -65,9 +65,24 @@ const char* cmdName[]={
|
||||||
"FM_LFO",
|
"FM_LFO",
|
||||||
"FM_LFO_WAVE",
|
"FM_LFO_WAVE",
|
||||||
"FM_TL",
|
"FM_TL",
|
||||||
|
"FM_AM",
|
||||||
"FM_AR",
|
"FM_AR",
|
||||||
|
"FM_DR",
|
||||||
|
"FM_SL",
|
||||||
|
"FM_D2R",
|
||||||
|
"FM_RR",
|
||||||
|
"FM_DT",
|
||||||
|
"FM_DT2",
|
||||||
|
"FM_RS",
|
||||||
|
"FM_KSR",
|
||||||
|
"FM_VIB",
|
||||||
|
"FM_SUS",
|
||||||
|
"FM_WS",
|
||||||
|
"FM_SSG",
|
||||||
"FM_FB",
|
"FM_FB",
|
||||||
"FM_MULT",
|
"FM_MULT",
|
||||||
|
"FM_FINE",
|
||||||
|
"FM_FIXFREQ",
|
||||||
"FM_EXTCH",
|
"FM_EXTCH",
|
||||||
"FM_AM_DEPTH",
|
"FM_AM_DEPTH",
|
||||||
"FM_PM_DEPTH",
|
"FM_PM_DEPTH",
|
||||||
|
@ -156,6 +171,8 @@ const char* cmdName[]={
|
||||||
"ES5506_FILTER_MODE",
|
"ES5506_FILTER_MODE",
|
||||||
"ES5506_FILTER_K1",
|
"ES5506_FILTER_K1",
|
||||||
"ES5506_FILTER_K2",
|
"ES5506_FILTER_K2",
|
||||||
|
"ES5506_FILTER_K1_SLIDE",
|
||||||
|
"ES5506_FILTER_K2_SLIDE",
|
||||||
"ES5506_ENVELOPE_COUNT",
|
"ES5506_ENVELOPE_COUNT",
|
||||||
"ES5506_ENVELOPE_LVRAMP",
|
"ES5506_ENVELOPE_LVRAMP",
|
||||||
"ES5506_ENVELOPE_RVRAMP",
|
"ES5506_ENVELOPE_RVRAMP",
|
||||||
|
@ -339,6 +356,9 @@ bool DivEngine::perSystemEffect(int ch, unsigned char effect, unsigned char effe
|
||||||
case 0x14: // sweep down
|
case 0x14: // sweep down
|
||||||
dispatchCmd(DivCommand(DIV_CMD_NES_SWEEP,ch,1,effectVal));
|
dispatchCmd(DivCommand(DIV_CMD_NES_SWEEP,ch,1,effectVal));
|
||||||
break;
|
break;
|
||||||
|
case 0x18: // DPCM mode
|
||||||
|
dispatchCmd(DivCommand(DIV_CMD_SAMPLE_MODE,ch,effectVal));
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -557,6 +577,14 @@ bool DivEngine::perSystemEffect(int ch, unsigned char effect, unsigned char effe
|
||||||
case 0x27: // envelope K2RAMP
|
case 0x27: // envelope K2RAMP
|
||||||
dispatchCmd(DivCommand(DIV_CMD_ES5506_ENVELOPE_K2RAMP,ch,effectVal,effect&0x01));
|
dispatchCmd(DivCommand(DIV_CMD_ES5506_ENVELOPE_K2RAMP,ch,effectVal,effect&0x01));
|
||||||
break;
|
break;
|
||||||
|
case 0x28: // filter K1 slide up
|
||||||
|
case 0x29: // filter K1 slide down
|
||||||
|
dispatchCmd(DivCommand(DIV_CMD_ES5506_FILTER_K1_SLIDE,ch,effectVal,effect&0x01));
|
||||||
|
break;
|
||||||
|
case 0x2a: // filter K2 slide up
|
||||||
|
case 0x2b: // filter K2 slide down
|
||||||
|
dispatchCmd(DivCommand(DIV_CMD_ES5506_FILTER_K2_SLIDE,ch,effectVal,effect&0x01));
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
if ((effect&0xf0)==0x30) {
|
if ((effect&0xf0)==0x30) {
|
||||||
dispatchCmd(DivCommand(DIV_CMD_ES5506_FILTER_K1,ch,((effect&0x0f)<<8)|effectVal));
|
dispatchCmd(DivCommand(DIV_CMD_ES5506_FILTER_K1,ch,((effect&0x0f)<<8)|effectVal));
|
||||||
|
@ -1978,8 +2006,8 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi
|
||||||
}
|
}
|
||||||
runtotal[i]=blip_clocks_needed(disCont[i].bb[0],size-lastAvail[i]);
|
runtotal[i]=blip_clocks_needed(disCont[i].bb[0],size-lastAvail[i]);
|
||||||
if (runtotal[i]>disCont[i].bbInLen) {
|
if (runtotal[i]>disCont[i].bbInLen) {
|
||||||
delete disCont[i].bbIn[0];
|
delete[] disCont[i].bbIn[0];
|
||||||
delete disCont[i].bbIn[1];
|
delete[] disCont[i].bbIn[1];
|
||||||
disCont[i].bbIn[0]=new short[runtotal[i]+256];
|
disCont[i].bbIn[0]=new short[runtotal[i]+256];
|
||||||
disCont[i].bbIn[1]=new short[runtotal[i]+256];
|
disCont[i].bbIn[1]=new short[runtotal[i]+256];
|
||||||
disCont[i].bbInLen=runtotal[i]+256;
|
disCont[i].bbInLen=runtotal[i]+256;
|
||||||
|
|
|
@ -752,11 +752,11 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
|
||||||
bool writeDACSamples=false;
|
bool writeDACSamples=false;
|
||||||
bool writeNESSamples=false;
|
bool writeNESSamples=false;
|
||||||
bool writePCESamples=false;
|
bool writePCESamples=false;
|
||||||
unsigned char writeADPCM=0;
|
DivDispatch* writeADPCM[2]={NULL,NULL};
|
||||||
unsigned char writeSegaPCM=0;
|
int writeSegaPCM=0;
|
||||||
unsigned char writeX1010=0;
|
DivDispatch* writeX1010[2]={NULL,NULL};
|
||||||
unsigned char writeQSound=0;
|
DivDispatch* writeQSound[2]={NULL,NULL};
|
||||||
unsigned char writeES5506=0;
|
DivDispatch* writeES5506[2]={NULL,NULL};
|
||||||
|
|
||||||
for (int i=0; i<song.systemLen; i++) {
|
for (int i=0; i<song.systemLen; i++) {
|
||||||
willExport[i]=false;
|
willExport[i]=false;
|
||||||
|
@ -845,11 +845,11 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
|
||||||
if (!hasX1) {
|
if (!hasX1) {
|
||||||
hasX1=disCont[i].dispatch->chipClock;
|
hasX1=disCont[i].dispatch->chipClock;
|
||||||
willExport[i]=true;
|
willExport[i]=true;
|
||||||
writeX1010=1;
|
writeX1010[0]=disCont[i].dispatch;
|
||||||
} else if (!(hasX1&0x40000000)) {
|
} else if (!(hasX1&0x40000000)) {
|
||||||
isSecond[i]=true;
|
isSecond[i]=true;
|
||||||
willExport[i]=true;
|
willExport[i]=true;
|
||||||
writeX1010=2;
|
writeX1010[1]=disCont[i].dispatch;
|
||||||
hasX1|=0x40000000;
|
hasX1|=0x40000000;
|
||||||
howManyChips++;
|
howManyChips++;
|
||||||
}
|
}
|
||||||
|
@ -863,11 +863,11 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
|
||||||
if (!hasOPNB) {
|
if (!hasOPNB) {
|
||||||
hasOPNB=disCont[i].dispatch->chipClock;
|
hasOPNB=disCont[i].dispatch->chipClock;
|
||||||
willExport[i]=true;
|
willExport[i]=true;
|
||||||
writeADPCM=1;
|
writeADPCM[0]=disCont[i].dispatch;
|
||||||
} else if (!(hasOPNB&0x40000000)) {
|
} else if (!(hasOPNB&0x40000000)) {
|
||||||
isSecond[i]=true;
|
isSecond[i]=true;
|
||||||
willExport[i]=true;
|
willExport[i]=true;
|
||||||
writeADPCM=2;
|
writeADPCM[1]=disCont[i].dispatch;
|
||||||
hasOPNB|=0x40000000;
|
hasOPNB|=0x40000000;
|
||||||
howManyChips++;
|
howManyChips++;
|
||||||
}
|
}
|
||||||
|
@ -969,11 +969,11 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
|
||||||
// not be able to handle the 64kb sample bank trick
|
// not be able to handle the 64kb sample bank trick
|
||||||
hasQSound=disCont[i].dispatch->chipClock;
|
hasQSound=disCont[i].dispatch->chipClock;
|
||||||
willExport[i]=true;
|
willExport[i]=true;
|
||||||
writeQSound=1;
|
writeQSound[0]=disCont[i].dispatch;
|
||||||
} else if (!(hasQSound&0x40000000)) {
|
} else if (!(hasQSound&0x40000000)) {
|
||||||
isSecond[i]=true;
|
isSecond[i]=true;
|
||||||
willExport[i]=false;
|
willExport[i]=false;
|
||||||
writeQSound=2;
|
writeQSound[1]=disCont[i].dispatch;
|
||||||
addWarning("dual QSound is not supported by the VGM format");
|
addWarning("dual QSound is not supported by the VGM format");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -997,12 +997,12 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
|
||||||
// VGM identifies ES5506 if highest bit sets, otherwise ES5505
|
// VGM identifies ES5506 if highest bit sets, otherwise ES5505
|
||||||
hasES5505=0x80000000|disCont[i].dispatch->chipClock;
|
hasES5505=0x80000000|disCont[i].dispatch->chipClock;
|
||||||
willExport[i]=true;
|
willExport[i]=true;
|
||||||
writeES5506=1;
|
writeES5506[0]=disCont[i].dispatch;
|
||||||
} else if (!(hasES5505&0x40000000)) {
|
} else if (!(hasES5505&0x40000000)) {
|
||||||
isSecond[i]=true;
|
isSecond[i]=true;
|
||||||
willExport[i]=false;
|
willExport[i]=false;
|
||||||
hasES5505|=0xc0000000;
|
hasES5505|=0xc0000000;
|
||||||
writeES5506=2;
|
writeES5506[1]=disCont[i].dispatch;
|
||||||
howManyChips++;
|
howManyChips++;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -1303,68 +1303,68 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
|
||||||
delete[] pcmMem;
|
delete[] pcmMem;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (adpcmAMemLen>0) {
|
for (int i=0; i<2; i++) {
|
||||||
for (unsigned char i=0; i<writeADPCM; i++) {
|
if (writeADPCM[i]!=NULL && writeADPCM[i]->getSampleMemUsage(0)>0) {
|
||||||
w->writeC(0x67);
|
w->writeC(0x67);
|
||||||
w->writeC(0x66);
|
w->writeC(0x66);
|
||||||
w->writeC(0x82);
|
w->writeC(0x82);
|
||||||
w->writeI((adpcmAMemLen+8)|(i*0x80000000));
|
w->writeI((writeADPCM[i]->getSampleMemUsage(0)+8)|(i*0x80000000));
|
||||||
w->writeI(adpcmAMemLen);
|
w->writeI(writeADPCM[i]->getSampleMemCapacity(0));
|
||||||
w->writeI(0);
|
w->writeI(0);
|
||||||
w->write(adpcmAMem,adpcmAMemLen);
|
w->write(writeADPCM[i]->getSampleMem(0),writeADPCM[i]->getSampleMemUsage(0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (adpcmBMemLen>0) {
|
for (int i=0; i<2; i++) {
|
||||||
for (unsigned char i=0; i<writeADPCM; i++) {
|
if (writeADPCM[i]!=NULL && writeADPCM[i]->getSampleMemUsage(1)>0) {
|
||||||
w->writeC(0x67);
|
w->writeC(0x67);
|
||||||
w->writeC(0x66);
|
w->writeC(0x66);
|
||||||
w->writeC(0x83);
|
w->writeC(0x83);
|
||||||
w->writeI((adpcmBMemLen+8)|(i*0x80000000));
|
w->writeI((writeADPCM[i]->getSampleMemUsage(1)+8)|(i*0x80000000));
|
||||||
w->writeI(adpcmBMemLen);
|
w->writeI(writeADPCM[i]->getSampleMemCapacity(1));
|
||||||
w->writeI(0);
|
w->writeI(0);
|
||||||
w->write(adpcmBMem,adpcmBMemLen);
|
w->write(writeADPCM[i]->getSampleMem(1),writeADPCM[i]->getSampleMemUsage(1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (qsoundMemLen>0) {
|
for (int i=0; i<2; i++) {
|
||||||
// always write a whole bank
|
if (writeQSound[i]!=NULL && writeQSound[i]->getSampleMemUsage()>0) {
|
||||||
unsigned int blockSize=(qsoundMemLen+0xffff)&(~0xffff);
|
unsigned int blockSize=(writeQSound[i]->getSampleMemUsage()+0xffff)&(~0xffff);
|
||||||
if (blockSize > 0x1000000) {
|
if (blockSize > 0x1000000) {
|
||||||
blockSize = 0x1000000;
|
blockSize = 0x1000000;
|
||||||
}
|
}
|
||||||
for (unsigned char i=0; i<writeQSound; i++) {
|
|
||||||
w->writeC(0x67);
|
w->writeC(0x67);
|
||||||
w->writeC(0x66);
|
w->writeC(0x66);
|
||||||
w->writeC(0x8F);
|
w->writeC(0x8F);
|
||||||
w->writeI((blockSize+8)|(i*0x80000000));
|
w->writeI((blockSize+8)|(i*0x80000000));
|
||||||
w->writeI(0x1000000);
|
w->writeI(writeQSound[i]->getSampleMemCapacity());
|
||||||
w->writeI(0);
|
w->writeI(0);
|
||||||
w->write(qsoundMem,blockSize);
|
w->write(writeQSound[i]->getSampleMem(),blockSize);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (x1_010MemLen>0) {
|
for (int i=0; i<2; i++) {
|
||||||
for (unsigned char i=0; i<writeX1010; i++) {
|
if (writeX1010[i]!=NULL && writeX1010[i]->getSampleMemUsage()>0) {
|
||||||
w->writeC(0x67);
|
w->writeC(0x67);
|
||||||
w->writeC(0x66);
|
w->writeC(0x66);
|
||||||
w->writeC(0x91);
|
w->writeC(0x91);
|
||||||
w->writeI((x1_010MemLen+8)|(i*0x80000000));
|
w->writeI((writeX1010[i]->getSampleMemUsage()+8)|(i*0x80000000));
|
||||||
w->writeI(x1_010MemLen);
|
w->writeI(writeX1010[i]->getSampleMemCapacity());
|
||||||
w->writeI(0);
|
w->writeI(0);
|
||||||
w->write(x1_010Mem,x1_010MemLen);
|
w->write(writeX1010[i]->getSampleMem(),writeX1010[i]->getSampleMemUsage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (es5506MemLen>0) {
|
// TODO
|
||||||
for (unsigned char i=0; i<writeES5506; i++) {
|
for (int i=0; i<2; i++) {
|
||||||
|
if (writeES5506[i]!=NULL && writeES5506[i]->getSampleMemUsage()>0) {
|
||||||
w->writeC(0x67);
|
w->writeC(0x67);
|
||||||
w->writeC(0x66);
|
w->writeC(0x66);
|
||||||
w->writeC(0x8F);
|
w->writeC(0x8F);
|
||||||
w->writeI((es5506MemLen+8)|(i*0x80000000));
|
w->writeI((writeES5506[i]->getSampleMemUsage()+8)|(i*0x80000000));
|
||||||
w->writeI(es5506MemLen);
|
w->writeI(writeES5506[i]->getSampleMemCapacity());
|
||||||
w->writeI(0);
|
w->writeI(0);
|
||||||
w->write(es5506Mem,es5506MemLen);
|
w->write(writeES5506[i]->getSampleMem(),writeES5506[i]->getSampleMemUsage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,15 +30,38 @@ void FurnaceGUI::drawChanOsc() {
|
||||||
if (!chanOscOpen) return;
|
if (!chanOscOpen) return;
|
||||||
ImGui::SetNextWindowSizeConstraints(ImVec2(64.0f*dpiScale,32.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale));
|
ImGui::SetNextWindowSizeConstraints(ImVec2(64.0f*dpiScale,32.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale));
|
||||||
if (ImGui::Begin("Oscilloscope (per-channel)",&chanOscOpen)) {
|
if (ImGui::Begin("Oscilloscope (per-channel)",&chanOscOpen)) {
|
||||||
if (ImGui::InputInt("Columns",&chanOscCols,1,1)) {
|
if (ImGui::BeginTable("ChanOscSettings",3)) {
|
||||||
|
ImGui::TableNextRow();
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::Text("Columns");
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
|
||||||
|
if (ImGui::InputInt("##COSColumns",&chanOscCols,1,1)) {
|
||||||
if (chanOscCols<1) chanOscCols=1;
|
if (chanOscCols<1) chanOscCols=1;
|
||||||
if (chanOscCols>64) chanOscCols=64;
|
if (chanOscCols>64) chanOscCols=64;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::Text("Size (ms)");
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
|
||||||
|
if (ImGui::InputFloat("##COSWinSize",&chanOscWindowSize,1.0f,1.0f)) {
|
||||||
|
if (chanOscWindowSize<1.0f) chanOscWindowSize=1.0f;
|
||||||
|
if (chanOscWindowSize>50.0f) chanOscWindowSize=50.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::Checkbox("Center waveform",&chanOscWaveCorr);
|
||||||
|
|
||||||
|
ImGui::EndTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_CellPadding,ImVec2(0.0f,0.0f));
|
ImGui::PushStyleVar(ImGuiStyleVar_CellPadding,ImVec2(0.0f,0.0f));
|
||||||
float availY=ImGui::GetContentRegionAvail().y;
|
float availY=ImGui::GetContentRegionAvail().y;
|
||||||
if (ImGui::BeginTable("ChanOsc",chanOscCols,ImGuiTableFlags_Borders)) {
|
if (ImGui::BeginTable("ChanOsc",chanOscCols,ImGuiTableFlags_Borders)) {
|
||||||
std::vector<DivDispatchOscBuffer*> oscBufs;
|
std::vector<DivDispatchOscBuffer*> oscBufs;
|
||||||
|
std::vector<int> oscChans;
|
||||||
int chans=e->getTotalChannelCount();
|
int chans=e->getTotalChannelCount();
|
||||||
ImDrawList* dl=ImGui::GetWindowDrawList();
|
ImDrawList* dl=ImGui::GetWindowDrawList();
|
||||||
ImGuiWindow* window=ImGui::GetCurrentWindow();
|
ImGuiWindow* window=ImGui::GetCurrentWindow();
|
||||||
|
@ -49,7 +72,10 @@ void FurnaceGUI::drawChanOsc() {
|
||||||
|
|
||||||
for (int i=0; i<chans; i++) {
|
for (int i=0; i<chans; i++) {
|
||||||
DivDispatchOscBuffer* buf=e->getOscBuffer(i);
|
DivDispatchOscBuffer* buf=e->getOscBuffer(i);
|
||||||
if (buf!=NULL) oscBufs.push_back(buf);
|
if (buf!=NULL) {
|
||||||
|
oscBufs.push_back(buf);
|
||||||
|
oscChans.push_back(i);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
int rows=(oscBufs.size()+(chanOscCols-1))/chanOscCols;
|
int rows=(oscBufs.size()+(chanOscCols-1))/chanOscCols;
|
||||||
|
|
||||||
|
@ -58,13 +84,14 @@ void FurnaceGUI::drawChanOsc() {
|
||||||
ImGui::TableNextColumn();
|
ImGui::TableNextColumn();
|
||||||
|
|
||||||
DivDispatchOscBuffer* buf=oscBufs[i];
|
DivDispatchOscBuffer* buf=oscBufs[i];
|
||||||
|
int ch=oscChans[i];
|
||||||
if (buf==NULL) {
|
if (buf==NULL) {
|
||||||
ImGui::Text("Error!");
|
ImGui::Text("Error!");
|
||||||
} else {
|
} else {
|
||||||
ImVec2 size=ImGui::GetContentRegionAvail();
|
ImVec2 size=ImGui::GetContentRegionAvail();
|
||||||
size.y=availY/rows;
|
size.y=availY/rows;
|
||||||
|
|
||||||
int displaySize=(buf->rate)/30;
|
int displaySize=(float)(buf->rate)*(chanOscWindowSize/1000.0f);
|
||||||
|
|
||||||
ImVec2 minArea=window->DC.CursorPos;
|
ImVec2 minArea=window->DC.CursorPos;
|
||||||
ImVec2 maxArea=ImVec2(
|
ImVec2 maxArea=ImVec2(
|
||||||
|
@ -85,7 +112,26 @@ void FurnaceGUI::drawChanOsc() {
|
||||||
waveform[i]=ImLerp(inRect.Min,inRect.Max,ImVec2(x,0.5f));
|
waveform[i]=ImLerp(inRect.Min,inRect.Max,ImVec2(x,0.5f));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
unsigned short needlePos=buf->needle-displaySize;
|
unsigned short needlePos=buf->needle;
|
||||||
|
if (chanOscWaveCorr) {
|
||||||
|
float cutoff=0.01f;
|
||||||
|
while (buf->readNeedle!=needlePos) {
|
||||||
|
//float old=chanOscLP1[ch];
|
||||||
|
chanOscLP0[ch]+=cutoff*((float)buf->data[buf->readNeedle]-chanOscLP0[ch]);
|
||||||
|
chanOscLP1[ch]+=cutoff*(chanOscLP0[ch]-chanOscLP1[ch]);
|
||||||
|
if (chanOscLP1[ch]>=20) {
|
||||||
|
lastCorrPos[ch]=buf->readNeedle;
|
||||||
|
}
|
||||||
|
buf->readNeedle++;
|
||||||
|
}
|
||||||
|
needlePos=lastCorrPos[ch];
|
||||||
|
/*
|
||||||
|
for (unsigned short i=0; i<displaySize; i++) {
|
||||||
|
short old=buf->data[needlePos--];
|
||||||
|
if (buf->data[needlePos]>old) break;
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
needlePos-=displaySize;
|
||||||
for (unsigned short i=0; i<512; i++) {
|
for (unsigned short i=0; i<512; i++) {
|
||||||
float x=(float)i/512.0f;
|
float x=(float)i/512.0f;
|
||||||
float y=(float)buf->data[(unsigned short)(needlePos+(i*displaySize/512))]/65536.0f;
|
float y=(float)buf->data[(unsigned short)(needlePos+(i*displaySize/512))]/65536.0f;
|
||||||
|
|
|
@ -367,6 +367,10 @@ void putDispatchChan(void* data, int chanNum, int type) {
|
||||||
ImGui::Text(" - K2Ramp: %d",ch->envelope.k2Ramp);
|
ImGui::Text(" - K2Ramp: %d",ch->envelope.k2Ramp);
|
||||||
ImGui::Text(" - K1Offs: %d",ch->k1Offs);
|
ImGui::Text(" - K1Offs: %d",ch->k1Offs);
|
||||||
ImGui::Text(" - K2Offs: %d",ch->k2Offs);
|
ImGui::Text(" - K2Offs: %d",ch->k2Offs);
|
||||||
|
ImGui::Text(" - K1Slide: %d",ch->k1Slide);
|
||||||
|
ImGui::Text(" - K2Slide: %d",ch->k2Slide);
|
||||||
|
ImGui::Text(" - K1Prev: %d",ch->k1Prev);
|
||||||
|
ImGui::Text(" - K2Prev: %d",ch->k2Prev);
|
||||||
ImGui::Text("- vol: %.2x",ch->vol);
|
ImGui::Text("- vol: %.2x",ch->vol);
|
||||||
ImGui::Text("- LVol: %.2x",ch->lVol);
|
ImGui::Text("- LVol: %.2x",ch->lVol);
|
||||||
ImGui::Text("- RVol: %.2x",ch->rVol);
|
ImGui::Text("- RVol: %.2x",ch->rVol);
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
#include "debug.h"
|
#include "debug.h"
|
||||||
#include "IconsFontAwesome4.h"
|
#include "IconsFontAwesome4.h"
|
||||||
#include <fmt/printf.h>
|
#include <fmt/printf.h>
|
||||||
|
#include <imgui.h>
|
||||||
|
|
||||||
void FurnaceGUI::drawDebug() {
|
void FurnaceGUI::drawDebug() {
|
||||||
static int bpOrder;
|
static int bpOrder;
|
||||||
|
@ -141,6 +142,51 @@ void FurnaceGUI::drawDebug() {
|
||||||
ImGui::Columns();
|
ImGui::Columns();
|
||||||
ImGui::TreePop();
|
ImGui::TreePop();
|
||||||
}
|
}
|
||||||
|
if (ImGui::TreeNode("Sample Debug")) {
|
||||||
|
for (int i=0; i<e->song.sampleLen; i++) {
|
||||||
|
DivSample* sample=e->getSample(i);
|
||||||
|
if (sample==NULL) {
|
||||||
|
ImGui::Text("%d: <NULL!>",i);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ImGui::Text("%d: %s",i,sample->name.c_str());
|
||||||
|
ImGui::Indent();
|
||||||
|
ImGui::Text("rate: %d",sample->rate);
|
||||||
|
ImGui::Text("centerRate: %d",sample->centerRate);
|
||||||
|
ImGui::Text("loopStart: %d",sample->loopStart);
|
||||||
|
ImGui::Text("loopOffP: %d",sample->loopOffP);
|
||||||
|
ImGui::Text("depth: %d",sample->depth);
|
||||||
|
ImGui::Text("length8: %d",sample->length8);
|
||||||
|
ImGui::Text("length16: %d",sample->length16);
|
||||||
|
ImGui::Text("length1: %d",sample->length1);
|
||||||
|
ImGui::Text("lengthDPCM: %d",sample->lengthDPCM);
|
||||||
|
ImGui::Text("lengthQSoundA: %d",sample->lengthQSoundA);
|
||||||
|
ImGui::Text("lengthA: %d",sample->lengthA);
|
||||||
|
ImGui::Text("lengthB: %d",sample->lengthB);
|
||||||
|
ImGui::Text("lengthX68: %d",sample->lengthX68);
|
||||||
|
ImGui::Text("lengthBRR: %d",sample->lengthBRR);
|
||||||
|
ImGui::Text("lengthVOX: %d",sample->lengthVOX);
|
||||||
|
|
||||||
|
ImGui::Text("off8: %x",sample->off8);
|
||||||
|
ImGui::Text("off16: %x",sample->off16);
|
||||||
|
ImGui::Text("off1: %x",sample->off1);
|
||||||
|
ImGui::Text("offDPCM: %x",sample->offDPCM);
|
||||||
|
ImGui::Text("offQSoundA: %x",sample->offQSoundA);
|
||||||
|
ImGui::Text("offA: %x",sample->offA);
|
||||||
|
ImGui::Text("offB: %x",sample->offB);
|
||||||
|
ImGui::Text("offX68: %x",sample->offX68);
|
||||||
|
ImGui::Text("offBRR: %x",sample->offBRR);
|
||||||
|
ImGui::Text("offVOX: %x",sample->offVOX);
|
||||||
|
ImGui::Text("offSegaPCM: %x",sample->offSegaPCM);
|
||||||
|
ImGui::Text("offQSound: %x",sample->offQSound);
|
||||||
|
ImGui::Text("offX1_010: %x",sample->offX1_010);
|
||||||
|
ImGui::Text("offES5506: %x",sample->offES5506);
|
||||||
|
|
||||||
|
ImGui::Text("samples: %d",sample->samples);
|
||||||
|
ImGui::Unindent();
|
||||||
|
}
|
||||||
|
ImGui::TreePop();
|
||||||
|
}
|
||||||
if (ImGui::TreeNode("Playground")) {
|
if (ImGui::TreeNode("Playground")) {
|
||||||
if (pgSys<0 || pgSys>=e->song.systemLen) pgSys=0;
|
if (pgSys<0 || pgSys>=e->song.systemLen) pgSys=0;
|
||||||
if (ImGui::BeginCombo("System",fmt::sprintf("%d. %s",pgSys+1,e->getSystemName(e->song.system[pgSys])).c_str())) {
|
if (ImGui::BeginCombo("System",fmt::sprintf("%d. %s",pgSys+1,e->getSystemName(e->song.system[pgSys])).c_str())) {
|
||||||
|
|
|
@ -4000,6 +4000,8 @@ FurnaceGUI::FurnaceGUI():
|
||||||
oscZoom(0.5f),
|
oscZoom(0.5f),
|
||||||
oscZoomSlider(false),
|
oscZoomSlider(false),
|
||||||
chanOscCols(3),
|
chanOscCols(3),
|
||||||
|
chanOscWindowSize(20.0f),
|
||||||
|
chanOscWaveCorr(true),
|
||||||
followLog(true),
|
followLog(true),
|
||||||
pianoOctaves(7),
|
pianoOctaves(7),
|
||||||
pianoOptions(false),
|
pianoOptions(false),
|
||||||
|
@ -4057,4 +4059,8 @@ FurnaceGUI::FurnaceGUI():
|
||||||
memset(patChanSlideY,0,sizeof(float)*(DIV_MAX_CHANS+1));
|
memset(patChanSlideY,0,sizeof(float)*(DIV_MAX_CHANS+1));
|
||||||
memset(lastIns,-1,sizeof(int)*DIV_MAX_CHANS);
|
memset(lastIns,-1,sizeof(int)*DIV_MAX_CHANS);
|
||||||
memset(oscValues,0,sizeof(float)*512);
|
memset(oscValues,0,sizeof(float)*512);
|
||||||
|
|
||||||
|
memset(chanOscLP0,0,sizeof(float)*DIV_MAX_CHANS);
|
||||||
|
memset(chanOscLP1,0,sizeof(float)*DIV_MAX_CHANS);
|
||||||
|
memset(lastCorrPos,0,sizeof(short)*DIV_MAX_CHANS);
|
||||||
}
|
}
|
||||||
|
|
|
@ -788,6 +788,7 @@ class FurnaceGUI {
|
||||||
int arcadeCore;
|
int arcadeCore;
|
||||||
int ym2612Core;
|
int ym2612Core;
|
||||||
int saaCore;
|
int saaCore;
|
||||||
|
int nesCore;
|
||||||
int mainFont;
|
int mainFont;
|
||||||
int patFont;
|
int patFont;
|
||||||
int audioRate;
|
int audioRate;
|
||||||
|
@ -870,6 +871,7 @@ class FurnaceGUI {
|
||||||
arcadeCore(0),
|
arcadeCore(0),
|
||||||
ym2612Core(0),
|
ym2612Core(0),
|
||||||
saaCore(1),
|
saaCore(1),
|
||||||
|
nesCore(0),
|
||||||
mainFont(0),
|
mainFont(0),
|
||||||
patFont(0),
|
patFont(0),
|
||||||
audioRate(44100),
|
audioRate(44100),
|
||||||
|
@ -1101,6 +1103,12 @@ class FurnaceGUI {
|
||||||
|
|
||||||
// per-channel oscilloscope
|
// per-channel oscilloscope
|
||||||
int chanOscCols;
|
int chanOscCols;
|
||||||
|
float chanOscWindowSize;
|
||||||
|
bool chanOscWaveCorr;
|
||||||
|
float chanOscLP0[DIV_MAX_CHANS];
|
||||||
|
float chanOscLP1[DIV_MAX_CHANS];
|
||||||
|
unsigned short lastNeedlePos[DIV_MAX_CHANS];
|
||||||
|
unsigned short lastCorrPos[DIV_MAX_CHANS];
|
||||||
|
|
||||||
// visualizer
|
// visualizer
|
||||||
float keyHit[DIV_MAX_CHANS];
|
float keyHit[DIV_MAX_CHANS];
|
||||||
|
|
|
@ -84,6 +84,11 @@ const char* saaCores[]={
|
||||||
"SAASound"
|
"SAASound"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const char* nesCores[]={
|
||||||
|
"puNES",
|
||||||
|
"NSFplay"
|
||||||
|
};
|
||||||
|
|
||||||
const char* valueInputStyles[]={
|
const char* valueInputStyles[]={
|
||||||
"Disabled/custom",
|
"Disabled/custom",
|
||||||
"Two octaves (0 is C-4, F is D#5)",
|
"Two octaves (0 is C-4, F is D#5)",
|
||||||
|
@ -859,6 +864,10 @@ void FurnaceGUI::drawSettings() {
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
ImGui::Combo("##SAACore",&settings.saaCore,saaCores,2);
|
ImGui::Combo("##SAACore",&settings.saaCore,saaCores,2);
|
||||||
|
|
||||||
|
ImGui::Text("NES core");
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::Combo("##NESCore",&settings.nesCore,nesCores,2);
|
||||||
|
|
||||||
ImGui::EndTabItem();
|
ImGui::EndTabItem();
|
||||||
}
|
}
|
||||||
if (ImGui::BeginTabItem("Appearance")) {
|
if (ImGui::BeginTabItem("Appearance")) {
|
||||||
|
@ -1731,6 +1740,7 @@ void FurnaceGUI::syncSettings() {
|
||||||
settings.arcadeCore=e->getConfInt("arcadeCore",0);
|
settings.arcadeCore=e->getConfInt("arcadeCore",0);
|
||||||
settings.ym2612Core=e->getConfInt("ym2612Core",0);
|
settings.ym2612Core=e->getConfInt("ym2612Core",0);
|
||||||
settings.saaCore=e->getConfInt("saaCore",1);
|
settings.saaCore=e->getConfInt("saaCore",1);
|
||||||
|
settings.nesCore=e->getConfInt("nesCore",0);
|
||||||
settings.mainFont=e->getConfInt("mainFont",0);
|
settings.mainFont=e->getConfInt("mainFont",0);
|
||||||
settings.patFont=e->getConfInt("patFont",0);
|
settings.patFont=e->getConfInt("patFont",0);
|
||||||
settings.mainFontPath=e->getConfString("mainFontPath","");
|
settings.mainFontPath=e->getConfString("mainFontPath","");
|
||||||
|
@ -1805,6 +1815,7 @@ void FurnaceGUI::syncSettings() {
|
||||||
clampSetting(settings.arcadeCore,0,1);
|
clampSetting(settings.arcadeCore,0,1);
|
||||||
clampSetting(settings.ym2612Core,0,1);
|
clampSetting(settings.ym2612Core,0,1);
|
||||||
clampSetting(settings.saaCore,0,1);
|
clampSetting(settings.saaCore,0,1);
|
||||||
|
clampSetting(settings.nesCore,0,1);
|
||||||
clampSetting(settings.mainFont,0,6);
|
clampSetting(settings.mainFont,0,6);
|
||||||
clampSetting(settings.patFont,0,6);
|
clampSetting(settings.patFont,0,6);
|
||||||
clampSetting(settings.patRowsBase,0,1);
|
clampSetting(settings.patRowsBase,0,1);
|
||||||
|
@ -1907,6 +1918,7 @@ void FurnaceGUI::commitSettings() {
|
||||||
e->setConf("arcadeCore",settings.arcadeCore);
|
e->setConf("arcadeCore",settings.arcadeCore);
|
||||||
e->setConf("ym2612Core",settings.ym2612Core);
|
e->setConf("ym2612Core",settings.ym2612Core);
|
||||||
e->setConf("saaCore",settings.saaCore);
|
e->setConf("saaCore",settings.saaCore);
|
||||||
|
e->setConf("nesCore",settings.nesCore);
|
||||||
e->setConf("mainFont",settings.mainFont);
|
e->setConf("mainFont",settings.mainFont);
|
||||||
e->setConf("patFont",settings.patFont);
|
e->setConf("patFont",settings.patFont);
|
||||||
e->setConf("mainFontPath",settings.mainFontPath);
|
e->setConf("mainFontPath",settings.mainFontPath);
|
||||||
|
|
|
@ -28,26 +28,17 @@ void FurnaceGUI::drawStats() {
|
||||||
}
|
}
|
||||||
if (!statsOpen) return;
|
if (!statsOpen) return;
|
||||||
if (ImGui::Begin("Statistics",&statsOpen)) {
|
if (ImGui::Begin("Statistics",&statsOpen)) {
|
||||||
String adpcmAUsage=fmt::sprintf("%d/16384KB",e->adpcmAMemLen/1024);
|
for (int i=0; i<e->song.systemLen; i++) {
|
||||||
String adpcmBUsage=fmt::sprintf("%d/16384KB",e->adpcmBMemLen/1024);
|
DivDispatch* dispatch=e->getDispatch(i);
|
||||||
String qsoundUsage=fmt::sprintf("%d/16384KB",e->qsoundMemLen/1024);
|
for (int j=0; dispatch!=NULL && dispatch->getSampleMemCapacity(j)>0; j++) {
|
||||||
String x1_010Usage=fmt::sprintf("%d/1024KB",e->x1_010MemLen/1024);
|
size_t capacity=dispatch->getSampleMemCapacity(j);
|
||||||
String es5506Usage=fmt::sprintf("%d/16384KB",e->es5506MemLen/1024);
|
size_t usage=dispatch->getSampleMemUsage(j);
|
||||||
ImGui::Text("ADPCM-A");
|
String usageStr=fmt::sprintf("%d/%dKB",usage/1024,capacity/1024);
|
||||||
|
ImGui::Text("%s [%d]", e->getSystemName(e->song.system[i]), j);
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
ImGui::ProgressBar(((float)e->adpcmAMemLen)/16777216.0f,ImVec2(-FLT_MIN,0),adpcmAUsage.c_str());
|
ImGui::ProgressBar(((float)usage)/((float)capacity),ImVec2(-FLT_MIN,0),usageStr.c_str());
|
||||||
ImGui::Text("ADPCM-B");
|
}
|
||||||
ImGui::SameLine();
|
}
|
||||||
ImGui::ProgressBar(((float)e->adpcmBMemLen)/16777216.0f,ImVec2(-FLT_MIN,0),adpcmBUsage.c_str());
|
|
||||||
ImGui::Text("QSound");
|
|
||||||
ImGui::SameLine();
|
|
||||||
ImGui::ProgressBar(((float)e->qsoundMemLen)/16777216.0f,ImVec2(-FLT_MIN,0),qsoundUsage.c_str());
|
|
||||||
ImGui::Text("X1-010");
|
|
||||||
ImGui::SameLine();
|
|
||||||
ImGui::ProgressBar(((float)e->x1_010MemLen)/1048576.0f,ImVec2(-FLT_MIN,0),x1_010Usage.c_str());
|
|
||||||
ImGui::Text("ES5506");
|
|
||||||
ImGui::SameLine();
|
|
||||||
ImGui::ProgressBar(((float)e->es5506MemLen)/16777216.0f,ImVec2(-FLT_MIN,0),es5506Usage.c_str());
|
|
||||||
}
|
}
|
||||||
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_STATS;
|
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_STATS;
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
|
|
Loading…
Reference in a new issue