diff --git a/CMakeLists.txt b/CMakeLists.txt index c08b8f553..55672051c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -250,6 +250,13 @@ src/engine/platform/sound/nes/mmc5.c src/engine/platform/sound/vera_psg.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/voice.cc src/engine/platform/sound/c64/wave.cc diff --git a/TODO.md b/TODO.md index 15ce4fe41..cb3f80db8 100644 --- a/TODO.md +++ b/TODO.md @@ -36,7 +36,6 @@ - store edit/followOrders/followPattern state in config - add ability to select a column by double clicking - add ability to move selection by dragging -- NSFPlay core for NES - settings: OK/Cancel buttons should be always visible - Apply button in settings - better FM chip names (number and codename) diff --git a/extern/imgui_patched/imgui_impl_sdl.cpp b/extern/imgui_patched/imgui_impl_sdl.cpp index 2870d2170..c1b2649ca 100644 --- a/extern/imgui_patched/imgui_impl_sdl.cpp +++ b/extern/imgui_patched/imgui_impl_sdl.cpp @@ -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_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); return true; } diff --git a/papers/doc/7-systems/nes.md b/papers/doc/7-systems/nes.md index 45ce95bc3..3ccca2647 100644 --- a/papers/doc/7-systems/nes.md +++ b/papers/doc/7-systems/nes.md @@ -18,4 +18,8 @@ also known as Famicom. It is a five-channel PSG: first two channels play pulse w - `14xy`: setup sweep down. - `x` is the time. - `y` is the shift. - - set to 0 to disable it. \ No newline at end of file + - 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. \ No newline at end of file diff --git a/papers/format.md b/papers/format.md index 570529c4e..42488719b 100644 --- a/papers/format.md +++ b/papers/format.md @@ -675,6 +675,26 @@ size | description 1 | parameter 2 1 | parameter 3 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) 1 | don't test/gate before new note ``` diff --git a/src/engine/dispatch.h b/src/engine/dispatch.h index d5843fee3..b28d880ad 100644 --- a/src/engine/dispatch.h +++ b/src/engine/dispatch.h @@ -64,9 +64,24 @@ enum DivDispatchCmds { DIV_CMD_FM_LFO, // (speed) DIV_CMD_FM_LFO_WAVE, // (waveform) DIV_CMD_FM_TL, // (op, value) + DIV_CMD_FM_AM, // (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_MULT, // (op, value) + DIV_CMD_FM_FINE, // (op, value) + DIV_CMD_FM_FIXFREQ, // (op, value) DIV_CMD_FM_EXTCH, // (enabled) DIV_CMD_FM_AM_DEPTH, // (depth) DIV_CMD_FM_PM_DEPTH, // (depth) @@ -155,6 +170,8 @@ enum DivDispatchCmds { DIV_CMD_ES5506_FILTER_MODE, // (value) DIV_CMD_ES5506_FILTER_K1, // (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_LVRAMP, // (ramp) DIV_CMD_ES5506_ENVELOPE_RVRAMP, // (ramp) @@ -218,11 +235,13 @@ struct DivRegWrite { struct DivDispatchOscBuffer { unsigned int rate; unsigned short needle; + unsigned short readNeedle; short data[65536]; DivDispatchOscBuffer(): rate(65536), - needle(0) { + needle(0), + readNeedle(0) { memset(data,0,65536*sizeof(short)); } }; @@ -447,6 +466,26 @@ class DivDispatch { */ 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. * @param parent the parent DivEngine. diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index 67209579f..ca51cefdb 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -190,6 +190,7 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do break; case DIV_SYSTEM_NES: dispatch=new DivPlatformNES; + ((DivPlatformNES*)dispatch)->setNSFPlay(eng->getConfInt("nesCore",0)==1); break; case DIV_SYSTEM_C64_6581: dispatch=new DivPlatformC64; diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index ef3abed55..9ff3660b6 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -507,148 +507,12 @@ void DivEngine::renderSamples() { song.sample[i]->render(); } - // step 2: allocate ADPCM-A samples - if (adpcmAMem==NULL) adpcmAMem=new unsigned char[16777216]; - - size_t memPos=0; - for (int i=0; ilengthA+255)&(~0xff); - if ((memPos&0xf00000)!=((memPos+paddedLen)&0xf00000)) { - memPos=(memPos+0xfffff)&0xf00000; + // step 2: render samples to dispatch + for (int i=0; irenderSamples(); } - 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; ilengthB+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; ilength8; - 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; idata8[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; ilength8+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; ilength16; - // 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& desc) { @@ -756,11 +620,11 @@ void DivEngine::createNew(const int* description) { initSongWithDesc(description); } recalcChans(); - renderSamples(); saveLock.unlock(); BUSY_END; initDispatch(); BUSY_BEGIN; + renderSamples(); reset(); BUSY_END; } @@ -998,6 +862,11 @@ DivSample* DivEngine::getSample(int 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) { remainingLoops=loops; } @@ -2511,6 +2380,7 @@ bool DivEngine::switchMaster() { } else { return false; } + renderSamples(); return true; } @@ -2808,9 +2678,9 @@ bool DivEngine::init() { // set default system preset if (!hasLoadedSomething) { - logI("setting"); + logD("setting default preset"); std::vector preset=decodeSysDesc(getConfString("initialSys","")); - logI("preset size %ld",preset.size()); + logD("preset size %ld",preset.size()); if (preset.size()>0 && (preset.size()&3)==0) { preset.push_back(0); initSongWithDesc(preset.data()); @@ -2856,6 +2726,7 @@ bool DivEngine::init() { oscBuf[1]=new float[32768]; initDispatch(); + renderSamples(); reset(); active=true; diff --git a/src/engine/engine.h b/src/engine/engine.h index 85e198b44..4b2467be8 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -424,6 +424,7 @@ class DivEngine { DivInstrument* getIns(int index, DivInstrumentType fallbackType=DIV_INS_FM); DivWavetable* getWave(int index); DivSample* getSample(int index); + DivDispatch* getDispatch(int index); // parse system setup description String encodeSysDesc(std::vector& desc); std::vector decodeSysDesc(String desc); @@ -851,21 +852,6 @@ class DivEngine { // terminate the engine. 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(): output(NULL), exportThread(NULL), @@ -939,21 +925,7 @@ class DivEngine { oscSize(1), oscReadPos(0), oscWritePos(0), - 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) { + tickMult(1) { memset(isMuted,0,DIV_MAX_CHANS*sizeof(bool)); memset(keyHit,0,DIV_MAX_CHANS*sizeof(bool)); memset(dispatchChanOfChan,0,DIV_MAX_CHANS*sizeof(int)); diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 33975f41e..12079e05e 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -899,12 +899,14 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { song.unload(); song=ds; recalcChans(); - renderSamples(); saveLock.unlock(); BUSY_END; if (active) { initDispatch(); - syncReset(); + BUSY_BEGIN; + renderSamples(); + reset(); + BUSY_END; } } catch (EndOfFileException& e) { logE("premature end of file!"); @@ -1603,12 +1605,14 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { song.unload(); song=ds; recalcChans(); - renderSamples(); saveLock.unlock(); BUSY_END; if (active) { initDispatch(); - syncReset(); + BUSY_BEGIN; + renderSamples(); + reset(); + BUSY_END; } } catch (EndOfFileException& e) { logE("premature end of file!"); @@ -2018,12 +2022,14 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { song.unload(); song=ds; recalcChans(); - renderSamples(); saveLock.unlock(); BUSY_END; if (active) { initDispatch(); - syncReset(); + BUSY_BEGIN; + renderSamples(); + reset(); + BUSY_END; } success=true; } catch (EndOfFileException& e) { diff --git a/src/engine/platform/abstract.cpp b/src/engine/platform/abstract.cpp index 91f61fc62..54366e1ba 100644 --- a/src/engine/platform/abstract.cpp +++ b/src/engine/platform/abstract.cpp @@ -141,6 +141,22 @@ const char** DivDispatch::getRegisterSheet() { 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) { return 0; } diff --git a/src/engine/platform/es5506.cpp b/src/engine/platform/es5506.cpp index 5b84c14cb..0bace273a 100644 --- a/src/engine/platform/es5506.cpp +++ b/src/engine/platform/es5506.cpp @@ -135,6 +135,18 @@ const char* DivPlatformES5506::getEffectName(unsigned char effect) { case 0x27: return "27xx: Set envelope k2 ramp (signed, slower)"; 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: if ((effect&0xf0)==0x30) { return "3xxx: Set filter K1"; @@ -254,6 +266,7 @@ void DivPlatformES5506::tick(bool sysTick) { for (int i=0; i<=chanMax; i++) { chan[i].std.next(); DivInstrument* ins=parent->getIns(chan[i].ins); + signed int k1=chan[i].k1Prev,k2=chan[i].k2Prev; // volume/panning macros if (chan[i].std.vol.had) { 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; } } + // 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 if (chan[i].volChanged.changed) { if (!isMuted[i]) { // calculate volume (16 bit) @@ -470,16 +500,16 @@ void DivPlatformES5506::tick(bool sysTick) { } if (chan[i].filterChanged.k2) { 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 { - pageWrite(0x00|i,0x07,chan[i].filter.k2); + k2=chan[i].filter.k2; } } if (chan[i].filterChanged.k1) { 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 { - 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].keyOn) { if (chan[i].pcm.index>=0) { + chan[i].k1Prev=0xffff; + chan[i].k2Prev=0xffff; pageWriteMask(0x00|i,0x5f,0x00,0x0303); // Wipeout CR 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 @@ -527,15 +559,19 @@ void DivPlatformES5506::tick(bool sysTick) { // initialize filter 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)) { - 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 { - 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)) { - 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 { - 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,0x04,chan[i].resRVol); 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; 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].filterChanged.k2=1; 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 case DIV_CMD_ES5506_ENVELOPE_COUNT: chan[c.chan].envelope.ecount=c.value&0x1ff; @@ -896,8 +948,51 @@ unsigned char* DivPlatformES5506::getRegisterPool() { int DivPlatformES5506::getRegisterPoolSize() { 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; isong.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) { + sampleMem=new signed short[getSampleMemCapacity()/sizeof(short)]; + sampleMemLen=0; parent=p; dumpWrites=false; skipRegisterWrites=false; @@ -916,6 +1011,7 @@ int DivPlatformES5506::init(DivEngine* p, int channels, int sugRate, unsigned in } void DivPlatformES5506::quit() { + delete[] sampleMem; for (int i=0; i<32; i++) { delete oscBuf[i]; } diff --git a/src/engine/platform/es5506.h b/src/engine/platform/es5506.h index d57a8234d..20b139ee7 100644 --- a/src/engine/platform/es5506.h +++ b/src/engine/platform/es5506.h @@ -105,6 +105,8 @@ class DivPlatformES5506: public DivDispatch, public es550x_intf { } envChanged; signed int k1Offs, k2Offs; + signed int k1Slide, k2Slide; + signed int k1Prev, k2Prev; unsigned int vol, lVol, rVol; unsigned int outVol, outLVol, outRVol; unsigned int resLVol, resRVol; @@ -121,6 +123,8 @@ class DivPlatformES5506: public DivDispatch, public es550x_intf { if (std.ex1.mode==2) { k2Offs=0; } + k1Prev=0xffff; + k2Prev=0xffff; } Channel(): freq(0), @@ -142,6 +146,10 @@ class DivPlatformES5506: public DivDispatch, public es550x_intf { isReverseLoop(false), k1Offs(0), k2Offs(0), + k1Slide(0), + k2Slide(0), + k1Prev(0xffff), + k2Prev(0xffff), vol(0xff), lVol(0xff), rVol(0xff), @@ -157,6 +165,8 @@ class DivPlatformES5506: public DivDispatch, public es550x_intf { Channel chan[32]; DivDispatchOscBuffer* oscBuf[32]; bool isMuted[32]; + signed short* sampleMem; // ES5506 uses 16 bit data bus for samples + size_t sampleMemLen; struct QueuedHostIntf { unsigned char step; unsigned char addr; @@ -203,8 +213,8 @@ class DivPlatformES5506: public DivDispatch, public es550x_intf { virtual void irqb(bool state) override; // IRQB output virtual s16 read_sample(u8 voice, u8 bank, u32 address) override { - if (parent->es5506Mem==NULL) return 0; - return parent->es5506Mem[((bank&3)<<21)|(address&0x1fffff)]; + if (sampleMem==NULL) return 0; + return sampleMem[((bank&3)<<21)|(address&0x1fffff)]; } 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 poke(unsigned int addr, unsigned short val) override; virtual void poke(std::vector& 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* getEffectName(unsigned char effect) override; virtual int init(DivEngine* parent, int channels, int sugRate, unsigned int flags) override; diff --git a/src/engine/platform/nes.cpp b/src/engine/platform/nes.cpp index 027d713f5..283c1c827 100644 --- a/src/engine/platform/nes.cpp +++ b/src/engine/platform/nes.cpp @@ -20,14 +20,15 @@ #include "nes.h" #include "sound/nes/cpu_inline.h" #include "../engine.h" -#include +#include "../../ta-log.h" +#include #include struct _nla_table nla_table; #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[]={ "S0Volume", "4000", @@ -53,6 +54,10 @@ const char* regCheatSheetNES[]={ NULL }; +unsigned char _readDMC(void* user, unsigned short addr) { + return ((DivPlatformNES*)user)->readDMC(addr); +} + const char** DivPlatformNES::getRegisterSheet() { return regCheatSheetNES; } @@ -71,41 +76,56 @@ const char* DivPlatformNES::getEffectName(unsigned char effect) { case 0x14: return "14xy: Sweep down (x: time; y: shift)"; break; + case 0x18: + return "18xx: Select PCM/DPCM mode (0: PCM; 1: DPCM)"; + break; } 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 && dacAntiClickloopMode!=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=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 && dacAntiClickloopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT) && dacPos>=s->loopEnd) || (dacPos>=s->samples)) { - if (s->isLoopable()) { - dacPos=s->loopStart; - } else { - dacSample=-1; - } - } - dacPeriod-=rate; - } else { - dacSample=-1; - } - } - } + doPCM; apu_tick(nes,NULL); 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; iTick(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]={ 6, 6, 6, 6, 6, 6, 6, 6, 6, 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 }; +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) { for (int i=0; i<4; i++) { chan[i].std.next(); @@ -279,6 +353,9 @@ void DivPlatformNES::tick(bool sysTick) { off=(double)s->centerRate/8363.0; } dacRate=MIN(chan[4].freq*off,32000); + if (dpcmMode && !skipRegisterWrites) { + rWrite(0x4010,calcDPCMRate(dacRate)); + } if (dumpWrites) addWrite(0xffff0001,dacRate); } chan[4].freqChanged=false; @@ -294,10 +371,10 @@ int DivPlatformNES::dispatch(DivCommand c) { dacSample=ins->amiga.initSample; if (dacSample<0 || dacSample>=parent->song.sampleLen) { dacSample=-1; - if (dumpWrites) addWrite(0xffff0002,0); + if (dumpWrites && !dpcmMode) addWrite(0xffff0002,0); break; } else { - if (dumpWrites) addWrite(0xffff0000,dacSample); + if (dumpWrites && !dpcmMode) addWrite(0xffff0000,dacSample); } dacPos=0; dacPeriod=0; @@ -309,6 +386,18 @@ int DivPlatformNES::dispatch(DivCommand c) { chan[c.chan].active=true; chan[c.chan].keyOn=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 { if (c.value!=DIV_NOTE_NULL) { chan[c.chan].note=c.value; @@ -316,16 +405,28 @@ int DivPlatformNES::dispatch(DivCommand c) { dacSample=12*sampleBank+chan[c.chan].note%12; if (dacSample>=parent->song.sampleLen) { dacSample=-1; - if (dumpWrites) addWrite(0xffff0002,0); + if (dumpWrites && !dpcmMode) addWrite(0xffff0002,0); break; } else { - if (dumpWrites) addWrite(0xffff0000,dacSample); + if (dumpWrites && !dpcmMode) addWrite(0xffff0000,dacSample); } dacPos=0; dacPeriod=0; dacRate=parent->getSample(dacSample)->rate; - if (dumpWrites) addWrite(0xffff0001,dacRate); + if (dumpWrites && !dpcmMode) addWrite(0xffff0001,dacRate); 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; } else if (c.chan==3) { // noise @@ -354,6 +455,7 @@ int DivPlatformNES::dispatch(DivCommand c) { if (c.chan==4) { dacSample=-1; if (dumpWrites) addWrite(0xffff0002,0); + if (dpcmMode && !skipRegisterWrites) rWrite(0x4015,15); } chan[c.chan].active=false; chan[c.chan].keyOff=true; @@ -435,6 +537,16 @@ int DivPlatformNES::dispatch(DivCommand c) { case DIV_CMD_NES_DMC: rWrite(0x4011,c.value&0x7f); 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: sampleBank=c.value; if (sampleBank>(parent->song.sample.size()/12)) { @@ -467,7 +579,12 @@ int DivPlatformNES::dispatch(DivCommand c) { void DivPlatformNES::muteChannel(int ch, bool mute) { isMuted[ch]=mute; - nes->muted[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; + } } void DivPlatformNES::forceIns() { @@ -513,11 +630,20 @@ void DivPlatformNES::reset() { dacRate=0; dacSample=-1; sampleBank=0; + dpcmBank=0; + dpcmMode=false; - apu_turn_on(nes,apuType); + 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); + nes->apu.cpu_cycles=0; + nes->apu.cpu_opcode_cycle=0; + } memset(regPool,0,128); - nes->apu.cpu_cycles=0; - nes->apu.cpu_opcode_cycle=0; rWrite(0x4015,0x1f); rWrite(0x4001,chan[0].sweep); @@ -535,14 +661,20 @@ void DivPlatformNES::setFlags(unsigned int flags) { if (flags==2) { // Dendy rate=COLOR_PAL*2.0/5.0; apuType=2; - nes->apu.type=apuType; } else if (flags==1) { // PAL rate=COLOR_PAL*3.0/8.0; apuType=1; - nes->apu.type=apuType; } else { // NTSC rate=COLOR_NTSC/2.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; } chipClock=rate; @@ -565,20 +697,87 @@ void DivPlatformNES::poke(std::vector& wlist) { 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; isong.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) { parent=p; apuType=flags; dumpWrites=false; skipRegisterWrites=false; - nes=new struct NESAPU; + 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->readDMC=_readDMC; + nes->readDMCUser=this; + } writeOscBuf=0; for (int i=0; i<5; i++) { isMuted[i]=false; - nes->muted[i]=false; + if (!useNP) nes->muted[i]=false; oscBuf[i]=new DivDispatchOscBuffer; } setFlags(flags); + dpcmMem=new unsigned char[262144]; + dpcmMemLen=0; + dpcmBank=0; + init_nla_table(500,500); reset(); return 5; @@ -588,7 +787,12 @@ void DivPlatformNES::quit() { for (int i=0; i<5; i++) { delete oscBuf[i]; } - delete nes; + if (useNP) { + delete nes1_NP; + delete nes2_NP; + } else { + delete nes; + } } DivPlatformNES::~DivPlatformNES() { diff --git a/src/engine/platform/nes.h b/src/engine/platform/nes.h index abd23da99..a03efc7a3 100644 --- a/src/engine/platform/nes.h +++ b/src/engine/platform/nes.h @@ -23,6 +23,8 @@ #include "../dispatch.h" #include "../macroInt.h" +#include "sound/nes_nsfplay/nes_apu.h" + class DivPlatformNES: public DivDispatch { struct Channel { int freq, baseFreq, pitch, pitch2, prevFreq, note, ins; @@ -62,15 +64,27 @@ class DivPlatformNES: public DivDispatch { int dacPeriod, dacRate; unsigned int dacPos, dacAntiClick; int dacSample; + unsigned char* dpcmMem; + size_t dpcmMemLen; + unsigned char dpcmBank; unsigned char sampleBank; unsigned char writeOscBuf; unsigned char apuType; + bool dpcmMode; bool dacAntiClickOn; + bool useNP; struct NESAPU* nes; + xgm::NES_APU* nes1_NP; + xgm::NES_DMC* nes2_NP; unsigned char regPool[128]; 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: void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); @@ -84,12 +98,18 @@ class DivPlatformNES: public DivDispatch { void muteChannel(int ch, bool mute); bool keyOffAffectsArp(int ch); float getPostAmp(); + unsigned char readDMC(unsigned short addr); + void setNSFPlay(bool use); void setFlags(unsigned int flags); void notifyInsDeletion(void* ins); void poke(unsigned int addr, unsigned short val); void poke(std::vector& wlist); const char** getRegisterSheet(); 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); void quit(); ~DivPlatformNES(); diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index 33c59eeb2..dfe83d364 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -705,7 +705,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { if (c.value==0 && c.value2==0) { chan[c.chan].pan=3; } 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; if (isMuted[c.chan]) { diff --git a/src/engine/platform/qsound.cpp b/src/engine/platform/qsound.cpp index 627bb2121..f3ab0e811 100644 --- a/src/engine/platform/qsound.cpp +++ b/src/engine/platform/qsound.cpp @@ -268,8 +268,6 @@ const char* DivPlatformQSound::getEffectName(unsigned char effect) { return NULL; } 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; hsong.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; idata8[i]; + } + logW("out of QSound PCM memory for sample %d!",i); + } else { + for (int i=0; idata8[i]; + } + } + s->offQSound=memPos^0x8000; + memPos+=length+16; + } + sampleMemLen=memPos+256; +} + int DivPlatformQSound::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { parent=p; dumpWrites=false; @@ -651,8 +694,10 @@ int DivPlatformQSound::init(DivEngine* p, int channels, int sugRate, unsigned in chipClock=60000000; rate = qsound_start(&chip, chipClock); - chip.rom_data = (unsigned char*)&chip.rom_mask; - chip.rom_mask = 0; + sampleMem=new unsigned char[getSampleMemCapacity()]; + sampleMemLen=0; + chip.rom_data=sampleMem; + chip.rom_mask=0xffffff; reset(); 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() { + delete[] sampleMem; for (int i=0; i<19; i++) { delete oscBuf[i]; } diff --git a/src/engine/platform/qsound.h b/src/engine/platform/qsound.h index 0fbce3b32..d12e952ef 100644 --- a/src/engine/platform/qsound.h +++ b/src/engine/platform/qsound.h @@ -67,6 +67,8 @@ class DivPlatformQSound: public DivDispatch { int echoDelay; int echoFeedback; + unsigned char* sampleMem; + size_t sampleMemLen; struct qsound_chip chip; unsigned short regPool[512]; @@ -94,6 +96,10 @@ class DivPlatformQSound: public DivDispatch { void poke(std::vector& wlist); const char** getRegisterSheet(); 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); void quit(); }; diff --git a/src/engine/platform/sound/nes/apu.h b/src/engine/platform/sound/nes/apu.h index 224ea2a04..d0f9c31f9 100644 --- a/src/engine/platform/sound/nes/apu.h +++ b/src/engine/platform/sound/nes/apu.h @@ -207,7 +207,7 @@ enum apu_mode { APU_60HZ, APU_48HZ }; break;\ }\ {\ - a->DMC.buffer = 0;\ + a->DMC.buffer = a->readDMC(a->readDMCUser,a->DMC.address);\ }\ /* incremento gli hwtick da compiere */\ if (hwtick) { hwtick[0] += tick; }\ @@ -525,6 +525,8 @@ EXTERNC struct NESAPU { _apuTriangle TR; _apuNoise NS; _apuDMC DMC; + void* readDMCUser; + unsigned char (*readDMC)(void*,unsigned short); unsigned char muted[5]; }; diff --git a/src/engine/platform/sound/nes_nsfplay/common.h b/src/engine/platform/sound/nes_nsfplay/common.h new file mode 100644 index 000000000..894a69737 --- /dev/null +++ b/src/engine/platform/sound/nes_nsfplay/common.h @@ -0,0 +1,4 @@ +namespace xgm { + const unsigned int DEFAULT_CLOCK=1789773; + const unsigned int DEFAULT_RATE=1789773; +}; diff --git a/src/engine/platform/sound/nes_nsfplay/nes_apu.cpp b/src/engine/platform/sound/nes_nsfplay/nes_apu.cpp new file mode 100644 index 000000000..f215a9aa6 --- /dev/null +++ b/src/engine/platform/sound/nes_nsfplay/nes_apu.cpp @@ -0,0 +1,380 @@ +// +// NES 2A03 +// +#include +#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 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; diff --git a/src/engine/platform/sound/nes_nsfplay/nes_apu.h b/src/engine/platform/sound/nes_nsfplay/nes_apu.h new file mode 100644 index 000000000..20c4d3663 --- /dev/null +++ b/src/engine/platform/sound/nes_nsfplay/nes_apu.h @@ -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 diff --git a/src/engine/platform/sound/nes_nsfplay/nes_dmc.cpp b/src/engine/platform/sound/nes_nsfplay/nes_dmc.cpp new file mode 100644 index 000000000..a37b544d2 --- /dev/null +++ b/src/engine/platform/sound/nes_nsfplay/nes_dmc.cpp @@ -0,0 +1,717 @@ +#include "nes_dmc.h" +#include "nes_apu.h" +#include "common.h" +#include +#include + +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 r) + { + memory = r; + } + + void NES_DMC::SetOption (int id, int val) + { + if(id> 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 diff --git a/src/engine/platform/sound/nes_nsfplay/nes_dmc.h b/src/engine/platform/sound/nes_nsfplay/nes_dmc.h new file mode 100644 index 000000000..f78dd9f4a --- /dev/null +++ b/src/engine/platform/sound/nes_nsfplay/nes_dmc.h @@ -0,0 +1,119 @@ +#ifndef _NES_DMC_H_ +#define _NES_DMC_H_ +#include + +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 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 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 diff --git a/src/engine/platform/sound/nes_nsfplay/nes_fds.cpp b/src/engine/platform/sound/nes_nsfplay/nes_fds.cpp new file mode 100644 index 000000000..f3e61decc --- /dev/null +++ b/src/engine/platform/sound/nes_nsfplay/nes_fds.cpp @@ -0,0 +1,385 @@ +#include +#include +#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< 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<= 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 diff --git a/src/engine/platform/sound/nes_nsfplay/nes_fds.h b/src/engine/platform/sound/nes_nsfplay/nes_fds.h new file mode 100644 index 000000000..9185e4da6 --- /dev/null +++ b/src/engine/platform/sound/nes_nsfplay/nes_fds.h @@ -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 diff --git a/src/engine/platform/sound/nes_nsfplay/nes_mmc5.cpp b/src/engine/platform/sound/nes_nsfplay/nes_mmc5.cpp new file mode 100644 index 000000000..500be87ca --- /dev/null +++ b/src/engine/platform/sound/nes_nsfplay/nes_mmc5.cpp @@ -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(idclock = 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 diff --git a/src/engine/platform/sound/nes_nsfplay/nes_mmc5.h b/src/engine/platform/sound/nes_nsfplay/nes_mmc5.h new file mode 100644 index 000000000..19d5ab269 --- /dev/null +++ b/src/engine/platform/sound/nes_nsfplay/nes_mmc5.h @@ -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 diff --git a/src/engine/platform/sound/nes_nsfplay/nes_n106.cpp b/src/engine/platform/sound/nes_nsfplay/nes_n106.cpp new file mode 100644 index 000000000..25e80fb47 --- /dev/null +++ b/src/engine/platform/sound/nes_nsfplay/nes_n106.cpp @@ -0,0 +1,332 @@ +#include +#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 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 diff --git a/src/engine/platform/sound/nes_nsfplay/nes_n106.h b/src/engine/platform/sound/nes_nsfplay/nes_n106.h new file mode 100644 index 000000000..a59dd6687 --- /dev/null +++ b/src/engine/platform/sound/nes_nsfplay/nes_n106.h @@ -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 diff --git a/src/engine/platform/sound/nes_nsfplay/nes_vrc6.cpp b/src/engine/platform/sound/nes_nsfplay/nes_vrc6.cpp new file mode 100644 index 000000000..754d44cf7 --- /dev/null +++ b/src/engine/platform/sound/nes_nsfplay/nes_vrc6.cpp @@ -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 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 diff --git a/src/engine/platform/sound/nes_nsfplay/nes_vrc6.h b/src/engine/platform/sound/nes_nsfplay/nes_vrc6.h new file mode 100644 index 000000000..198a8bd66 --- /dev/null +++ b/src/engine/platform/sound/nes_nsfplay/nes_vrc6.h @@ -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 diff --git a/src/engine/platform/sound/nes_nsfplay/readme.txt b/src/engine/platform/sound/nes_nsfplay/readme.txt new file mode 100644 index 000000000..f3a625d90 --- /dev/null +++ b/src/engine/platform/sound/nes_nsfplay/readme.txt @@ -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/ diff --git a/src/engine/platform/sound/ymfm/ymfm_opz.cpp b/src/engine/platform/sound/ymfm/ymfm_opz.cpp index 62a3d4d9c..cc7a7c9d5 100644 --- a/src/engine/platform/sound/ymfm/ymfm_opz.cpp +++ b/src/engine/platform/sound/ymfm/ymfm_opz.cpp @@ -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 // example, a frequency of 8.0009Hz when 8Hz is requested uint32_t substep = m_phase_substep[opoffs]; - substep += 75 * freq; + substep += 75 * 1024 * freq; phase_step = substep >> 12; m_phase_substep[opoffs] = substep & 0xfff; diff --git a/src/engine/platform/sound/ymfm/ymfm_opz.h b/src/engine/platform/sound/ymfm/ymfm_opz.h index 4bc4663a0..5be148a38 100644 --- a/src/engine/platform/sound/ymfm/ymfm_opz.h +++ b/src/engine/platform/sound/ymfm/ymfm_opz.h @@ -320,6 +320,9 @@ public: // generate one sample of sound void generate(output_data *output, uint32_t numsamples = 1); + // get the engine + fm_engine* debug_engine() { return &m_fm; } + protected: // internal state uint8_t m_address; // address register diff --git a/src/engine/platform/tx81z.cpp b/src/engine/platform/tx81z.cpp index d69dfc875..788cfe245 100644 --- a/src/engine/platform/tx81z.cpp +++ b/src/engine/platform/tx81z.cpp @@ -147,6 +147,8 @@ const char* DivPlatformTX81Z::getEffectName(unsigned char effect) { void DivPlatformTX81Z::acquire(short* bufL, short* bufR, size_t start, size_t len) { static int os[2]; + ymfm::ym2414::fm_engine* fme=fm_ymfm->debug_engine(); + for (size_t h=start; hgenerate(&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]; if (os[0]<-32768) os[0]=-32768; if (os[0]>32767) os[0]=32767; @@ -192,10 +198,14 @@ void DivPlatformTX81Z::tick(bool sysTick) { for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[i]|opOffs[j]; DivInstrumentFM::Operator& op=chan[i].state.op[j]; - if (isOutput[chan[i].state.alg][j]) { - rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127)); + if (isMuted[i]) { + rWrite(baseAddr+ADDR_TL,127); } else { - rWrite(baseAddr+ADDR_TL,op.tl); + if (isOutput[chan[i].state.alg][j]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } } } } @@ -260,11 +270,7 @@ void DivPlatformTX81Z::tick(bool sysTick) { if (chan[i].std.alg.had) { 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++) { unsigned short baseAddr=chanOffs[i]|opOffs[j]; DivInstrumentFM::Operator& op=chan[i].state.op[j]; @@ -281,11 +287,7 @@ void DivPlatformTX81Z::tick(bool sysTick) { } if (chan[i].std.fb.had) { 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) { chan[i].state.fms=chan[i].std.fms.val; @@ -313,7 +315,7 @@ void DivPlatformTX81Z::tick(bool sysTick) { } if (m.mult.had) { 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) { op.rr=m.rr.val; @@ -325,10 +327,14 @@ void DivPlatformTX81Z::tick(bool sysTick) { } if (m.tl.had) { op.tl=127-m.tl.val; - if (isOutput[chan[i].state.alg][j]) { - rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127)); + if (isMuted[i]) { + rWrite(baseAddr+ADDR_TL,127); } else { - rWrite(baseAddr+ADDR_TL,op.tl); + if (isOutput[chan[i].state.alg][j]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } } } if (m.rs.had) { @@ -337,7 +343,7 @@ void DivPlatformTX81Z::tick(bool sysTick) { } if (m.dt.had) { 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) { op.d2r=m.d2r.val; @@ -358,12 +364,8 @@ void DivPlatformTX81Z::tick(bool sysTick) { 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); - 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].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)); if (chan[i].hardReset && chan[i].keyOn) { for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[i]|opOffs[j]; @@ -405,12 +407,8 @@ void DivPlatformTX81Z::tick(bool sysTick) { chan[i].freqChanged=false; } 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(chanOffs[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)|0x40|(chan[i].chVolR<<7)); - } + //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)); chan[i].keyOn=false; } } @@ -418,13 +416,19 @@ void DivPlatformTX81Z::tick(bool sysTick) { void DivPlatformTX81Z::muteChannel(int ch, bool mute) { isMuted[ch]=mute; - // TODO: use volume registers! - /* - if (isMuted[ch]) { - immWrite(chanOffs[ch]+ADDR_LR_FB_ALG,(chan[ch].state.alg&7)|(chan[ch].state.fb<<3)); - } 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)); - }*/ + 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]) { + rWrite(baseAddr+ADDR_TL,127); + } else { + 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) { @@ -444,17 +448,21 @@ int DivPlatformTX81Z::dispatch(DivCommand c) { for (int i=0; i<4; i++) { unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; DivInstrumentFM::Operator op=chan[c.chan].state.op[i]; - if (isOutput[chan[c.chan].state.alg][i]) { - if (!chan[c.chan].active || chan[c.chan].insChanged) { - rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127)); - } + if (isMuted[c.chan]) { + rWrite(baseAddr+ADDR_TL,127); } else { - if (chan[c.chan].insChanged) { - rWrite(baseAddr+ADDR_TL,op.tl); + if (isOutput[chan[c.chan].state.alg][i]) { + if (!chan[c.chan].active || chan[c.chan].insChanged) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127)); + } + } else { + if (chan[c.chan].insChanged) { + rWrite(baseAddr+ADDR_TL,op.tl); + } } } 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_AM_DR,(op.dr&31)|(op.am<<7)); rWrite(baseAddr+ADDR_DT2_D2R,(op.d2r&31)|(op.dt2<<6)); @@ -506,10 +514,14 @@ int DivPlatformTX81Z::dispatch(DivCommand c) { for (int i=0; i<4; i++) { unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; - if (isOutput[chan[c.chan].state.alg][i]) { - rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127)); + if (isMuted[c.chan]) { + rWrite(baseAddr+ADDR_TL,127); } else { - rWrite(baseAddr+ADDR_TL,op.tl); + if (isOutput[chan[c.chan].state.alg][i]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } } } break; @@ -593,17 +605,21 @@ int DivPlatformTX81Z::dispatch(DivCommand c) { unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; 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; } case DIV_CMD_FM_TL: { unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; op.tl=c.value2; - if (isOutput[chan[c.chan].state.alg][c.value]) { - rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127)); + if (isMuted[c.chan]) { + rWrite(baseAddr+ADDR_TL,127); } else { - rWrite(baseAddr+ADDR_TL,op.tl); + if (isOutput[chan[c.chan].state.alg][c.value]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } } break; } @@ -672,12 +688,16 @@ void DivPlatformTX81Z::forceIns() { for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[i]|opOffs[j]; DivInstrumentFM::Operator op=chan[i].state.op[j]; - if (isOutput[chan[i].state.alg][j]) { - rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127)); + if (isMuted[i]) { + rWrite(baseAddr+ADDR_TL,127); } else { - rWrite(baseAddr+ADDR_TL,op.tl); + if (isOutput[chan[i].state.alg][j]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127)); + } else { + 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_AM_DR,(op.dr&31)|(op.am<<7)); rWrite(baseAddr+ADDR_DT2_D2R,(op.d2r&31)|(op.dt2<<6)); @@ -714,6 +734,10 @@ void* DivPlatformTX81Z::getChanState(int ch) { return &chan[ch]; } +DivDispatchOscBuffer* DivPlatformTX81Z::getOscBuffer(int ch) { + return oscBuf[ch]; +} + unsigned char* DivPlatformTX81Z::getRegisterPool() { return regPool; } @@ -777,6 +801,9 @@ void DivPlatformTX81Z::setFlags(unsigned int flags) { baseFreqOff=0; } rate=chipClock/64; + for (int i=0; i<8; i++) { + oscBuf[i]->rate=rate; + } } bool DivPlatformTX81Z::isStereo() { @@ -789,6 +816,7 @@ int DivPlatformTX81Z::init(DivEngine* p, int channels, int sugRate, unsigned int skipRegisterWrites=false; for (int i=0; i<8; i++) { isMuted[i]=false; + oscBuf[i]=new DivDispatchOscBuffer; } setFlags(flags); fm_ymfm=new ymfm::ym2414(iface); @@ -798,6 +826,9 @@ int DivPlatformTX81Z::init(DivEngine* p, int channels, int sugRate, unsigned int } void DivPlatformTX81Z::quit() { + for (int i=0; i<8; i++) { + delete oscBuf[i]; + } delete fm_ymfm; } diff --git a/src/engine/platform/tx81z.h b/src/engine/platform/tx81z.h index 5df591f64..60ea66ae0 100644 --- a/src/engine/platform/tx81z.h +++ b/src/engine/platform/tx81z.h @@ -68,6 +68,7 @@ class DivPlatformTX81Z: public DivDispatch { chVolR(127) {} }; Channel chan[8]; + DivDispatchOscBuffer* oscBuf[8]; struct QueuedWrite { unsigned short addr; unsigned char val; @@ -102,6 +103,7 @@ class DivPlatformTX81Z: public DivDispatch { void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); void* getChanState(int chan); + DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); void reset(); diff --git a/src/engine/platform/x1_010.cpp b/src/engine/platform/x1_010.cpp index a2e28b518..03cd4f6be 100644 --- a/src/engine/platform/x1_010.cpp +++ b/src/engine/platform/x1_010.cpp @@ -19,6 +19,7 @@ #include "x1_010.h" #include "../engine.h" +#include "../../ta-log.h" #include //#define rWrite(a,v) pendingWrites[a]=v; @@ -909,6 +910,48 @@ void DivPlatformX1_010::poke(std::vector& wlist) { 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; isong.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) { parent=p; dumpWrites=false; @@ -919,7 +962,9 @@ int DivPlatformX1_010::init(DivEngine* p, int channels, int sugRate, unsigned in oscBuf[i]=new DivDispatchOscBuffer; } setFlags(flags); - intf.parent=parent; + sampleMem=new unsigned char[getSampleMemCapacity()]; + sampleMemLen=0; + intf.memory=sampleMem; x1_010=new x1_010_core(intf); x1_010->reset(); reset(); @@ -931,6 +976,7 @@ void DivPlatformX1_010::quit() { delete oscBuf[i]; } delete x1_010; + delete[] sampleMem; } DivPlatformX1_010::~DivPlatformX1_010() { diff --git a/src/engine/platform/x1_010.h b/src/engine/platform/x1_010.h index 11de4bf4a..939280ef9 100644 --- a/src/engine/platform/x1_010.h +++ b/src/engine/platform/x1_010.h @@ -28,13 +28,13 @@ class DivX1_010Interface: public x1_010_mem_intf { public: - DivEngine* parent; + unsigned char* memory; int sampleBank; virtual u8 read_byte(u32 address) override { - if (parent->x1_010Mem==NULL) return 0; - return parent->x1_010Mem[address & 0xfffff]; + if (memory==NULL) return 0; + return memory[address & 0xfffff]; } - DivX1_010Interface(): parent(NULL), sampleBank(0) {} + DivX1_010Interface(): memory(NULL), sampleBank(0) {} }; class DivPlatformX1_010: public DivDispatch { @@ -115,6 +115,8 @@ class DivPlatformX1_010: public DivDispatch { DivDispatchOscBuffer* oscBuf[16]; bool isMuted[16]; bool stereo=false; + unsigned char* sampleMem; + size_t sampleMemLen; unsigned char sampleBank; DivX1_010Interface intf; x1_010_core* x1_010; @@ -141,6 +143,10 @@ class DivPlatformX1_010: public DivDispatch { void notifyInsDeletion(void* ins); void poke(unsigned int addr, unsigned short val); void poke(std::vector& 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* getEffectName(unsigned char effect); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); diff --git a/src/engine/platform/ym2610.cpp b/src/engine/platform/ym2610.cpp index 73ccf708e..f3cc8c1e1 100644 --- a/src/engine/platform/ym2610.cpp +++ b/src/engine/platform/ym2610.cpp @@ -20,6 +20,7 @@ #include "ym2610.h" #include "sound/ymfm/ymfm.h" #include "../engine.h" +#include "../../ta-log.h" #include #include @@ -245,6 +246,85 @@ const char* regCheatSheetYM2610[]={ 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; isong.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; isong.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() { return regCheatSheetYM2610; } @@ -1185,7 +1265,7 @@ void DivPlatformYM2610::setSkipRegisterWrites(bool value) { } int DivPlatformYM2610::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { - parent=p; + DivPlatformYM2610Base::init(p, channels, sugRate, flags); dumpWrites=false; skipRegisterWrites=false; 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++) { oscBuf[i]->rate=rate; } - iface.parent=parent; - iface.sampleBank=0; fm=new ymfm::ym2610(iface); // YM2149, 2MHz ay=new DivPlatformAY8910; @@ -1215,6 +1293,7 @@ void DivPlatformYM2610::quit() { ay->quit(); delete ay; delete fm; + DivPlatformYM2610Base::quit(); } DivPlatformYM2610::~DivPlatformYM2610() { diff --git a/src/engine/platform/ym2610.h b/src/engine/platform/ym2610.h index c46bc3c43..45ecb335a 100644 --- a/src/engine/platform/ym2610.h +++ b/src/engine/platform/ym2610.h @@ -27,14 +27,32 @@ class DivYM2610Interface: public ymfm::ymfm_interface { public: - DivEngine* parent; + unsigned char* adpcmAMem; + unsigned char* adpcmBMem; int sampleBank; 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); - 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: const unsigned short chanOffs[4]={ 0x01, 0x02, 0x101, 0x102 @@ -93,7 +111,6 @@ class DivPlatformYM2610: public DivDispatch { std::queue writes; ymfm::ym2610* fm; ymfm::ym2610::output_data fmout; - DivYM2610Interface iface; DivPlatformAY8910* ay; unsigned char regPool[512]; diff --git a/src/engine/platform/ym2610Interface.cpp b/src/engine/platform/ym2610Interface.cpp index 9154b40e7..d442ce347 100644 --- a/src/engine/platform/ym2610Interface.cpp +++ b/src/engine/platform/ym2610Interface.cpp @@ -24,13 +24,11 @@ uint8_t DivYM2610Interface::ymfm_external_read(ymfm::access_class type, uint32_t address) { switch (type) { case ymfm::ACCESS_ADPCM_A: - if (parent->adpcmAMem==NULL) return 0; - if ((address&0xffffff)>=parent->adpcmAMemLen) return 0; - return parent->adpcmAMem[address&0xffffff]; + if (adpcmAMem==NULL) return 0; + return adpcmAMem[address&0xffffff]; case ymfm::ACCESS_ADPCM_B: - if (parent->adpcmBMem==NULL) return 0; - if ((address&0xffffff)>=parent->adpcmBMemLen) return 0; - return parent->adpcmBMem[address&0xffffff]; + if (adpcmBMem==NULL) return 0; + return adpcmBMem[address&0xffffff]; default: return 0; } diff --git a/src/engine/platform/ym2610b.cpp b/src/engine/platform/ym2610b.cpp index ed3eac281..7d3b6a9c8 100644 --- a/src/engine/platform/ym2610b.cpp +++ b/src/engine/platform/ym2610b.cpp @@ -1243,7 +1243,7 @@ void DivPlatformYM2610B::setSkipRegisterWrites(bool value) { } int DivPlatformYM2610B::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { - parent=p; + DivPlatformYM2610Base::init(p, channels, sugRate, flags); dumpWrites=false; skipRegisterWrites=false; 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++) { oscBuf[i]->rate=rate; } - iface.parent=parent; - iface.sampleBank=0; fm=new ymfm::ym2610b(iface); // YM2149, 2MHz ay=new DivPlatformAY8910; @@ -1273,6 +1271,7 @@ void DivPlatformYM2610B::quit() { ay->quit(); delete ay; delete fm; + DivPlatformYM2610Base::quit(); } DivPlatformYM2610B::~DivPlatformYM2610B() { diff --git a/src/engine/platform/ym2610b.h b/src/engine/platform/ym2610b.h index df0a83233..d1af3069a 100644 --- a/src/engine/platform/ym2610b.h +++ b/src/engine/platform/ym2610b.h @@ -26,7 +26,7 @@ #include "ym2610.h" -class DivPlatformYM2610B: public DivDispatch { +class DivPlatformYM2610B: public DivPlatformYM2610Base { protected: const unsigned short chanOffs[6]={ 0x00, 0x01, 0x02, 0x100, 0x101, 0x102 @@ -85,7 +85,6 @@ class DivPlatformYM2610B: public DivDispatch { std::queue writes; ymfm::ym2610b* fm; ymfm::ym2610b::output_data fmout; - DivYM2610Interface iface; unsigned char regPool[512]; unsigned char lastBusy; diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index b23e9c027..eb1ef1058 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -65,9 +65,24 @@ const char* cmdName[]={ "FM_LFO", "FM_LFO_WAVE", "FM_TL", + "FM_AM", "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_MULT", + "FM_FINE", + "FM_FIXFREQ", "FM_EXTCH", "FM_AM_DEPTH", "FM_PM_DEPTH", @@ -156,6 +171,8 @@ const char* cmdName[]={ "ES5506_FILTER_MODE", "ES5506_FILTER_K1", "ES5506_FILTER_K2", + "ES5506_FILTER_K1_SLIDE", + "ES5506_FILTER_K2_SLIDE", "ES5506_ENVELOPE_COUNT", "ES5506_ENVELOPE_LVRAMP", "ES5506_ENVELOPE_RVRAMP", @@ -339,6 +356,9 @@ bool DivEngine::perSystemEffect(int ch, unsigned char effect, unsigned char effe case 0x14: // sweep down dispatchCmd(DivCommand(DIV_CMD_NES_SWEEP,ch,1,effectVal)); break; + case 0x18: // DPCM mode + dispatchCmd(DivCommand(DIV_CMD_SAMPLE_MODE,ch,effectVal)); + break; default: return false; } @@ -557,6 +577,14 @@ bool DivEngine::perSystemEffect(int ch, unsigned char effect, unsigned char effe case 0x27: // envelope K2RAMP dispatchCmd(DivCommand(DIV_CMD_ES5506_ENVELOPE_K2RAMP,ch,effectVal,effect&0x01)); 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: if ((effect&0xf0)==0x30) { 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]); if (runtotal[i]>disCont[i].bbInLen) { - delete disCont[i].bbIn[0]; - delete disCont[i].bbIn[1]; + delete[] disCont[i].bbIn[0]; + delete[] disCont[i].bbIn[1]; disCont[i].bbIn[0]=new short[runtotal[i]+256]; disCont[i].bbIn[1]=new short[runtotal[i]+256]; disCont[i].bbInLen=runtotal[i]+256; diff --git a/src/engine/vgmOps.cpp b/src/engine/vgmOps.cpp index af57325a2..2b2d33621 100644 --- a/src/engine/vgmOps.cpp +++ b/src/engine/vgmOps.cpp @@ -752,11 +752,11 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) { bool writeDACSamples=false; bool writeNESSamples=false; bool writePCESamples=false; - unsigned char writeADPCM=0; - unsigned char writeSegaPCM=0; - unsigned char writeX1010=0; - unsigned char writeQSound=0; - unsigned char writeES5506=0; + DivDispatch* writeADPCM[2]={NULL,NULL}; + int writeSegaPCM=0; + DivDispatch* writeX1010[2]={NULL,NULL}; + DivDispatch* writeQSound[2]={NULL,NULL}; + DivDispatch* writeES5506[2]={NULL,NULL}; for (int i=0; ichipClock; willExport[i]=true; - writeX1010=1; + writeX1010[0]=disCont[i].dispatch; } else if (!(hasX1&0x40000000)) { isSecond[i]=true; willExport[i]=true; - writeX1010=2; + writeX1010[1]=disCont[i].dispatch; hasX1|=0x40000000; howManyChips++; } @@ -863,11 +863,11 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) { if (!hasOPNB) { hasOPNB=disCont[i].dispatch->chipClock; willExport[i]=true; - writeADPCM=1; + writeADPCM[0]=disCont[i].dispatch; } else if (!(hasOPNB&0x40000000)) { isSecond[i]=true; willExport[i]=true; - writeADPCM=2; + writeADPCM[1]=disCont[i].dispatch; hasOPNB|=0x40000000; howManyChips++; } @@ -969,11 +969,11 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) { // not be able to handle the 64kb sample bank trick hasQSound=disCont[i].dispatch->chipClock; willExport[i]=true; - writeQSound=1; + writeQSound[0]=disCont[i].dispatch; } else if (!(hasQSound&0x40000000)) { isSecond[i]=true; willExport[i]=false; - writeQSound=2; + writeQSound[1]=disCont[i].dispatch; addWarning("dual QSound is not supported by the VGM format"); } break; @@ -997,12 +997,12 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) { // VGM identifies ES5506 if highest bit sets, otherwise ES5505 hasES5505=0x80000000|disCont[i].dispatch->chipClock; willExport[i]=true; - writeES5506=1; + writeES5506[0]=disCont[i].dispatch; } else if (!(hasES5505&0x40000000)) { isSecond[i]=true; willExport[i]=false; hasES5505|=0xc0000000; - writeES5506=2; + writeES5506[1]=disCont[i].dispatch; howManyChips++; } break; @@ -1303,68 +1303,68 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) { delete[] pcmMem; } - if (adpcmAMemLen>0) { - for (unsigned char i=0; igetSampleMemUsage(0)>0) { w->writeC(0x67); w->writeC(0x66); w->writeC(0x82); - w->writeI((adpcmAMemLen+8)|(i*0x80000000)); - w->writeI(adpcmAMemLen); + w->writeI((writeADPCM[i]->getSampleMemUsage(0)+8)|(i*0x80000000)); + w->writeI(writeADPCM[i]->getSampleMemCapacity(0)); w->writeI(0); - w->write(adpcmAMem,adpcmAMemLen); + w->write(writeADPCM[i]->getSampleMem(0),writeADPCM[i]->getSampleMemUsage(0)); } } - if (adpcmBMemLen>0) { - for (unsigned char i=0; igetSampleMemUsage(1)>0) { w->writeC(0x67); w->writeC(0x66); w->writeC(0x83); - w->writeI((adpcmBMemLen+8)|(i*0x80000000)); - w->writeI(adpcmBMemLen); + w->writeI((writeADPCM[i]->getSampleMemUsage(1)+8)|(i*0x80000000)); + w->writeI(writeADPCM[i]->getSampleMemCapacity(1)); w->writeI(0); - w->write(adpcmBMem,adpcmBMemLen); + w->write(writeADPCM[i]->getSampleMem(1),writeADPCM[i]->getSampleMemUsage(1)); } } - if (qsoundMemLen>0) { - // always write a whole bank - unsigned int blockSize=(qsoundMemLen+0xffff)&(~0xffff); - if (blockSize > 0x1000000) { - blockSize = 0x1000000; - } - for (unsigned char i=0; igetSampleMemUsage()>0) { + unsigned int blockSize=(writeQSound[i]->getSampleMemUsage()+0xffff)&(~0xffff); + if (blockSize > 0x1000000) { + blockSize = 0x1000000; + } w->writeC(0x67); w->writeC(0x66); w->writeC(0x8F); w->writeI((blockSize+8)|(i*0x80000000)); - w->writeI(0x1000000); + w->writeI(writeQSound[i]->getSampleMemCapacity()); w->writeI(0); - w->write(qsoundMem,blockSize); + w->write(writeQSound[i]->getSampleMem(),blockSize); } } - if (x1_010MemLen>0) { - for (unsigned char i=0; igetSampleMemUsage()>0) { w->writeC(0x67); w->writeC(0x66); w->writeC(0x91); - w->writeI((x1_010MemLen+8)|(i*0x80000000)); - w->writeI(x1_010MemLen); + w->writeI((writeX1010[i]->getSampleMemUsage()+8)|(i*0x80000000)); + w->writeI(writeX1010[i]->getSampleMemCapacity()); w->writeI(0); - w->write(x1_010Mem,x1_010MemLen); + w->write(writeX1010[i]->getSampleMem(),writeX1010[i]->getSampleMemUsage()); } } - if (es5506MemLen>0) { - for (unsigned char i=0; igetSampleMemUsage()>0) { w->writeC(0x67); w->writeC(0x66); w->writeC(0x8F); - w->writeI((es5506MemLen+8)|(i*0x80000000)); - w->writeI(es5506MemLen); + w->writeI((writeES5506[i]->getSampleMemUsage()+8)|(i*0x80000000)); + w->writeI(writeES5506[i]->getSampleMemCapacity()); w->writeI(0); - w->write(es5506Mem,es5506MemLen); + w->write(writeES5506[i]->getSampleMem(),writeES5506[i]->getSampleMemUsage()); } } diff --git a/src/gui/chanOsc.cpp b/src/gui/chanOsc.cpp index f1248ffe9..dc392a884 100644 --- a/src/gui/chanOsc.cpp +++ b/src/gui/chanOsc.cpp @@ -30,15 +30,38 @@ void FurnaceGUI::drawChanOsc() { if (!chanOscOpen) return; ImGui::SetNextWindowSizeConstraints(ImVec2(64.0f*dpiScale,32.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale)); if (ImGui::Begin("Oscilloscope (per-channel)",&chanOscOpen)) { - if (ImGui::InputInt("Columns",&chanOscCols,1,1)) { - if (chanOscCols<1) chanOscCols=1; - if (chanOscCols>64) chanOscCols=64; + 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>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)); float availY=ImGui::GetContentRegionAvail().y; if (ImGui::BeginTable("ChanOsc",chanOscCols,ImGuiTableFlags_Borders)) { std::vector oscBufs; + std::vector oscChans; int chans=e->getTotalChannelCount(); ImDrawList* dl=ImGui::GetWindowDrawList(); ImGuiWindow* window=ImGui::GetCurrentWindow(); @@ -49,7 +72,10 @@ void FurnaceGUI::drawChanOsc() { for (int i=0; igetOscBuffer(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; @@ -58,13 +84,14 @@ void FurnaceGUI::drawChanOsc() { ImGui::TableNextColumn(); DivDispatchOscBuffer* buf=oscBufs[i]; + int ch=oscChans[i]; if (buf==NULL) { ImGui::Text("Error!"); } else { ImVec2 size=ImGui::GetContentRegionAvail(); size.y=availY/rows; - int displaySize=(buf->rate)/30; + int displaySize=(float)(buf->rate)*(chanOscWindowSize/1000.0f); ImVec2 minArea=window->DC.CursorPos; ImVec2 maxArea=ImVec2( @@ -85,7 +112,26 @@ void FurnaceGUI::drawChanOsc() { waveform[i]=ImLerp(inRect.Min,inRect.Max,ImVec2(x,0.5f)); } } 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; idata[needlePos--]; + if (buf->data[needlePos]>old) break; + }*/ + } + needlePos-=displaySize; for (unsigned short i=0; i<512; i++) { float x=(float)i/512.0f; float y=(float)buf->data[(unsigned short)(needlePos+(i*displaySize/512))]/65536.0f; diff --git a/src/gui/debug.cpp b/src/gui/debug.cpp index 79fb1f397..122415a22 100644 --- a/src/gui/debug.cpp +++ b/src/gui/debug.cpp @@ -367,6 +367,10 @@ void putDispatchChan(void* data, int chanNum, int type) { ImGui::Text(" - K2Ramp: %d",ch->envelope.k2Ramp); ImGui::Text(" - K1Offs: %d",ch->k1Offs); 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("- LVol: %.2x",ch->lVol); ImGui::Text("- RVol: %.2x",ch->rVol); diff --git a/src/gui/debugWindow.cpp b/src/gui/debugWindow.cpp index 06184c0ba..c45ffc71d 100644 --- a/src/gui/debugWindow.cpp +++ b/src/gui/debugWindow.cpp @@ -21,6 +21,7 @@ #include "debug.h" #include "IconsFontAwesome4.h" #include +#include void FurnaceGUI::drawDebug() { static int bpOrder; @@ -141,6 +142,51 @@ void FurnaceGUI::drawDebug() { ImGui::Columns(); ImGui::TreePop(); } + if (ImGui::TreeNode("Sample Debug")) { + for (int i=0; isong.sampleLen; i++) { + DivSample* sample=e->getSample(i); + if (sample==NULL) { + ImGui::Text("%d: ",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 (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())) { diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 6d63d0f50..80fad49e2 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -4000,6 +4000,8 @@ FurnaceGUI::FurnaceGUI(): oscZoom(0.5f), oscZoomSlider(false), chanOscCols(3), + chanOscWindowSize(20.0f), + chanOscWaveCorr(true), followLog(true), pianoOctaves(7), pianoOptions(false), @@ -4057,4 +4059,8 @@ FurnaceGUI::FurnaceGUI(): memset(patChanSlideY,0,sizeof(float)*(DIV_MAX_CHANS+1)); memset(lastIns,-1,sizeof(int)*DIV_MAX_CHANS); 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); } diff --git a/src/gui/gui.h b/src/gui/gui.h index cf532eef5..49e56f0c2 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -788,6 +788,7 @@ class FurnaceGUI { int arcadeCore; int ym2612Core; int saaCore; + int nesCore; int mainFont; int patFont; int audioRate; @@ -870,6 +871,7 @@ class FurnaceGUI { arcadeCore(0), ym2612Core(0), saaCore(1), + nesCore(0), mainFont(0), patFont(0), audioRate(44100), @@ -1101,6 +1103,12 @@ class FurnaceGUI { // per-channel oscilloscope 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 float keyHit[DIV_MAX_CHANS]; diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 152f94b8c..383fa4202 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -84,6 +84,11 @@ const char* saaCores[]={ "SAASound" }; +const char* nesCores[]={ + "puNES", + "NSFplay" +}; + const char* valueInputStyles[]={ "Disabled/custom", "Two octaves (0 is C-4, F is D#5)", @@ -859,6 +864,10 @@ void FurnaceGUI::drawSettings() { ImGui::SameLine(); ImGui::Combo("##SAACore",&settings.saaCore,saaCores,2); + ImGui::Text("NES core"); + ImGui::SameLine(); + ImGui::Combo("##NESCore",&settings.nesCore,nesCores,2); + ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Appearance")) { @@ -1731,6 +1740,7 @@ void FurnaceGUI::syncSettings() { settings.arcadeCore=e->getConfInt("arcadeCore",0); settings.ym2612Core=e->getConfInt("ym2612Core",0); settings.saaCore=e->getConfInt("saaCore",1); + settings.nesCore=e->getConfInt("nesCore",0); settings.mainFont=e->getConfInt("mainFont",0); settings.patFont=e->getConfInt("patFont",0); settings.mainFontPath=e->getConfString("mainFontPath",""); @@ -1805,6 +1815,7 @@ void FurnaceGUI::syncSettings() { clampSetting(settings.arcadeCore,0,1); clampSetting(settings.ym2612Core,0,1); clampSetting(settings.saaCore,0,1); + clampSetting(settings.nesCore,0,1); clampSetting(settings.mainFont,0,6); clampSetting(settings.patFont,0,6); clampSetting(settings.patRowsBase,0,1); @@ -1907,6 +1918,7 @@ void FurnaceGUI::commitSettings() { e->setConf("arcadeCore",settings.arcadeCore); e->setConf("ym2612Core",settings.ym2612Core); e->setConf("saaCore",settings.saaCore); + e->setConf("nesCore",settings.nesCore); e->setConf("mainFont",settings.mainFont); e->setConf("patFont",settings.patFont); e->setConf("mainFontPath",settings.mainFontPath); diff --git a/src/gui/stats.cpp b/src/gui/stats.cpp index ed8cbab52..35d6cfc49 100644 --- a/src/gui/stats.cpp +++ b/src/gui/stats.cpp @@ -28,26 +28,17 @@ void FurnaceGUI::drawStats() { } if (!statsOpen) return; if (ImGui::Begin("Statistics",&statsOpen)) { - String adpcmAUsage=fmt::sprintf("%d/16384KB",e->adpcmAMemLen/1024); - String adpcmBUsage=fmt::sprintf("%d/16384KB",e->adpcmBMemLen/1024); - String qsoundUsage=fmt::sprintf("%d/16384KB",e->qsoundMemLen/1024); - String x1_010Usage=fmt::sprintf("%d/1024KB",e->x1_010MemLen/1024); - String es5506Usage=fmt::sprintf("%d/16384KB",e->es5506MemLen/1024); - ImGui::Text("ADPCM-A"); - ImGui::SameLine(); - ImGui::ProgressBar(((float)e->adpcmAMemLen)/16777216.0f,ImVec2(-FLT_MIN,0),adpcmAUsage.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()); + for (int i=0; isong.systemLen; i++) { + DivDispatch* dispatch=e->getDispatch(i); + for (int j=0; dispatch!=NULL && dispatch->getSampleMemCapacity(j)>0; j++) { + size_t capacity=dispatch->getSampleMemCapacity(j); + size_t usage=dispatch->getSampleMemUsage(j); + String usageStr=fmt::sprintf("%d/%dKB",usage/1024,capacity/1024); + ImGui::Text("%s [%d]", e->getSystemName(e->song.system[i]), j); + ImGui::SameLine(); + ImGui::ProgressBar(((float)usage)/((float)capacity),ImVec2(-FLT_MIN,0),usageStr.c_str()); + } + } } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_STATS; ImGui::End();