diff --git a/CMakeLists.txt b/CMakeLists.txt index fc12cfdc..fe7c8118 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -424,6 +424,9 @@ src/engine/platform/sound/ymfm/ymfm_ssg.cpp src/engine/platform/sound/lynx/Mikey.cpp +src/engine/platform/sound/pokey/mzpokeysnd.c +src/engine/platform/sound/pokey/AltASAP.cpp + src/engine/platform/sound/qsound.c src/engine/platform/sound/swan.cpp @@ -508,6 +511,7 @@ src/engine/platform/pcspkr.cpp src/engine/platform/segapcm.cpp src/engine/platform/qsound.cpp src/engine/platform/x1_010.cpp +src/engine/platform/pokey.cpp src/engine/platform/lynx.cpp src/engine/platform/su.cpp src/engine/platform/swan.cpp diff --git a/TODO.md b/TODO.md index 1c6b99e8..137e4f7b 100644 --- a/TODO.md +++ b/TODO.md @@ -1,9 +1,3 @@ # to-do for 0.6pre2 -- POKEY -- Pokémon Mini - - register layout - - confirm emulation -- (maybe) YM2612 CSM (no DualPCM) -- port op macro code to all other OPN chips -- bug fixes \ No newline at end of file +- bug fixes diff --git a/demos/amiga/furnace0.6crk.fur b/demos/amiga/furnace0.6crk.fur new file mode 100644 index 00000000..0966f17b Binary files /dev/null and b/demos/amiga/furnace0.6crk.fur differ diff --git a/demos/ay8910/remark_music.fur b/demos/ay8910/remark_music.fur new file mode 100644 index 00000000..92ad66e0 Binary files /dev/null and b/demos/ay8910/remark_music.fur differ diff --git a/demos/misc/rf5wapianoroll.fur b/demos/misc/rf5wapianoroll.fur new file mode 100644 index 00000000..4b2919f3 Binary files /dev/null and b/demos/misc/rf5wapianoroll.fur differ diff --git a/demos/sms/thunderblade-type-ii.fur b/demos/sms/thunderblade-type-ii.fur new file mode 100644 index 00000000..d2f922fa Binary files /dev/null and b/demos/sms/thunderblade-type-ii.fur differ diff --git a/demos/snes/Breezy.fur b/demos/snes/Breezy.fur new file mode 100644 index 00000000..20c5121a Binary files /dev/null and b/demos/snes/Breezy.fur differ diff --git a/demos/specs2/object.fur b/demos/specs2/object.fur new file mode 100644 index 00000000..c9a6e8ea Binary files /dev/null and b/demos/specs2/object.fur differ diff --git a/papers/doc/7-systems/pokey.md b/papers/doc/7-systems/pokey.md new file mode 100644 index 00000000..b05c4367 --- /dev/null +++ b/papers/doc/7-systems/pokey.md @@ -0,0 +1,34 @@ +# POKEY + +a sound and input chip developed by Atari for their 8-bit computers (Atari 400, 800, XL/XE and so on). 4 channels of signature Atari sounds. + +# effects + +- 10xx: set waveform. + - 0: harsh noise (poly5+17) + - 1: square buzz (poly5) + - 2: weird noise (poly4+5) + - 3: square buzz (poly5) + - 4: soft noise (poly17) + - 5: square + - 6: bass (poly4) + - 7: buzz (poly4) +- 11xx: set AUDCTL. `xx` is a bitmask. + - bit 7: 9-bit poly mode. shortens noise. + - bit 6: high channel 1 clock (~1.79MHz on NTSC). + - overrides 15KHz mode. + - bit 5: high channel 3 clock (~1.79MHz on NTSC). + - overrides 15KHz mode. + - bit 4: join channels 1 and 2 for a wide period range. + - use with conjunction with bit 6. + - channel 2 becomes inaccessible when this is on. + - bit 3: join channels 3 and 4 for a wide period range. + - use with conjunction with bit 5. + - channel 4 becomes inaccessible when this is on. + - bit 2: high-pass filter (channels 1 and 3). + - filtered output on channel 1 (I suggest you to set channel 3 volume to 0). + - use for PWM effects (not automatic!). + - bit 1: high-pass filter (channels 2 and 4). + - filtered output on channel 2 (I suggest you to set channel 4 volume to 0). + - use for PWM effects (not automatic!). + - bit 0: 15KHz mode. diff --git a/papers/format.md b/papers/format.md index d633299e..a36325df 100644 --- a/papers/format.md +++ b/papers/format.md @@ -215,8 +215,8 @@ size | description | - 0x8a: FDS - 1 channel | - 0x8b: MMC5 - 3 channels | - 0x8c: Namco 163 - 8 channels - | - 0x8d: OPN (YM2203) - 6 channels - | - 0x8e: PC-98 (YM2608) - 16 channels + | - 0x8d: YM2203 - 6 channels + | - 0x8e: YM2608 - 16 channels | - 0x8f: OPL (YM3526) - 9 channels | - 0x90: OPL2 (YM3812) - 9 channels | - 0x91: OPL3 (YMF262) - 18 channels @@ -256,8 +256,8 @@ size | description | - 0xb3: Yamaha Y8950 drums - 12 channels | - 0xb4: Konami SCC+ - 5 channels | - 0xb5: tildearrow Sound Unit - 8 channels - | - 0xb6: OPN extended - 9 channels - | - 0xb7: PC-98 extended - 19 channels + | - 0xb6: YM2203 extended - 9 channels + | - 0xb7: YM2608 extended - 19 channels | - 0xb8: YMZ280B - 8 channels | - 0xb9: Namco WSG - 3 channels | - 0xba: Namco 15xx - 8 channels @@ -269,8 +269,8 @@ size | description | - 0xc0: PCM DAC - 1 channel | - 0xc1: YM2612 CSM - 10 channels | - 0xc2: Neo Geo CSM (YM2610) - 18 channels - | - 0xc3: OPN CSM - 10 channels - | - 0xc4: PC-98 CSM - 20 channels + | - 0xc3: YM2203 CSM - 10 channels + | - 0xc4: YM2608 CSM - 20 channels | - 0xc5: YM2610B CSM - 20 channels | - 0xc6: K007232 - 2 channels | - 0xc7: GA20 - 4 channels @@ -1441,7 +1441,7 @@ chips which aren't on this list don't have any flags. - bit 4-6: channels (int) - bit 7: multiplex (bool) -## 0x8d: OPN (YM2203) and 0xb6: OPN extended +## 0x8d: YM2203 and 0xb6: YM2203 extended - bit 0-4: clockSel (int) - 0: NTSC @@ -1455,7 +1455,7 @@ chips which aren't on this list don't have any flags. - 1: /3 - 2: /2 -## 0x8e: PC-98 (YM2608) and 0xb7: PC-98 extended +## 0x8e: YM2608 and 0xb7: YM2608 extended - bit 0-4: clockSel (int) - 0: 8MHz diff --git a/src/engine/config.cpp b/src/engine/config.cpp index ce7b1b4a..86c7ddc0 100644 --- a/src/engine/config.cpp +++ b/src/engine/config.cpp @@ -54,6 +54,10 @@ String DivConfig::toBase64() { return taEncodeBase64(data); } +const std::map& DivConfig::configMap() { + return conf; +} + void DivConfig::parseLine(const char* line) { String key=""; String value=""; @@ -171,6 +175,15 @@ String DivConfig::getString(String key, String fallback) const { return fallback; } +bool DivConfig::has(String key) { + try { + String test=conf.at(key); + } catch (std::out_of_range& e) { + return false; + } + return true; +} + void DivConfig::set(String key, bool value) { if (value) { conf[key]="true"; diff --git a/src/engine/config.h b/src/engine/config.h index d573a451..a867dc82 100644 --- a/src/engine/config.h +++ b/src/engine/config.h @@ -35,6 +35,9 @@ class DivConfig { String toBase64(); bool save(const char* path); + // get the map + const std::map& configMap(); + // get a config value bool getBool(String key, bool fallback) const; int getInt(String key, int fallback) const; @@ -42,6 +45,9 @@ class DivConfig { double getDouble(String key, double fallback) const; String getString(String key, String fallback) const; + // check for existence + bool has(String key); + // set a config value void set(String key, bool value); void set(String key, int value); diff --git a/src/engine/configEngine.cpp b/src/engine/configEngine.cpp index 5589e064..a265f0be 100644 --- a/src/engine/configEngine.cpp +++ b/src/engine/configEngine.cpp @@ -23,6 +23,7 @@ #ifdef _WIN32 #include "winStuff.h" #define CONFIG_FILE "\\furnace.cfg" +#define LOG_FILE "\\furnace.log" #else #ifdef __HAIKU__ #include diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index 733239ea..90f7f884 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -57,6 +57,7 @@ #include "platform/su.h" #include "platform/swan.h" #include "platform/lynx.h" +#include "platform/pokey.h" #include "platform/zxbeeper.h" #include "platform/bubsyswsg.h" #include "platform/n163.h" @@ -201,12 +202,18 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do ((DivPlatformGenesisExt*)dispatch)->setYMFM(eng->getConfInt("ym2612Core",0)); ((DivPlatformGenesisExt*)dispatch)->setSoftPCM(false); break; - case DIV_SYSTEM_YM2612_FRAC: + case DIV_SYSTEM_YM2612_CSM: + dispatch=new DivPlatformGenesisExt; + ((DivPlatformGenesisExt*)dispatch)->setYMFM(eng->getConfInt("ym2612Core",0)); + ((DivPlatformGenesisExt*)dispatch)->setSoftPCM(false); + ((DivPlatformGenesisExt*)dispatch)->setCSMChannel(6); + break; + case DIV_SYSTEM_YM2612_DUALPCM: dispatch=new DivPlatformGenesis; ((DivPlatformGenesis*)dispatch)->setYMFM(eng->getConfInt("ym2612Core",0)); ((DivPlatformGenesis*)dispatch)->setSoftPCM(true); break; - case DIV_SYSTEM_YM2612_FRAC_EXT: + case DIV_SYSTEM_YM2612_DUALPCM_EXT: dispatch=new DivPlatformGenesisExt; ((DivPlatformGenesisExt*)dispatch)->setYMFM(eng->getConfInt("ym2612Core",0)); ((DivPlatformGenesisExt*)dispatch)->setSoftPCM(true); @@ -269,16 +276,16 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do case DIV_SYSTEM_TIA: dispatch=new DivPlatformTIA; break; - case DIV_SYSTEM_OPN: + case DIV_SYSTEM_YM2203: dispatch=new DivPlatformYM2203; break; - case DIV_SYSTEM_OPN_EXT: + case DIV_SYSTEM_YM2203_EXT: dispatch=new DivPlatformYM2203Ext; break; - case DIV_SYSTEM_PC98: + case DIV_SYSTEM_YM2608: dispatch=new DivPlatformYM2608; break; - case DIV_SYSTEM_PC98_EXT: + case DIV_SYSTEM_YM2608_EXT: dispatch=new DivPlatformYM2608Ext; break; case DIV_SYSTEM_OPLL: @@ -339,6 +346,10 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do case DIV_SYSTEM_LYNX: dispatch=new DivPlatformLynx; break; + case DIV_SYSTEM_POKEY: + dispatch=new DivPlatformPOKEY; + ((DivPlatformPOKEY*)dispatch)->setAltASAP(eng->getConfInt("pokeyCore",1)==1); + break; case DIV_SYSTEM_QSOUND: dispatch=new DivPlatformQSound; break; diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 4ebc170f..2809b8f6 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -3295,7 +3295,7 @@ DivSample* DivEngine::sampleFromFile(const char* path) { #endif } -DivSample* DivEngine::sampleFromFileRaw(const char* path, DivSampleDepth depth, int channels, bool bigEndian, bool unsign) { +DivSample* DivEngine::sampleFromFileRaw(const char* path, DivSampleDepth depth, int channels, bool bigEndian, bool unsign, bool swapNibbles) { if (song.sample.size()>=256) { lastError="too many samples!"; return NULL; @@ -3461,6 +3461,14 @@ DivSample* DivEngine::sampleFromFileRaw(const char* path, DivSampleDepth depth, } delete[] buf; + // swap nibbles if needed + if (swapNibbles) { + unsigned char* b=(unsigned char*)sample->getCurBuf(); + for (unsigned int i=0; igetCurBufLen(); i++) { + b[i]=(b[i]<<4)|(b[i]>>4); + } + } + BUSY_END; return sample; } @@ -4215,7 +4223,7 @@ bool DivEngine::deinitAudioBackend(bool dueToSwitchMaster) { return true; } -bool DivEngine::init() { +void DivEngine::preInit() { // register systems if (!systemsRegistered) registerSystems(); @@ -4223,8 +4231,13 @@ bool DivEngine::init() { initConfDir(); logD("config path: %s",configPath.c_str()); + String logPath=configPath+DIR_SEPARATOR_STR+"furnace.log"; + startLogFile(logPath.c_str()); + loadConf(); +} +bool DivEngine::init() { loadSampleROMs(); // set default system preset diff --git a/src/engine/engine.h b/src/engine/engine.h index 612bd4be..f57bd8d0 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -796,7 +796,7 @@ class DivEngine { DivSample* sampleFromFile(const char* path); // get raw sample - DivSample* sampleFromFileRaw(const char* path, DivSampleDepth depth, int channels, bool bigEndian, bool unsign); + DivSample* sampleFromFileRaw(const char* path, DivSampleDepth depth, int channels, bool bigEndian, bool unsign, bool swapNibbles); // delete sample void delSample(int index); @@ -1012,6 +1012,9 @@ class DivEngine { // quit dispatch void quitDispatch(); + // pre-initialize the engine. + void preInit(); + // initialize the engine. bool init(); diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index b21d3a0d..d56836bf 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -1207,8 +1207,8 @@ void DivEngine::convertOldFlags(unsigned int oldFlags, DivConfig& newFlags, DivS break; case DIV_SYSTEM_YM2612: case DIV_SYSTEM_YM2612_EXT: - case DIV_SYSTEM_YM2612_FRAC: - case DIV_SYSTEM_YM2612_FRAC_EXT: + case DIV_SYSTEM_YM2612_DUALPCM: + case DIV_SYSTEM_YM2612_DUALPCM_EXT: switch (oldFlags&0x7fffffff) { case 0: newFlags.set("clockSel",0); @@ -1295,8 +1295,8 @@ void DivEngine::convertOldFlags(unsigned int oldFlags, DivConfig& newFlags, DivS newFlags.set("channels",(int)((oldFlags>>4)&7)); if (oldFlags&128) newFlags.set("multiplex",true); break; - case DIV_SYSTEM_OPN: - case DIV_SYSTEM_OPN_EXT: + case DIV_SYSTEM_YM2203: + case DIV_SYSTEM_YM2203_EXT: switch (oldFlags&31) { case 0: newFlags.set("clockSel",0); @@ -1329,8 +1329,8 @@ void DivEngine::convertOldFlags(unsigned int oldFlags, DivConfig& newFlags, DivS break; } break; - case DIV_SYSTEM_PC98: - case DIV_SYSTEM_PC98_EXT: + case DIV_SYSTEM_YM2608: + case DIV_SYSTEM_YM2608_EXT: switch (oldFlags&31) { case 0: newFlags.set("clockSel",0); @@ -2484,14 +2484,14 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { case DIV_SYSTEM_YM2610_FULL_EXT: case DIV_SYSTEM_YM2610B: case DIV_SYSTEM_YM2610B_EXT: - case DIV_SYSTEM_OPN: - case DIV_SYSTEM_OPN_EXT: - case DIV_SYSTEM_PC98: - case DIV_SYSTEM_PC98_EXT: + case DIV_SYSTEM_YM2203: + case DIV_SYSTEM_YM2203_EXT: + case DIV_SYSTEM_YM2608: + case DIV_SYSTEM_YM2608_EXT: case DIV_SYSTEM_YM2612: case DIV_SYSTEM_YM2612_EXT: - case DIV_SYSTEM_YM2612_FRAC: - case DIV_SYSTEM_YM2612_FRAC_EXT: + case DIV_SYSTEM_YM2612_DUALPCM: + case DIV_SYSTEM_YM2612_DUALPCM_EXT: opnCount++; break; default: @@ -2514,12 +2514,12 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { if (ds.version<125) { for (int i=0; i>13); + double x=(double)i*M_PI/8192.0; + sincTable8[mapped]=sin(x)/x; + } + + for (int i=0; i<32768; i++) { + int mapped=((i&8191)<<2)|(i>>13); + sincTable8[mapped]*=pow(cos(M_PI*(double)i/65536.0),2.0); + } + } + return sincTable8; +} + float* DivFilterTables::getSincIntegralTable() { if (sincIntegralTable==NULL) { logD("initializing sinc integral table."); diff --git a/src/engine/filter.h b/src/engine/filter.h index 974a448e..035c05e6 100644 --- a/src/engine/filter.h +++ b/src/engine/filter.h @@ -21,6 +21,7 @@ class DivFilterTables { public: static float* cubicTable; static float* sincTable; + static float* sincTable8; static float* sincIntegralTable; /** @@ -35,6 +36,12 @@ class DivFilterTables { */ static float* getSincTable(); + /** + * get a 8192x4 one-side sine-windowed sinc table. + * @return the table. + */ + static float* getSincTable8(); + /** * get a 8192x8 one-side sine-windowed sinc integral table. * @return the table. diff --git a/src/engine/platform/arcade.cpp b/src/engine/platform/arcade.cpp index e1ce592b..e7324998 100644 --- a/src/engine/platform/arcade.cpp +++ b/src/engine/platform/arcade.cpp @@ -353,9 +353,9 @@ void DivPlatformArcade::tick(bool sysTick) { chan[i].freq=chan[i].baseFreq+(chan[i].pitch>>1)-64+chan[i].pitch2; if (!parent->song.oldArpStrategy) { if (chan[i].fixedArp) { - chan[i].freq=(chan[i].baseNoteOverride<<7)+(chan[i].pitch>>1)-64+chan[i].pitch2; + chan[i].freq=(chan[i].baseNoteOverride<<6)+(chan[i].pitch>>1)-64+chan[i].pitch2; } else { - chan[i].freq+=chan[i].arpOff<<7; + chan[i].freq+=chan[i].arpOff<<6; } } if (chan[i].freq<0) chan[i].freq=0; diff --git a/src/engine/platform/ay.cpp b/src/engine/platform/ay.cpp index fbd19f06..25a4cd1a 100644 --- a/src/engine/platform/ay.cpp +++ b/src/engine/platform/ay.cpp @@ -830,6 +830,9 @@ void DivPlatformAY8910::setFlags(const DivConfig& flags) { case 14: chipClock=1536000; break; + case 15: + chipClock=38400*13*4; // 31948800/16 + break; default: chipClock=COLOR_NTSC/2.0; break; diff --git a/src/engine/platform/fmshared_OPN.h b/src/engine/platform/fmshared_OPN.h index a8ec6772..610e6160 100644 --- a/src/engine/platform/fmshared_OPN.h +++ b/src/engine/platform/fmshared_OPN.h @@ -83,6 +83,8 @@ return 2; \ } +#define IS_EXTCH_MUTED (isOpMuted[0] && isOpMuted[1] && isOpMuted[2] && isOpMuted[3]) + class DivPlatformOPN: public DivPlatformFMBase { protected: const unsigned short ADDR_MULT_DT=0x30; @@ -147,17 +149,21 @@ class DivPlatformOPN: public DivPlatformFMBase { double fmFreqBase; unsigned int fmDivBase; unsigned int ayDiv; + unsigned char csmChan; + unsigned char lfoValue; bool extSys; DivConfig ayFlags; friend void putDispatchChip(void*,int); friend void putDispatchChan(void*,int,int); - DivPlatformOPN(double f=9440540.0, unsigned int d=72, unsigned int a=32, bool isExtSys=false): + DivPlatformOPN(double f=9440540.0, unsigned int d=72, unsigned int a=32, bool isExtSys=false, unsigned char cc=255): DivPlatformFMBase(), fmFreqBase(f), fmDivBase(d), ayDiv(a), + csmChan(cc), + lfoValue(0), extSys(isExtSys) {} }; diff --git a/src/engine/platform/genesis.cpp b/src/engine/platform/genesis.cpp index 1a3a5b2e..55471ada 100644 --- a/src/engine/platform/genesis.cpp +++ b/src/engine/platform/genesis.cpp @@ -479,7 +479,7 @@ void DivPlatformGenesis::tick(bool sysTick) { } } - for (int i=0; i<7; i++) { + for (int i=0; isong.linearPitch==2) { @@ -554,7 +554,7 @@ int DivPlatformGenesis::dispatch(DivCommand c) { switch (c.cmd) { case DIV_CMD_NOTE_ON: { DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_FM); - if (c.chan==7 && extMode && softPCM) { // CSM + if (c.chan==csmChan && extMode) { // CSM chan[c.chan].macroInit(ins); chan[c.chan].insChanged=false; @@ -691,7 +691,7 @@ int DivPlatformGenesis::dispatch(DivCommand c) { break; } case DIV_CMD_NOTE_OFF: - if (c.chan>=5 && c.chan<7) { + if (c.chan>=5 && c.chansong.brokenDACMode) { @@ -786,7 +786,7 @@ int DivPlatformGenesis::dispatch(DivCommand c) { } break; } - if (c.chan==7) { + if (c.chan==csmChan) { int destFreq=NOTE_PERIODIC(c.value2); bool return2=false; if (destFreq>chan[c.chan].baseFreq) { @@ -855,7 +855,7 @@ int DivPlatformGenesis::dispatch(DivCommand c) { break; } case DIV_CMD_LEGATO: { - if (c.chan==7) { + if (c.chan==csmChan) { chan[c.chan].baseFreq=NOTE_PERIODIC(c.value); } else if (c.chan>=5 && chan[c.chan].furnaceDac && chan[c.chan].dacMode) { chan[c.chan].baseFreq=parent->calcBaseFreq(1,1,c.value,false); @@ -1222,7 +1222,7 @@ void DivPlatformGenesis::poke(std::vector& wlist) { } int DivPlatformGenesis::getPortaFloor(int ch) { - return (ch>5)?12:0; + return 0; } void DivPlatformGenesis::setYMFM(bool use) { diff --git a/src/engine/platform/genesis.h b/src/engine/platform/genesis.h index 28b823c0..a99aac61 100644 --- a/src/engine/platform/genesis.h +++ b/src/engine/platform/genesis.h @@ -86,8 +86,6 @@ class DivPlatformGenesis: public DivPlatformOPN { ymfm::ym2612* fm_ymfm; ymfm::ym2612::output_data out_ymfm; DivYM2612Interface iface; - - unsigned char lfoValue; int softPCMTimer; @@ -133,7 +131,7 @@ class DivPlatformGenesis: public DivPlatformOPN { int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags); void quit(); DivPlatformGenesis(): - DivPlatformOPN(9440540.0, 72, 32) {} + DivPlatformOPN(9440540.0, 72, 32, false, 7) {} ~DivPlatformGenesis(); }; #endif diff --git a/src/engine/platform/genesisext.cpp b/src/engine/platform/genesisext.cpp index 6bf151a0..ae6ac70b 100644 --- a/src/engine/platform/genesisext.cpp +++ b/src/engine/platform/genesisext.cpp @@ -25,7 +25,6 @@ #define CHIP_DIVIDER fmDivBase #define IS_REALLY_MUTED(x) (isMuted[x] && (x<5 || !softPCM || (isMuted[5] && isMuted[6]))) -#define IS_EXTCH_MUTED (isOpMuted[0] && isOpMuted[1] && isOpMuted[2] && isOpMuted[3]) int DivPlatformGenesisExt::dispatch(DivCommand c) { if (c.chan<2) { @@ -220,14 +219,14 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) { rWrite(chanOffs[2]+ADDR_FB_ALG,(chan[2].state.alg&7)|(chan[2].state.fb<<3)); break; } - case DIV_CMD_FM_MULT: { // TODO + case DIV_CMD_FM_MULT: { unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]]; DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]]; op.mult=c.value2&15; rWrite(baseAddr+0x30,(op.mult&15)|(dtTable[op.dt&7]<<4)); break; } - case DIV_CMD_FM_TL: { // TODO + case DIV_CMD_FM_TL: { unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]]; DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]]; op.tl=c.value2; @@ -454,7 +453,7 @@ void DivPlatformGenesisExt::tick(bool sysTick) { } } if (writeSomething) { - if (chan[7].active) { // CSM + if (chan[csmChan].active) { // CSM writeMask^=0xf0; } /*printf( @@ -588,18 +587,18 @@ void DivPlatformGenesisExt::tick(bool sysTick) { } } - if (extMode && softPCM) { - if (chan[7].freqChanged) { - chan[7].freq=parent->calcFreq(chan[7].baseFreq,chan[7].pitch,chan[7].fixedArp?chan[7].baseNoteOverride:chan[7].arpOff,chan[7].fixedArp,true,0,chan[7].pitch2,chipClock,CHIP_DIVIDER); - if (chan[7].freq<1) chan[7].freq=1; - if (chan[7].freq>1024) chan[7].freq=1024; - int wf=0x400-chan[7].freq; + if (extMode) { + if (chan[csmChan].freqChanged) { + chan[csmChan].freq=parent->calcFreq(chan[csmChan].baseFreq,chan[csmChan].pitch,chan[csmChan].fixedArp?chan[csmChan].baseNoteOverride:chan[csmChan].arpOff,chan[csmChan].fixedArp,true,0,chan[csmChan].pitch2,chipClock,CHIP_DIVIDER); + if (chan[csmChan].freq<1) chan[csmChan].freq=1; + if (chan[csmChan].freq>1024) chan[csmChan].freq=1024; + int wf=0x400-chan[csmChan].freq; immWrite(0x24,wf>>2); immWrite(0x25,wf&3); - chan[7].freqChanged=false; + chan[csmChan].freqChanged=false; } - if (chan[7].keyOff || chan[7].keyOn) { + if (chan[csmChan].keyOff || chan[csmChan].keyOn) { writeNoteOn=true; for (int i=0; i<4; i++) { writeMask|=opChan[i].active<<(4+i); @@ -608,7 +607,7 @@ void DivPlatformGenesisExt::tick(bool sysTick) { } if (writeNoteOn) { - if (chan[7].active) { // CSM + if (chan[csmChan].active) { // CSM writeMask^=0xf0; } /*printf( @@ -621,14 +620,14 @@ void DivPlatformGenesisExt::tick(bool sysTick) { immWrite(0x28,writeMask); } - if (extMode && softPCM) { - if (chan[7].keyOn) { + if (extMode) { + if (chan[csmChan].keyOn) { immWrite(0x27,0x81); - chan[7].keyOn=false; + chan[csmChan].keyOn=false; } - if (chan[7].keyOff) { + if (chan[csmChan].keyOff) { immWrite(0x27,0x40); - chan[7].keyOff=false; + chan[csmChan].keyOff=false; } } } @@ -686,10 +685,10 @@ void DivPlatformGenesisExt::forceIns() { opChan[i].freqChanged=true; } } - if (extMode && softPCM && chan[7].active) { // CSM - chan[7].insChanged=true; - chan[7].freqChanged=true; - chan[7].keyOn=true; + if (extMode && chan[csmChan].active) { // CSM + chan[csmChan].insChanged=true; + chan[csmChan].freqChanged=true; + chan[csmChan].keyOn=true; } } @@ -701,7 +700,7 @@ void* DivPlatformGenesisExt::getChanState(int ch) { DivMacroInt* DivPlatformGenesisExt::getChanMacroInt(int ch) { if (ch>=6) return &chan[ch-3].std; - if (ch>=2) return NULL; // currently not implemented + if (ch>=2) return &opChan[ch-2].std; return &chan[ch].std; } @@ -747,6 +746,10 @@ int DivPlatformGenesisExt::getPortaFloor(int ch) { return (ch>8)?12:0; } +void DivPlatformGenesisExt::setCSMChannel(unsigned char ch) { + csmChan=ch; +} + int DivPlatformGenesisExt::init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags) { DivPlatformGenesis::init(parent,channels,sugRate,flags); for (int i=0; i<4; i++) { diff --git a/src/engine/platform/genesisext.h b/src/engine/platform/genesisext.h index eec093a8..304f609f 100644 --- a/src/engine/platform/genesisext.h +++ b/src/engine/platform/genesisext.h @@ -42,6 +42,7 @@ class DivPlatformGenesisExt: public DivPlatformGenesis { bool keyOffAffectsPorta(int ch); void notifyInsChange(int ins); int getPortaFloor(int ch); + void setCSMChannel(unsigned char ch); int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags); void quit(); ~DivPlatformGenesisExt(); diff --git a/src/engine/platform/k007232.cpp b/src/engine/platform/k007232.cpp index aa19773d..ce15f115 100644 --- a/src/engine/platform/k007232.cpp +++ b/src/engine/platform/k007232.cpp @@ -529,10 +529,10 @@ void DivPlatformK007232::renderSamples(int sysID) { } const int length=s->getLoopEndPosition(DIV_SAMPLE_DEPTH_8BIT); - int actualLength=MIN((int)(getSampleMemCapacity()-memPos)-1,length); + int actualLength=MIN((int)(getSampleMemCapacity()-memPos)-2,length); if (actualLength>0) { - if (actualLength>131072-1) { - actualLength=131072-1; + if (actualLength>131072-2) { + actualLength=131072-2; } if ((memPos&0xfe0000)!=((memPos+actualLength+1)&0xfe0000)) { memPos=(memPos+0x1ffff)&0xfe0000; diff --git a/src/engine/platform/msm6295.cpp b/src/engine/platform/msm6295.cpp index f1f61122..292229a1 100644 --- a/src/engine/platform/msm6295.cpp +++ b/src/engine/platform/msm6295.cpp @@ -356,7 +356,7 @@ void DivPlatformMSM6295::renderSamples(int sysID) { // sample data size_t memPos=128*8; int sampleCount=parent->song.sampleLen; - if (sampleCount>128) sampleCount=128; + if (sampleCount>127) sampleCount=127; for (int i=0; isong.sample[i]; if (!s->renderOn[0][sysID]) { diff --git a/src/engine/platform/pcmdac.cpp b/src/engine/platform/pcmdac.cpp index 803eef7d..53416456 100644 --- a/src/engine/platform/pcmdac.cpp +++ b/src/engine/platform/pcmdac.cpp @@ -20,6 +20,7 @@ #define _USE_MATH_DEFINES #include "pcmdac.h" #include "../engine.h" +#include "../filter.h" #include // to ease the driver, freqency register is a 8.16 counter relative to output sample rate @@ -103,7 +104,50 @@ void DivPlatformPCMDAC::acquire(short* bufL, short* bufR, size_t start, size_t l } } if (chan[0].audPos>=0 && chan[0].audPos<(int)s->samples) { - output=s->data16[chan[0].audPos]; + int s_4=((chan[0].audPos-4)>=0)?s->data16[chan[0].audPos-4]:0; + int s_3=((chan[0].audPos-3)>=0)?s->data16[chan[0].audPos-3]:0; + int s_2=((chan[0].audPos-2)>=0)?s->data16[chan[0].audPos-2]:0; + int s_1=((chan[0].audPos-1)>=0)?s->data16[chan[0].audPos-1]:0; + int s0=s->data16[chan[0].audPos]; + int s1=((chan[0].audPos+1)<(int)s->samples)?s->data16[chan[0].audPos+1]:0; + int s2=((chan[0].audPos+2)<(int)s->samples)?s->data16[chan[0].audPos+2]:0; + int s3=((chan[0].audPos+3)<(int)s->samples)?s->data16[chan[0].audPos+3]:0; + switch (interp) { + case 1: // linear + output=s0+((s1-s0)*(chan[0].audSub&0xffff)>>16); + break; + case 2: { // cubic + float* cubicTable=DivFilterTables::getCubicTable(); + float* t=&cubicTable[((chan[0].audSub&0xffff)>>6)<<2]; + float result=(float)s_1*t[0]+(float)s0*t[1]+(float)s1*t[2]+(float)s2*t[3]; + if (result<-32768) result=-32768; + if (result>32767) result=32767; + output=result; + break; + } + case 3: { // sinc + float* sincTable=DivFilterTables::getSincTable8(); + float* t1=&sincTable[(8191-((chan[0].audSub&0xffff)>>3))<<2]; + float* t2=&sincTable[((chan[0].audSub&0xffff)>>3)<<2]; + float result=( + s_4*t2[3]+ + s_3*t2[2]+ + s_2*t2[1]+ + s_1*t2[0]+ + s0*t1[0]+ + s1*t1[1]+ + s2*t1[2]+ + s3*t1[3] + ); + if (result<-32768) result=-32768; + if (result>32767) result=32767; + output=result; + break; + } + default: // none + output=s0; + break; + } } } else { chan[0].sample=-1; @@ -398,6 +442,8 @@ void DivPlatformPCMDAC::setFlags(const DivConfig& flags) { chipClock=rate; outDepth=(flags.getInt("outDepth",15))&15; outStereo=flags.getBool("stereo",true); + interp=flags.getInt("interpolation",0); + oscBuf->rate=rate; } int DivPlatformPCMDAC::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) { diff --git a/src/engine/platform/pcmdac.h b/src/engine/platform/pcmdac.h index dcdb4587..95c01424 100644 --- a/src/engine/platform/pcmdac.h +++ b/src/engine/platform/pcmdac.h @@ -55,6 +55,12 @@ class DivPlatformPCMDAC: public DivDispatch { DivDispatchOscBuffer* oscBuf; bool isMuted; int outDepth; + // valid values: + // - 0: none + // - 1: linear + // - 2: cubic spline + // - 3: sinc + int interp; bool outStereo; friend void putDispatchChip(void*,int); diff --git a/src/engine/platform/pokemini.cpp b/src/engine/platform/pokemini.cpp index fdf0962c..7625f1ed 100644 --- a/src/engine/platform/pokemini.cpp +++ b/src/engine/platform/pokemini.cpp @@ -26,7 +26,20 @@ #define CHIP_DIVIDER 1 const char* regCheatSheetPokeMini[]={ - "Period", "0", + "TMR3_SCALE", "1C", + "TMR3_OSC", "1D", + "TMR3_CTRL_L", "48", + "TMR3_CTRL_H", "49", + "TMR3_PRE_L", "4A", + "TMR3_PRE_H", "4B", + "TMR3_PVT_L", "4C", + "TMR3_PVT_H", "4D", + "TMR3_CNT_L", "4E", + "TMR3_CNT_H", "4F", + "IO_DIR", "60", + "IO_DATA", "61", + "AUD_CTRL", "70", + "AUD_VOL", "71", NULL }; @@ -42,6 +55,37 @@ const char** DivPlatformPokeMini::getRegisterSheet() { return regCheatSheetPokeMini; } +void DivPlatformPokeMini::rWrite(unsigned char addr, unsigned char val) { + if (addr<128) regPool[addr]=val; + switch (addr) { + case 0x1c: + // ignore + break; + case 0x1d: + // ignore + break; + case 0x48: case 0x49: + on=val&4; + if (val&2) pos=0; + break; + case 0x4a: + preset=(preset&0xff00)|val; + break; + case 0x4b: + preset=(preset&0xff)|(val<<8); + break; + case 0x4c: + pivot=(pivot&0xff00)|val; + break; + case 0x4d: + pivot=(pivot&0xff)|(val<<8); + break; + case 0x71: + vol=val&3; + break; + } +} + void DivPlatformPokeMini::acquire(short* bufL, short* bufR, size_t start, size_t len) { int out=0; for (size_t i=start; i65535) chan[i].freq=65535; if (chan[i].keyOn) { - on=true; + rWrite(0x48,4); } if (chan[i].keyOff) { - on=false; + rWrite(0x48,0); } - preset=chan[i].freq; - pivot=(chan[i].duty*preset)>>8; + rWrite(0x4a,chan[i].freq&0xff); + rWrite(0x4b,chan[i].freq>>8); + int pvt=(chan[i].duty*chan[i].freq)>>8; + rWrite(0x4c,pvt&0xff); + rWrite(0x4d,pvt>>8); if (chan[i].keyOn) chan[i].keyOn=false; if (chan[i].keyOff) chan[i].keyOff=false; chan[i].freqChanged=false; @@ -122,7 +169,7 @@ int DivPlatformPokeMini::dispatch(DivCommand c) { chan[c.chan].freqChanged=true; chan[c.chan].note=c.value; } - vol=(chan[c.chan].outVol==2)?3:chan[c.chan].outVol; + rWrite(0x71,(chan[c.chan].outVol==2)?3:chan[c.chan].outVol); chan[c.chan].active=true; chan[c.chan].keyOn=true; chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_POKEMINI)); @@ -151,7 +198,7 @@ int DivPlatformPokeMini::dispatch(DivCommand c) { chan[c.chan].outVol=c.value; } if (chan[c.chan].active) { - on=chan[c.chan].vol; + rWrite(0x71,(chan[c.chan].outVol==2)?3:chan[c.chan].outVol); } } break; @@ -186,14 +233,13 @@ int DivPlatformPokeMini::dispatch(DivCommand c) { break; } case DIV_CMD_LEGATO: - if (c.chan==3) break; chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+((HACKY_LEGATO_MESS)?(chan[c.chan].std.arp.val):(0))); chan[c.chan].freqChanged=true; chan[c.chan].note=c.value; break; case DIV_CMD_PRE_PORTA: if (chan[c.chan].active && c.value2) { - if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_BEEPER)); + if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_POKEMINI)); } if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note); chan[c.chan].inPorta=c.value; @@ -239,18 +285,11 @@ DivDispatchOscBuffer* DivPlatformPokeMini::getOscBuffer(int ch) { } unsigned char* DivPlatformPokeMini::getRegisterPool() { - if (on) { - regPool[0]=preset; - regPool[1]=preset>>8; - } else { - regPool[0]=0; - regPool[1]=0; - } return regPool; } int DivPlatformPokeMini::getRegisterPoolSize() { - return 2; + return 128; } void DivPlatformPokeMini::reset() { @@ -272,7 +311,7 @@ void DivPlatformPokeMini::reset() { pivot=0; elapsedMain=0; - memset(regPool,0,2); + memset(regPool,0,128); } bool DivPlatformPokeMini::keyOffAffectsArp(int ch) { @@ -294,11 +333,11 @@ void DivPlatformPokeMini::notifyInsDeletion(void* ins) { } void DivPlatformPokeMini::poke(unsigned int addr, unsigned short val) { - // ??? + rWrite(addr,val); } void DivPlatformPokeMini::poke(std::vector& wlist) { - // ??? + for (DivRegWrite& i: wlist) rWrite(i.addr,i.val); } int DivPlatformPokeMini::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) { diff --git a/src/engine/platform/pokemini.h b/src/engine/platform/pokemini.h index b310de73..7fc17ddf 100644 --- a/src/engine/platform/pokemini.h +++ b/src/engine/platform/pokemini.h @@ -37,9 +37,11 @@ class DivPlatformPokeMini: public DivDispatch { int pos; unsigned char timerScale, vol; unsigned short preset, pivot; - unsigned char regPool[2]; + unsigned char regPool[128]; unsigned short elapsedMain; + void rWrite(unsigned char addr, unsigned char val); + friend void putDispatchChip(void*,int); friend void putDispatchChan(void*,int,int); diff --git a/src/engine/platform/pokey.cpp b/src/engine/platform/pokey.cpp new file mode 100644 index 00000000..a6ddb78f --- /dev/null +++ b/src/engine/platform/pokey.cpp @@ -0,0 +1,504 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "pokey.h" +#include "../engine.h" +#include "../../ta-log.h" + +#define rWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} } + +#define CHIP_DIVIDER 1 + +const char* regCheatSheetPOKEY[]={ + "AUDF1", "0", + "AUDC1", "1", + "AUDF2", "2", + "AUDC2", "3", + "AUDF3", "4", + "AUDC3", "5", + "AUDF4", "6", + "AUDC4", "7", + "AUDCTL", "8", + NULL +}; + +// LLsLSsLLsSLsLLn +const unsigned char snapPeriodLong[15]={ + 0, 1, 1, 3, 3, 6, 6, 7, 7, 10, 10, 12, 12, 13, 13 +}; + +const unsigned char snapPeriodShort[15]={ + 2, 2, 2, 2, 5, 5, 5, 8, 8, 11, 11, 11, 11, 17, 17 +}; + +// LsSLsLLnLLsLSsL +const unsigned char snapPeriodLong16[15]={ + 0, 0, 3, 3, 3, 5, 6, 6, 8, 9, 9, 11, 11, 14, 14 +}; + +const unsigned char snapPeriodShort16[15]={ + 1, 1, 1, 4, 4, 4, 4, 4, 10, 10, 10, 10, 13, 13, 13 +}; + +const unsigned char waveMap[8]={ + 0, 1, 2, 3, 4, 5, 6, 6 +}; + +const char** DivPlatformPOKEY::getRegisterSheet() { + return regCheatSheetPOKEY; +} + +void DivPlatformPOKEY::acquire(short* bufL, short* bufR, size_t start, size_t len) { + if (useAltASAP) { + acquireASAP(bufL, start, len); + } else { + acquireMZ(bufL, start, len); + } +} + +void DivPlatformPOKEY::acquireMZ(short* buf, size_t start, size_t len) { + for (size_t h=start; h=14) { + oscBufDelay=0; + oscBuf[0]->data[oscBuf[0]->needle++]=pokey.outvol_0<<11; + oscBuf[1]->data[oscBuf[1]->needle++]=pokey.outvol_1<<11; + oscBuf[2]->data[oscBuf[2]->needle++]=pokey.outvol_2<<11; + oscBuf[3]->data[oscBuf[3]->needle++]=pokey.outvol_3<<11; + } + } +} + +void DivPlatformPOKEY::acquireASAP(short* buf, size_t start, size_t len) { + while (!writes.empty()) { + QueuedWrite w=writes.front(); + altASAP.write(w.addr, w.val); + writes.pop(); + } + + for (size_t h=start; h=2) { + oscBufDelay=0; + buf[h]=altASAP.sampleAudio(oscBuf); + } else { + buf[h]=altASAP.sampleAudio(); + } + } +} + +void DivPlatformPOKEY::tick(bool sysTick) { + for (int i=0; i<4; i++) { + chan[i].std.next(); + if (chan[i].std.vol.had) { + chan[i].outVol=VOL_SCALE_LINEAR(chan[i].vol&15,MIN(15,chan[i].std.vol.val),15); + chan[i].ctlChanged=true; + } + if (NEW_ARP_STRAT) { + chan[i].handleArp(); + } else if (chan[i].std.arp.had) { + if (!chan[i].inPorta) { + chan[i].baseFreq=NOTE_PERIODIC(parent->calcArp(chan[i].note,chan[i].std.arp.val)); + } + chan[i].freqChanged=true; + } + if (chan[i].std.duty.had) { + audctl=chan[i].std.duty.val; + audctlChanged=true; + } + if (chan[i].std.wave.had) { + chan[i].wave=chan[i].std.wave.val; + chan[i].ctlChanged=true; + chan[i].freqChanged=true; + } + if (chan[i].std.pitch.had) { + if (chan[i].std.pitch.mode) { + chan[i].pitch2+=chan[i].std.pitch.val; + CLAMP_VAR(chan[i].pitch2,-32768,32767); + } else { + chan[i].pitch2=chan[i].std.pitch.val; + } + chan[i].freqChanged=true; + } + } + + if (audctlChanged) { + audctlChanged=false; + rWrite(8,audctl); + for (int i=0; i<4; i++) { + chan[i].freqChanged=true; + chan[i].ctlChanged=true; + } + } + + for (int i=0; i<4; i++) { + if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER); + + if ((i==0 && !(audctl&64)) || (i==2 && !(audctl&32)) || i==1 || i==3) { + chan[i].freq/=7; + switch (chan[i].wave) { + case 6: + chan[i].freq/=5; + chan[i].freq>>=1; + break; + case 7: + if (audctl&1) { + chan[i].freq/=5; + } else { + chan[i].freq/=15; + } + chan[i].freq>>=1; + break; + default: + chan[i].freq>>=2; + break; + } + } else if ((i==0 && audctl&64) || (i==2 && audctl&32)) { + switch (chan[i].wave) { + case 6: + chan[i].freq<<=1; + chan[i].freq/=5; + break; + case 7: + chan[i].freq<<=1; + chan[i].freq/=15; + break; + } + } + + if (audctl&1 && !((i==0 && audctl&64) || (i==2 && audctl&32))) { + chan[i].freq>>=2; + } + + if (--chan[i].freq<0) chan[i].freq=0; + + // snap buzz periods + int minFreq8=255; + if (chan[i].wave==7) { + if ((i==0 && audctl&64) || (i==2 && audctl&32)) { + chan[i].freq=15*(chan[i].freq/15)+snapPeriodLong16[(chan[i].freq%15)]+1; + } else { + if (!(audctl&1)) chan[i].freq=15*(chan[i].freq/15)+snapPeriodLong[(chan[i].freq%15)]; + } + } else if (chan[i].wave==6) { + if ((i==0 && audctl&64) || (i==2 && audctl&32)) { + chan[i].freq=15*(chan[i].freq/15)+snapPeriodShort16[(chan[i].freq%15)]+1; + } else { + if (!(audctl&1)) chan[i].freq=15*(chan[i].freq/15)+snapPeriodShort[(chan[i].freq%15)]; + } + minFreq8=251; + } + + if ((i==0 && audctl&16) || (i==2 && audctl&8)) { + if (chan[i].freq>65535) chan[i].freq=65535; + } else { + if (chan[i].freq>minFreq8) chan[i].freq=minFreq8; + } + + // write frequency + if ((i==1 && audctl&16) || (i==3 && audctl&8)) { + // ignore - channel is paired + } else { + rWrite(i<<1,chan[i].freq&0xff); + if ((i==0 && audctl&16) || (i==2 && audctl&8)) { + rWrite((1+i)<<1,chan[i].freq>>8); + } + } + + if (chan[i].keyOff) { + chan[i].ctlChanged=true; + } + if (chan[i].keyOn) chan[i].keyOn=false; + if (chan[i].keyOff) chan[i].keyOff=false; + chan[i].freqChanged=false; + } + if (chan[i].ctlChanged) { + unsigned char val=((chan[i].active && !isMuted[i])?(chan[i].outVol&15):0)|(waveMap[chan[i].wave&7]<<5); + chan[i].ctlChanged=false; + if ((i==1 && audctl&16) || (i==3 && audctl&8)) { + // ignore - channel is paired + } else if ((i==0 && audctl&16) || (i==0 && audctl&8)) { + rWrite(1+(i<<1),0); + rWrite(3+(i<<1),val); + } else { + + rWrite(1+(i<<1),val); + } + } + } +} + +int DivPlatformPOKEY::dispatch(DivCommand c) { + switch (c.cmd) { + case DIV_CMD_NOTE_ON: { + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_POKEY); + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].baseFreq=NOTE_PERIODIC(c.value); + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + } + chan[c.chan].active=true; + chan[c.chan].keyOn=true; + chan[c.chan].macroInit(ins); + if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) { + chan[c.chan].outVol=chan[c.chan].vol; + chan[c.chan].ctlChanged=true; + } + chan[c.chan].insChanged=false; + break; + } + case DIV_CMD_NOTE_OFF: + chan[c.chan].active=false; + chan[c.chan].keyOff=true; + chan[c.chan].macroInit(NULL); + break; + case DIV_CMD_NOTE_OFF_ENV: + case DIV_CMD_ENV_RELEASE: + chan[c.chan].std.release(); + break; + case DIV_CMD_INSTRUMENT: + if (chan[c.chan].ins!=c.value || c.value2==1) { + chan[c.chan].ins=c.value; + chan[c.chan].insChanged=true; + } + break; + case DIV_CMD_VOLUME: + if (chan[c.chan].vol!=c.value) { + chan[c.chan].vol=c.value; + if (!chan[c.chan].std.vol.has) { + chan[c.chan].outVol=c.value; + if (chan[c.chan].active) { + chan[c.chan].ctlChanged=true; + } + } + } + break; + case DIV_CMD_GET_VOLUME: + if (chan[c.chan].std.vol.has) { + return chan[c.chan].vol; + } + return chan[c.chan].outVol; + break; + case DIV_CMD_PITCH: + chan[c.chan].pitch=c.value; + chan[c.chan].freqChanged=true; + break; + case DIV_CMD_WAVE: + chan[c.chan].wave=c.value; + chan[c.chan].ctlChanged=true; + break; + case DIV_CMD_STD_NOISE_MODE: + audctl=c.value&0xff; + audctlChanged=true; + break; + case DIV_CMD_NOTE_PORTA: { + int destFreq=NOTE_PERIODIC(c.value2); + bool return2=false; + if (destFreq>chan[c.chan].baseFreq) { + chan[c.chan].baseFreq+=c.value; + if (chan[c.chan].baseFreq>=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } else { + chan[c.chan].baseFreq-=c.value; + if (chan[c.chan].baseFreq<=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } + chan[c.chan].freqChanged=true; + if (return2) { + chan[c.chan].inPorta=false; + return 2; + } + break; + } + case DIV_CMD_LEGATO: + chan[c.chan].baseFreq=NOTE_PERIODIC(c.value); + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + break; + case DIV_CMD_PRE_PORTA: + if (chan[c.chan].active && c.value2) { + if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_POKEY)); + } + if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note); + chan[c.chan].inPorta=c.value; + break; + case DIV_CMD_GET_VOLMAX: + return 15; + break; + case DIV_CMD_MACRO_OFF: + chan[c.chan].std.mask(c.value,true); + break; + case DIV_CMD_MACRO_ON: + chan[c.chan].std.mask(c.value,false); + break; + case DIV_ALWAYS_SET_VOLUME: + return 1; + break; + default: + break; + } + return 1; +} + +void DivPlatformPOKEY::muteChannel(int ch, bool mute) { + isMuted[ch]=mute; + chan[ch].ctlChanged=true; +} + +void DivPlatformPOKEY::forceIns() { + for (int i=0; i<4; i++) { + chan[i].insChanged=true; + chan[i].ctlChanged=true; + chan[i].freqChanged=true; + } + audctlChanged=true; +} + +void* DivPlatformPOKEY::getChanState(int ch) { + return &chan[ch]; +} + +DivMacroInt* DivPlatformPOKEY::getChanMacroInt(int ch) { + return &chan[ch].std; +} + +DivDispatchOscBuffer* DivPlatformPOKEY::getOscBuffer(int ch) { + return oscBuf[ch]; +} + +unsigned char* DivPlatformPOKEY::getRegisterPool() { + if (useAltASAP) { + return const_cast(altASAP.getRegisterPool()); + } else { + return regPool; + } +} + +int DivPlatformPOKEY::getRegisterPoolSize() { + return 9; +} + +void DivPlatformPOKEY::reset() { + while (!writes.empty()) writes.pop(); + memset(regPool,0,16); + for (int i=0; i<4; i++) { + chan[i]=DivPlatformPOKEY::Channel(); + chan[i].std.setEngine(parent); + } + if (dumpWrites) { + addWrite(0xffffffff,0); + } + + if (useAltASAP) { + altASAP.reset(); + } else { + ResetPokeyState(&pokey); + } + + audctl=0; + audctlChanged=true; +} + +bool DivPlatformPOKEY::keyOffAffectsArp(int ch) { + return true; +} + +float DivPlatformPOKEY::getPostAmp() { + return 2.0f; +} + +void DivPlatformPOKEY::notifyInsDeletion(void* ins) { + for (int i=0; i<4; i++) { + chan[i].std.notifyInsDeletion((DivInstrument*)ins); + } +} + +void DivPlatformPOKEY::setFlags(const DivConfig& flags) { + if (flags.getInt("clockSel",0)) { + chipClock=COLOR_PAL*2.0/5.0; + } else { + chipClock=COLOR_NTSC/2.0; + } + CHECK_CUSTOM_CLOCK; + + if (useAltASAP) { + rate=chipClock/7; + for (int i=0; i<4; i++) { + oscBuf[i]->rate=rate/2; + } + altASAP.init(chipClock,rate); + } else { + rate=chipClock; + for (int i=0; i<4; i++) { + oscBuf[i]->rate=rate/14; + } + } +} + +void DivPlatformPOKEY::poke(unsigned int addr, unsigned short val) { + rWrite(addr,val); +} + +void DivPlatformPOKEY::poke(std::vector& wlist) { + for (DivRegWrite& i: wlist) rWrite(i.addr,i.val); +} + +int DivPlatformPOKEY::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) { + parent=p; + dumpWrites=false; + skipRegisterWrites=false; + oscBufDelay=0; + for (int i=0; i<4; i++) { + isMuted[i]=false; + oscBuf[i]=new DivDispatchOscBuffer; + } + + if (!useAltASAP) { + MZPOKEYSND_Init(&pokey); + } + + setFlags(flags); + reset(); + return 6; +} + +void DivPlatformPOKEY::quit() { + for (int i=0; i<4; i++) { + delete oscBuf[i]; + } +} + +void DivPlatformPOKEY::setAltASAP(bool value) { + useAltASAP=value; +} + +DivPlatformPOKEY::~DivPlatformPOKEY() { +} diff --git a/src/engine/platform/pokey.h b/src/engine/platform/pokey.h new file mode 100644 index 00000000..e68178a2 --- /dev/null +++ b/src/engine/platform/pokey.h @@ -0,0 +1,87 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef _POKEY_H +#define _POKEY_H + +#include "../dispatch.h" +#include + +extern "C" { +#include "sound/pokey/mzpokeysnd.h" +} + +#include "sound/pokey/AltASAP.hpp" + + +class DivPlatformPOKEY: public DivDispatch { + struct Channel: public SharedChannel { + unsigned char wave; + bool ctlChanged; + Channel(): + SharedChannel(15), + wave(5), + ctlChanged(true) {} + }; + Channel chan[4]; + DivDispatchOscBuffer* oscBuf[4]; + bool isMuted[4]; + struct QueuedWrite { + unsigned char addr; + unsigned char val; + QueuedWrite(unsigned char a, unsigned char v): addr(a), val(v) {} + }; + std::queue writes; + unsigned char audctl; + bool audctlChanged; + unsigned char oscBufDelay; + PokeyState pokey; + AltASAP::Pokey altASAP; + bool useAltASAP; + unsigned char regPool[16]; + friend void putDispatchChip(void*,int); + friend void putDispatchChan(void*,int,int); + public: + void acquire(short* bufL, short* bufR, size_t start, size_t len); + void acquireMZ(short* buf, size_t start, size_t len); + void acquireASAP(short* buf, size_t start, size_t len); + int dispatch(DivCommand c); + void* getChanState(int chan); + DivMacroInt* getChanMacroInt(int ch); + DivDispatchOscBuffer* getOscBuffer(int chan); + unsigned char* getRegisterPool(); + int getRegisterPoolSize(); + void reset(); + void forceIns(); + void tick(bool sysTick=true); + void muteChannel(int ch, bool mute); + bool keyOffAffectsArp(int ch); + float getPostAmp(); + void setFlags(const DivConfig& flags); + void notifyInsDeletion(void* ins); + void poke(unsigned int addr, unsigned short val); + void poke(std::vector& wlist); + const char** getRegisterSheet(); + int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags); + void quit(); + void setAltASAP(bool useAltASAP); + ~DivPlatformPOKEY(); +}; + +#endif diff --git a/src/engine/platform/rf5c68.cpp b/src/engine/platform/rf5c68.cpp index 5e84820b..58732f49 100644 --- a/src/engine/platform/rf5c68.cpp +++ b/src/engine/platform/rf5c68.cpp @@ -61,6 +61,11 @@ void DivPlatformRF5C68::acquire(short* bufL, short* bufR, size_t start, size_t l buf[8],buf[9],buf[10],buf[11],buf[12],buf[13],buf[14],buf[15] }; size_t pos=start; + + for (int i=0; i<16; i++) { + memset(buf[i],0,256*sizeof(short)); + } + while (len > 0) { size_t blockLen=MIN(len,256); short* bufPtrs[2]={&bufL[pos],&bufR[pos]}; diff --git a/src/engine/platform/segapcm.cpp b/src/engine/platform/segapcm.cpp index 80f405db..30d3b804 100644 --- a/src/engine/platform/segapcm.cpp +++ b/src/engine/platform/segapcm.cpp @@ -96,24 +96,16 @@ void DivPlatformSegaPCM::tick(bool sysTick) { } if (parent->song.newSegaPCM) if (chan[i].std.panL.had) { - if (chan[i].isNewSegaPCM) { - chan[i].chPanL=chan[i].std.panL.val&127; - chan[i].chVolL=(chan[i].outVol*chan[i].chPanL)/127; - } else { - chan[i].chVolL=chan[i].std.panL.val&127; - } + chan[i].chPanL=chan[i].std.panL.val&127; + chan[i].chVolL=(chan[i].outVol*chan[i].chPanL)/127; if (dumpWrites) { addWrite(0x10002+(i<<3),chan[i].chVolL); } } if (parent->song.newSegaPCM) if (chan[i].std.panR.had) { - if (chan[i].isNewSegaPCM) { - chan[i].chPanR=chan[i].std.panR.val&127; - chan[i].chVolR=(chan[i].outVol*chan[i].chPanR)/127; - } else { - chan[i].chVolR=chan[i].std.panR.val&127; - } + chan[i].chPanR=chan[i].std.panR.val&127; + chan[i].chVolR=(chan[i].outVol*chan[i].chPanR)/127; if (dumpWrites) { addWrite(0x10003+(i<<3),chan[i].chVolR); } @@ -287,7 +279,7 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) { if (!chan[c.chan].std.vol.has) { chan[c.chan].outVol=c.value; } - if (parent->song.newSegaPCM && chan[c.chan].isNewSegaPCM) { + if (parent->song.newSegaPCM) { chan[c.chan].chVolL=(c.value*chan[c.chan].chPanL)/127; chan[c.chan].chVolR=(c.value*chan[c.chan].chPanR)/127; } else { @@ -311,7 +303,7 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) { chan[c.chan].ins=c.value; break; case DIV_CMD_PANNING: { - if (parent->song.newSegaPCM && chan[c.chan].isNewSegaPCM) { + if (parent->song.newSegaPCM) { chan[c.chan].chPanL=c.value>>1; chan[c.chan].chPanR=c.value2>>1; chan[c.chan].chVolL=(chan[c.chan].outVol*chan[c.chan].chPanL)/127; diff --git a/src/engine/platform/sound/pokey/AltASAP.cpp b/src/engine/platform/sound/pokey/AltASAP.cpp new file mode 100644 index 00000000..21bc31a2 --- /dev/null +++ b/src/engine/platform/sound/pokey/AltASAP.cpp @@ -0,0 +1,653 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * Original author: Piotr Fusik (http://asap.sourceforge.net) + * Rewritten based on Mikey emulation by Waldemar Pawlaszek + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "AltASAP.hpp" +#include +#include +#include +#include +#include + +namespace AltASAP +{ + +namespace +{ + +static constexpr int64_t CNT_MAX = std::numeric_limits::max() & ~7; +static constexpr int MuteFrequency = 1; +static constexpr int MuteInit = 2; +static constexpr int MuteSerialInput = 8; +//just some magick value to match the audio level of mzpokeysnd +static constexpr int16_t MAGICK_VOLUME_BOOSTER = 160; +static constexpr int16_t MAGICK_OSC_VOLUME_BOOSTER = 4; + +struct PokeyBase +{ + int64_t mPolyIndex; + int mAudctl; + int mSkctl; + bool mInit; + std::array mPoly9Lookup; + std::array mPoly17Lookup; + + + PokeyBase() : mPolyIndex{ 15 * 31 * 131071 }, mAudctl{ 0 }, mSkctl{ 3 }, mInit{ false }, mPoly9Lookup{}, mPoly17Lookup{} + { + int reg = 0x1ff; + for ( int i = 0; i < 511; i++ ) + { + reg = ( ( ( reg >> 5 ^ reg ) & 1 ) << 8 ) + ( reg >> 1 ); + mPoly9Lookup[i] = reg & 0xff; + } + reg = 0x1ffff; + for ( int i = 0; i < 16385; i++ ) + { + reg = ( ( ( reg >> 5 ^ reg ) & 0xff ) << 9 ) + ( reg >> 8 ); + mPoly17Lookup[i] = reg >> 1 & 0xff; + } + + } +}; + + +/* + "Queue" holding event timepoints. + - 4 channel timer fire points + - 1 sample point + Three LSBs are used to encode event kind: 0-3 are channels, 4 is sampling. + Channel sequence is 2,3,0,1 +*/ +class ActionQueue +{ +public: + + ActionQueue() : mTab{ CNT_MAX | 0, CNT_MAX | 1, CNT_MAX | 2, CNT_MAX | 3, CNT_MAX | 4 } + { + } + + void insert( int idx, int64_t value ) + { + assert( idx < (int)mTab.size() ); + idx ^= 2; + mTab[idx] = value | idx; + } + + void insertSampling( int64_t value ) + { + mTab[4] = value | 4; + } + + void disable( int idx ) + { + assert( idx < (int)mTab.size() ); + idx ^= 2; + mTab[idx] = CNT_MAX | idx; + } + + bool enqueued( int idx ) + { + assert( idx < (int)mTab.size() ); + idx ^= 2; + return mTab[idx] < CNT_MAX; + } + + int64_t pop() + { + int64_t min1 = std::min( mTab[0], mTab[1] ); + int64_t min2 = std::min( mTab[2], mTab[3] ); + int64_t min3 = std::min( min1, mTab[4] ); + int64_t min4 = std::min( min2, min3 ); + + return min4 ^ 2; + } + +private: + std::array mTab; +}; + +class AudioChannel +{ +public: + AudioChannel( ActionQueue& queue, uint32_t number ) : mQueue{ queue }, mNumber{ number }, mAudf{ 0 }, mAudc{ 0 }, mPeriodCycles{ 28 }, mMute{ MuteFrequency }, mDelta{ 0 }, mOut{ 0 } + { + } + + int16_t getOutput() const + { + return (int16_t)mDelta; + } + + void fillRegisterPool( uint8_t* regs ) + { + regs[0] = (uint8_t)mAudf; + regs[1] = (uint8_t)mAudc; + } + + void renewTrigger( int64_t cycle ) + { + overrideTrigger( cycle + mPeriodCycles ); + } + + void overrideTrigger( int64_t cycle ) + { + mQueue.insert( mNumber, cycle << 3 ); + } + + void doStimer( int64_t cycle ) + { + if ( mQueue.enqueued( mNumber ) ) + renewTrigger( cycle ); + } + + void trigger( int64_t cycle, PokeyBase const& pokey ) + { + renewTrigger( cycle ); + if ( ( mAudc & 0xb0 ) == 0xa0 ) + mOut ^= 1; + else if ( ( mAudc & 0x10 ) != 0 || pokey.mInit ) + return; + else + { + int64_t poly = cycle + pokey.mPolyIndex - mNumber; + if ( mAudc < 0x80 && ( 0x65bd44e0 & 1 << ( poly % 31 ) ) == 0 ) // 0000011100100010101111011010011 + return; + if ( ( mAudc & 0x20 ) != 0 ) + mOut ^= 1; + else + { + uint32_t newOut; + if ( ( mAudc & 0x40 ) != 0 ) + newOut = 0x5370u >> (int)( poly % 15 ); // 000011101100101 + else if ( pokey.mAudctl < 0x80 ) + { + poly %= 131071; + newOut = pokey.mPoly17Lookup[poly >> 3] >> ( poly & 7 ); + } + else + newOut = pokey.mPoly9Lookup[poly % 511]; + newOut &= 1; + if ( mOut == newOut ) + return; + mOut = newOut; + } + } + toggle(); + } + + uint32_t mute() const + { + return mMute; + } + + uint32_t audf() const + { + return mAudf; + } + + uint32_t audc() const + { + return mAudc; + } + + void setAudf( uint32_t value ) + { + mAudf = value; + } + + void setAudc( int64_t cycle, uint32_t value ) + { + if ( mAudc == value ) + return; + mAudc = value; + int32_t volume = value & 0x0f; + if ( ( value & 0x10 ) != 0 ) + { + mDelta = volume * MAGICK_VOLUME_BOOSTER; + } + else + { + muteUltrasound( cycle ); + if ( mDelta > 0 ) + mDelta = volume * MAGICK_VOLUME_BOOSTER; + else + mDelta = -volume * MAGICK_VOLUME_BOOSTER; + } + } + + void toggle() + { + mDelta = -mDelta; + } + + void setPeriodCycles( int64_t value ) + { + mPeriodCycles = value; + } + + void setMute( bool enable, int mask, int64_t cycle ) + { + if ( enable ) + { + mMute |= mask; + mQueue.disable( mNumber ); + } + else + { + mMute &= ~mask; + if ( mMute == 0 && !mQueue.enqueued( mNumber ) ) + overrideTrigger( cycle ); + } + } + + void muteUltrasound( int64_t cycle ) + { + static constexpr int UltrasoundCycles = 112; + setMute( mPeriodCycles <= UltrasoundCycles && ( mAudc & 0xb0 ) == 0xa0, MuteFrequency, cycle ); + } + +private: + ActionQueue& mQueue; + uint32_t mNumber; + uint32_t mAudf; + uint32_t mAudc; + int64_t mPeriodCycles; + + uint32_t mMute; + int32_t mDelta; + uint32_t mOut; +}; + +} + + +class PokeyPimpl : public PokeyBase +{ +public: + + PokeyPimpl( uint32_t pokeyClock, uint32_t sampleRate ) : PokeyBase{}, mQueue{ std::make_unique() }, + mAudioChannels{ AudioChannel{ *mQueue, 0u }, AudioChannel{ *mQueue, 1u }, AudioChannel{ *mQueue, 2u }, AudioChannel{ *mQueue, 3u } }, + mRegisterPool{}, mTick{}, mNextTick{}, + mReloadCycles1{ 28 }, mReloadCycles3{ 28 }, mDivCycles{ 28 }, mSampleRate{ sampleRate }, mSamplesRemainder{}, + mTicksPerSample{ ( pokeyClock * 8 ) / mSampleRate, ( pokeyClock * 8 ) % mSampleRate } + { + std::fill_n( mRegisterPool.data(), mRegisterPool.size(), (uint8_t)0xff ); + enqueueSampling(); + } + + ~PokeyPimpl() {} + + void write( uint8_t address, uint8_t value ) + { + auto cycle = mTick >> 3; + + switch ( address & 0xf ) + { + case 0x00: + if ( value == mAudioChannels[0].audf() ) + break; + mAudioChannels[0].setAudf( value ); + switch ( mAudctl & 0x50 ) + { + case 0x00: + mAudioChannels[0].setPeriodCycles( mDivCycles * ( value + 1 ) ); + break; + case 0x10: + mAudioChannels[1].setPeriodCycles( mDivCycles * ( value + ( mAudioChannels[1].audf() << 8 ) + 1 ) ); + mReloadCycles1 = mDivCycles * ( value + 1 ); + mAudioChannels[1].muteUltrasound( cycle ); + break; + case 0x40: + mAudioChannels[0].setPeriodCycles( value + 4 ); + break; + case 0x50: + mAudioChannels[1].setPeriodCycles( value + ( mAudioChannels[1].audf() << 8 ) + 7 ); + mReloadCycles1 = value + 4; + mAudioChannels[1].muteUltrasound( cycle ); + break; + default: + assert( false ); + } + mAudioChannels[0].muteUltrasound( cycle ); + break; + case 0x01: + mAudioChannels[0].setAudc( cycle, value ); + break; + case 0x02: + if ( value == mAudioChannels[1].audf() ) + break; + mAudioChannels[1].setAudf( value ); + switch ( mAudctl & 0x50 ) + { + case 0x00: + case 0x40: + mAudioChannels[1].setPeriodCycles( mDivCycles * ( value + 1 ) ); + break; + case 0x10: + mAudioChannels[1].setPeriodCycles( mDivCycles * ( mAudioChannels[0].audf() + ( value << 8 ) + 1 ) ); + break; + case 0x50: + mAudioChannels[1].setPeriodCycles( mAudioChannels[0].audf() + ( value << 8 ) + 7 ); + break; + default: + assert( false ); + } + mAudioChannels[1].muteUltrasound( cycle ); + break; + case 0x03: + mAudioChannels[1].setAudc( cycle, value ); + break; + case 0x04: + if ( value == mAudioChannels[2].audf() ) + break; + mAudioChannels[2].setAudf( value ); + switch ( mAudctl & 0x28 ) + { + case 0x00: + mAudioChannels[2].setPeriodCycles( mDivCycles * ( value + 1 ) ); + break; + case 0x08: + mAudioChannels[3].setPeriodCycles( mDivCycles * ( value + ( mAudioChannels[3].audf() << 8 ) + 1 ) ); + mReloadCycles3 = mDivCycles * ( value + 1 ); + mAudioChannels[3].muteUltrasound( cycle ); + break; + case 0x20: + mAudioChannels[2].setPeriodCycles( value + 4 ); + break; + case 0x28: + mAudioChannels[3].setPeriodCycles( value + ( mAudioChannels[3].audf() << 8 ) + 7 ); + mReloadCycles3 = value + 4; + mAudioChannels[3].muteUltrasound( cycle ); + break; + default: + assert( false ); + } + mAudioChannels[2].muteUltrasound( cycle ); + break; + case 0x05: + mAudioChannels[2].setAudc( cycle, value ); + break; + case 0x06: + if ( value == mAudioChannels[3].audf() ) + break; + mAudioChannels[3].setAudf( value ); + switch ( mAudctl & 0x28 ) + { + case 0x00: + case 0x20: + mAudioChannels[3].setPeriodCycles( mDivCycles * ( value + 1 ) ); + break; + case 0x08: + mAudioChannels[3].setPeriodCycles( mDivCycles * ( mAudioChannels[2].audf() + ( value << 8 ) + 1 ) ); + break; + case 0x28: + mAudioChannels[3].setPeriodCycles( mAudioChannels[2].audf() + ( value << 8 ) + 7 ); + break; + default: + assert( false ); + } + mAudioChannels[3].muteUltrasound( cycle ); + break; + case 0x07: + mAudioChannels[3].setAudc( cycle, value ); + break; + case 0x08: + if ( value == mAudctl ) + break; + mAudctl = value; + mDivCycles = ( value & 1 ) != 0 ? 114 : 28; + switch ( value & 0x50 ) + { + case 0x00: + mAudioChannels[0].setPeriodCycles( mDivCycles * ( mAudioChannels[0].audf() + 1 ) ); + mAudioChannels[1].setPeriodCycles( mDivCycles * ( mAudioChannels[1].audf() + 1 ) ); + break; + case 0x10: + mAudioChannels[0].setPeriodCycles( (int64_t)mDivCycles << 8 ); + mAudioChannels[1].setPeriodCycles( mDivCycles * ( mAudioChannels[0].audf() + ( mAudioChannels[1].audf() << 8 ) + 1 ) ); + mReloadCycles1 = mDivCycles * ( mAudioChannels[0].audf() + 1 ); + break; + case 0x40: + mAudioChannels[0].setPeriodCycles( mAudioChannels[0].audf() + 4 ); + mAudioChannels[1].setPeriodCycles( mDivCycles * ( mAudioChannels[1].audf() + 1 ) ); + break; + case 0x50: + mAudioChannels[0].setPeriodCycles( 256 ); + mAudioChannels[1].setPeriodCycles( mAudioChannels[0].audf() + ( mAudioChannels[1].audf() << 8 ) + 7 ); + mReloadCycles1 = mAudioChannels[0].audf() + 4; + break; + default: + assert( false ); + } + mAudioChannels[0].muteUltrasound( cycle ); + mAudioChannels[1].muteUltrasound( cycle ); + switch ( value & 0x28 ) + { + case 0x00: + mAudioChannels[2].setPeriodCycles( mDivCycles * ( mAudioChannels[2].audf() + 1 ) ); + mAudioChannels[3].setPeriodCycles( mDivCycles * ( mAudioChannels[3].audf() + 1 ) ); + break; + case 0x08: + mAudioChannels[2].setPeriodCycles( (int64_t)mDivCycles << 8 ); + mAudioChannels[3].setPeriodCycles( mDivCycles * ( mAudioChannels[2].audf() + ( mAudioChannels[3].audf() << 8 ) + 1 ) ); + mReloadCycles3 = mDivCycles * ( mAudioChannels[2].audf() + 1 ); + break; + case 0x20: + mAudioChannels[2].setPeriodCycles( mAudioChannels[2].audf() + 4 ); + mAudioChannels[3].setPeriodCycles( mDivCycles * ( mAudioChannels[3].audf() + 1 ) ); + break; + case 0x28: + mAudioChannels[2].setPeriodCycles( 256 ); + mAudioChannels[3].setPeriodCycles( mAudioChannels[2].audf() + ( mAudioChannels[3].audf() << 8 ) + 7 ); + mReloadCycles3 = mAudioChannels[2].audf() + 4; + break; + default: + assert( false ); + } + mAudioChannels[2].muteUltrasound( cycle ); + mAudioChannels[3].muteUltrasound( cycle ); + initMute( cycle ); + break; + case 0x09: + for ( int i = 0; i < 4; i++ ) + mAudioChannels[i].doStimer( cycle ); + break; + case 0x0f: + { + if ( value == mSkctl ) + break; + mSkctl = value; + bool init = ( value & 3 ) == 0; + if ( mInit && !init ) + mPolyIndex = ( ( mAudctl & 0x80 ) != 0 ? 15 * 31 * 511 - 1 : 15 * 31 * 131071 - 1 ) - cycle; + mInit = init; + initMute( cycle ); + mAudioChannels[2].setMute( ( value & 0x10 ) != 0, MuteSerialInput, cycle ); + mAudioChannels[3].setMute( ( value & 0x10 ) != 0, MuteSerialInput, cycle ); + break; + } + default: + break; + } + } + + int16_t sampleAudio( DivDispatchOscBuffer** oscb ) + { + for ( ;; ) + { + int64_t value = mQueue->pop(); + if ( ( value & 7 ) == 6 ) // 6 == 4 ^ 2 + { + int16_t ch0 = mAudioChannels[0].getOutput(); + int16_t ch1 = mAudioChannels[1].getOutput(); + int16_t ch2 = mAudioChannels[2].getOutput(); + int16_t ch3 = mAudioChannels[3].getOutput(); + + if ( oscb != nullptr ) + { + oscb[0]->data[oscb[0]->needle++]=ch0 * MAGICK_OSC_VOLUME_BOOSTER; + oscb[1]->data[oscb[1]->needle++]=ch1 * MAGICK_OSC_VOLUME_BOOSTER; + oscb[2]->data[oscb[2]->needle++]=ch2 * MAGICK_OSC_VOLUME_BOOSTER; + oscb[3]->data[oscb[3]->needle++]=ch3 * MAGICK_OSC_VOLUME_BOOSTER; + } + + enqueueSampling(); + return ch0 + ch1 + ch2 + ch3; + } + else + { + fireTimer( value ); + } + } + } + + uint8_t const* getRegisterPool() + { + for ( size_t i = 0; i < mAudioChannels.size(); ++i ) + { + mAudioChannels[i].fillRegisterPool( mRegisterPool.data() + 2 * i ); + } + + mRegisterPool[8] = mAudctl; + + return mRegisterPool.data(); + } + +private: + + void initMute( int64_t cycle ) + { + mAudioChannels[0].setMute( mInit && ( mAudctl & 0x40 ) == 0, MuteInit, cycle ); + mAudioChannels[1].setMute( mInit && ( mAudctl & 0x50 ) != 0x50, MuteInit, cycle ); + mAudioChannels[2].setMute( mInit && ( mAudctl & 0x20 ) == 0, MuteInit, cycle ); + mAudioChannels[3].setMute( mInit && ( mAudctl & 0x28 ) != 0x28, MuteInit, cycle ); + } + + void fireTimer( int64_t tick ) + { + mTick = tick & ~7; + size_t ch = tick & 3; + auto cycle = tick >> 3; + + switch ( ch ) + { + case 0: + if ( ( mSkctl & 0x88 ) == 8 ) // two-tone, sending 1 (i.e. timer1) + mAudioChannels[1].renewTrigger( cycle ); + mAudioChannels[0].trigger( cycle, *this ); + break; + case 1: + if ( ( mAudctl & 0x10 ) != 0 ) + mAudioChannels[0].overrideTrigger( cycle + mReloadCycles1 ); + else if ( ( mSkctl & 8 ) != 0 ) // two-tone + mAudioChannels[0].renewTrigger( cycle ); + mAudioChannels[1].trigger( cycle, *this ); + break; + case 2: + if ( ( mAudctl & 4 ) != 0 && mAudioChannels[0].getOutput() > 0 && mAudioChannels[0].mute() == 0 ) + mAudioChannels[0].toggle(); + mAudioChannels[2].trigger( cycle, *this ); + break; + case 3: + if ( ( mAudctl & 8 ) != 0 ) + mAudioChannels[2].overrideTrigger( cycle + mReloadCycles3 ); + if ( ( mAudctl & 2 ) != 0 && mAudioChannels[1].getOutput() > 0 && mAudioChannels[1].mute() == 0 ) + mAudioChannels[1].toggle(); + mAudioChannels[3].trigger( cycle, *this ); + break; + default: + break; + } + } + + void enqueueSampling() + { + mTick = mNextTick & ~7; + mNextTick = mNextTick + mTicksPerSample.first; + mSamplesRemainder += mTicksPerSample.second; + if ( mSamplesRemainder > mSampleRate ) + { + mSamplesRemainder %= mSampleRate; + mNextTick += 1; + } + + mQueue->insertSampling( mNextTick & ~7 ); + } + + +private: + + std::unique_ptr mQueue; + std::array mAudioChannels; + + std::array mRegisterPool; + + uint64_t mTick; + uint64_t mNextTick; + int64_t mReloadCycles1; + int64_t mReloadCycles3; + int64_t mDivCycles; + uint32_t mSampleRate; + uint32_t mSamplesRemainder; + std::pair mTicksPerSample; +}; + +//Initializing periods with safe defaults +Pokey::Pokey() : mPokeyClock{ (uint32_t)COLOR_NTSC / 2 }, mSampleRate{ mPokeyClock / 7 }, mPokey{} +{ +} + +void Pokey::init( uint32_t pokeyClock, uint32_t sampleRate ) +{ + mPokey.reset(); + mPokeyClock = pokeyClock; + mSampleRate = sampleRate; +} + +void Pokey::reset() +{ + mPokey = std::make_unique( mPokeyClock, mSampleRate ); +} + +Pokey::~Pokey() +{ +} + +void Pokey::write( uint8_t address, uint8_t value ) +{ + assert( mPokey ); + mPokey->write( address, value ); +} + +int16_t Pokey::sampleAudio( DivDispatchOscBuffer** oscb ) +{ + assert( mPokey ); + return mPokey->sampleAudio( oscb ); +} + +uint8_t const* Pokey::getRegisterPool() +{ + assert( mPokey ); + return mPokey->getRegisterPool(); +} + +} diff --git a/src/engine/platform/sound/pokey/AltASAP.hpp b/src/engine/platform/sound/pokey/AltASAP.hpp new file mode 100644 index 00000000..c159e9d5 --- /dev/null +++ b/src/engine/platform/sound/pokey/AltASAP.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include +#include + +// can you forgive me +#include "../../../dispatch.h" + +namespace AltASAP +{ + +class PokeyPimpl; + +class Pokey +{ +public: + + Pokey(); + void init( uint32_t pokeyClock, uint32_t sampleRate ); + ~Pokey(); + + void write( uint8_t address, uint8_t value ); + int16_t sampleAudio( DivDispatchOscBuffer** oscb = nullptr ); + + uint8_t const* getRegisterPool(); + + void reset(); + +private: + uint32_t mPokeyClock; + uint32_t mSampleRate; + std::unique_ptr mPokey; +}; + +} diff --git a/src/engine/platform/sound/pokey/mzpokeysnd.c b/src/engine/platform/sound/pokey/mzpokeysnd.c new file mode 100644 index 00000000..9594bd5a --- /dev/null +++ b/src/engine/platform/sound/pokey/mzpokeysnd.c @@ -0,0 +1,1655 @@ +/* + * mzpokeysnd.c - POKEY sound chip emulation, v1.6 + * + * Copyright (C) 2002 Michael Borisov + * Copyright (C) 2002-2014 Atari800 development team (see DOC/CREDITS) + * + * This file is part of the Atari800 emulator project which emulates + * the Atari 400, 800, 800XL, 130XE, and 5200 8-bit computers. + * + * Atari800 is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Atari800 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Atari800; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +// additional modifications for Furnace by tildearrow + +#define _USE_MATH_DEFINES +#include +#include + +#include "pokey.h" +#include "mzpokeysnd.h" + +#define CONSOLE_VOL 8 +#ifdef NONLINEAR_MIXING +static const double pokeymix[61+CONSOLE_VOL] = { /* Nonlinear POKEY mixing array */ +0.000000, 5.169146, 10.157015, 15.166247, +20.073793, 24.927443, 29.728237, 34.495266, +39.181262, 43.839780, 48.429508, 52.932530, +57.327319, 61.586304, 65.673220, 69.547672, +73.207846, 76.594474, 79.739231, 82.631161, +85.300361, 87.750638, 90.020656, 92.108334, +94.051256, 95.848478, 97.521287, 99.080719, +100.540674, 101.902750, 103.185339, 104.375596, +105.491149, 106.523735, 107.473511, 108.361458, +109.185669, 109.962251, 110.685574, 111.367150, +112.008476, 112.612760, 113.185603, 113.722735, +114.227904, 114.712206, 115.171007, 115.605730, +116.024396, 116.416097, 116.803169, 117.155108, +117.532921, 117.835494, 118.196180, 118.502785, +118.825177, 119.138170, 119.421378, 119.734493, +/* need to add CONSOLE_VOL extra copies of the last val */ +120.000000,120.0,120.0,120.0,120.0,120.0,120.0,120.0,120.0}; +#endif + +/* Poly tables */ +static int poly4tbl[15]; +static int poly5tbl[31]; +static unsigned char poly17tbl[131071]; +static int poly9tbl[511]; + +/* Forward declarations for ResetPokeyState */ + +int readout0_normal(PokeyState* ps); +void event0_pure(PokeyState* ps, int p5v, int p4v, int p917v); + +int readout1_normal(PokeyState* ps); +void event1_pure(PokeyState* ps, int p5v, int p4v, int p917v); + +int readout2_normal(PokeyState* ps); +void event2_pure(PokeyState* ps, int p5v, int p4v, int p917v); + +int readout3_normal(PokeyState* ps); +void event3_pure(PokeyState* ps, int p5v, int p4v, int p917v); + +void ResetPokeyState(PokeyState* ps) +{ + /* Poly positions */ + ps->poly4pos = 0; + ps->poly5pos = 0; + ps->poly9pos = 0; + ps->poly17pos = 0; + + /* Global Pokey controls */ + ps->mdivk = 28; + + ps->selpoly9 = 0; + ps->c0_hf = 0; + ps->c1_f0 = 0; + ps->c2_hf = 0; + ps->c3_f2 = 0; + + /* SKCTL for two-tone mode */ + ps->skctl = 0; + + ps->outvol_all = 0; + ps->forcero = 0; + + /* Channel 0 state */ + ps->readout_0 = readout0_normal; + ps->event_0 = event0_pure; + + ps->c0divpos = 1000; + ps->c0divstart = 1000; + ps->c0divstart_p = 1000; + ps->c0diva = 255; + + ps->c0t1 = 0; + ps->c0t2 = 0; + ps->c0t3 = 0; + + ps->c0sw1 = 0; + ps->c0sw2 = 0; + ps->c0sw3 = 0; + ps->c0sw4 = 0; + ps->c0vo = 1; + +#ifndef NONLINEAR_MIXING + ps->c0stop = 1; +#endif + + ps->vol0 = 0; + + ps->outvol_0 = 0; + + + /* Channel 1 state */ + ps->readout_1 = readout1_normal; + ps->event_1 = event1_pure; + + ps->c1divpos = 1000; + ps->c1divstart = 1000; + ps->c1diva = 255; + + ps->c1t1 = 0; + ps->c1t2 = 0; + ps->c1t3 = 0; + + ps->c1sw1 = 0; + ps->c1sw2 = 0; + ps->c1sw3 = 0; + ps->c1sw4 = 0; + ps->c1vo = 1; + +#ifndef NONLINEAR_MIXING + ps->c1stop = 1; +#endif + + ps->vol1 = 0; + + ps->outvol_1 = 0; + + /* Channel 2 state */ + ps->readout_2 = readout2_normal; + ps->event_2 = event2_pure; + + ps->c2divpos = 1000; + ps->c2divstart = 1000; + ps->c2divstart_p = 1000; + ps->c2diva = 255; + + ps->c2t1 = 0; + ps->c2t2 = 0; + + ps->c2sw1 = 0; + ps->c2sw2 = 0; + ps->c2sw3 = 0; + + ps->c2vo = 0; + +#ifndef NONLINEAR_MIXING + ps->c2stop = 1; +#endif + + ps->vol2 = 0; + + ps->outvol_2 = 0; + + /* Channel 3 state */ + ps->readout_3 = readout3_normal; + ps->event_3 = event3_pure; + + ps->c3divpos = 1000; + ps->c3divstart = 1000; + ps->c3diva = 255; + + ps->c3t1 = 0; + ps->c3t2 = 0; + + ps->c3sw1 = 0; + ps->c3sw2 = 0; + ps->c3sw3 = 0; + + ps->c3vo = 0; + +#ifndef NONLINEAR_MIXING + ps->c3stop = 1; +#endif + + ps->vol3 = 0; + + ps->outvol_3 = 0; +} + +static void build_poly4(void) +{ + unsigned char c; + unsigned char i; + unsigned char poly4=1; + + for(i=0; i<15; i++) + { + poly4tbl[i] = ~poly4; + c = ((poly4>>2)&1) ^ ((poly4>>3)&1); + poly4 = ((poly4<<1)&15) + c; + } +} + +static void build_poly5(void) +{ + unsigned char c; + unsigned char i; + unsigned char poly5 = 1; + + for(i = 0; i < 31; i++) { + poly5tbl[i] = ~poly5; /* Inversion! Attention! */ + c = ((poly5 >> 2) ^ (poly5 >> 4)) & 1; + poly5 = ((poly5 << 1) & 31) + c; + } +} + +static void build_poly17(void) +{ + unsigned int c; + unsigned int i; + unsigned int poly17 = 1; + + for(i = 0; i < 131071; i++) { + poly17tbl[i] = (unsigned char) poly17; + c = ((poly17 >> 11) ^ (poly17 >> 16)) & 1; + poly17 = ((poly17 << 1) & 131071) + c; + } +} + +static void build_poly9(void) +{ + unsigned int c; + unsigned int i; + unsigned int poly9 = 1; + + for(i = 0; i < 511; i++) { + poly9tbl[i] = (unsigned char) poly9; + c = ((poly9 >> 3) ^ (poly9 >> 8)) & 1; + poly9 = ((poly9 << 1) & 511) + c; + } +} + +void advance_polies(PokeyState* ps, int tacts) +{ + ps->poly4pos = (tacts + ps->poly4pos) % 15; + ps->poly5pos = (tacts + ps->poly5pos) % 31; + ps->poly17pos = (tacts + ps->poly17pos) % 131071; + ps->poly9pos = (tacts + ps->poly9pos) % 511; +} + +/*********************************** + + READ OUTPUT 0 + + ************************************/ + +int readout0_vo(PokeyState* ps) +{ + return ps->vol0; +} + +int readout0_hipass(PokeyState* ps) +{ + if(ps->c0t2 ^ ps->c0t3) + return ps->vol0; + else return 0; +} + +int readout0_normal(PokeyState* ps) +{ + if(ps->c0t2) + return ps->vol0; + else return 0; +} + +/*********************************** + + READ OUTPUT 1 + + ************************************/ + +int readout1_vo(PokeyState* ps) +{ + return ps->vol1; +} + +int readout1_hipass(PokeyState* ps) +{ + if(ps->c1t2 ^ ps->c1t3) + return ps->vol1; + else return 0; +} + +int readout1_normal(PokeyState* ps) +{ + if(ps->c1t2) + return ps->vol1; + else return 0; +} + +/*********************************** + + READ OUTPUT 2 + + ************************************/ + +int readout2_vo(PokeyState* ps) +{ + return ps->vol2; +} + +int readout2_normal(PokeyState* ps) +{ + if(ps->c2t2) + return ps->vol2; + else return 0; +} + +/*********************************** + + READ OUTPUT 3 + + ************************************/ + +int readout3_vo(PokeyState* ps) +{ + return ps->vol3; +} + +int readout3_normal(PokeyState* ps) +{ + if(ps->c3t2) + return ps->vol3; + else return 0; +} + + +/*********************************** + + EVENT CHANNEL 0 + + ************************************/ + +void event0_pure(PokeyState* ps, int p5v, int p4v, int p917v) +{ + ps->c0t2 = !ps->c0t2; + ps->c0t1 = p5v; +} + +void event0_p5(PokeyState* ps, int p5v, int p4v, int p917v) +{ + if(ps->c0t1) + ps->c0t2 = !ps->c0t2; + ps->c0t1 = p5v; +} + +void event0_p4(PokeyState* ps, int p5v, int p4v, int p917v) +{ + ps->c0t2 = p4v; + ps->c0t1 = p5v; +} + +void event0_p917(PokeyState* ps, int p5v, int p4v, int p917v) +{ + ps->c0t2 = p917v; + ps->c0t1 = p5v; +} + +void event0_p4_p5(PokeyState* ps, int p5v, int p4v, int p917v) +{ + if(ps->c0t1) + ps->c0t2 = p4v; + ps->c0t1 = p5v; +} + +void event0_p917_p5(PokeyState* ps, int p5v, int p4v, int p917v) +{ + if(ps->c0t1) + ps->c0t2 = p917v; + ps->c0t1 = p5v; +} + +/*********************************** + + EVENT CHANNEL 1 + + ************************************/ + +void event1_pure(PokeyState* ps, int p5v, int p4v, int p917v) +{ + ps->c1t2 = !ps->c1t2; + ps->c1t1 = p5v; +} + +void event1_p5(PokeyState* ps, int p5v, int p4v, int p917v) +{ + if(ps->c1t1) + ps->c1t2 = !ps->c1t2; + ps->c1t1 = p5v; +} + +void event1_p4(PokeyState* ps, int p5v, int p4v, int p917v) +{ + ps->c1t2 = p4v; + ps->c1t1 = p5v; +} + +void event1_p917(PokeyState* ps, int p5v, int p4v, int p917v) +{ + ps->c1t2 = p917v; + ps->c1t1 = p5v; +} + +void event1_p4_p5(PokeyState* ps, int p5v, int p4v, int p917v) +{ + if(ps->c1t1) + ps->c1t2 = p4v; + ps->c1t1 = p5v; +} + +void event1_p917_p5(PokeyState* ps, int p5v, int p4v, int p917v) +{ + if(ps->c1t1) + ps->c1t2 = p917v; + ps->c1t1 = p5v; +} + +/*********************************** + + EVENT CHANNEL 2 + + ************************************/ + +void event2_pure(PokeyState* ps, int p5v, int p4v, int p917v) +{ + ps->c2t2 = !ps->c2t2; + ps->c2t1 = p5v; + /* high-pass clock for channel 0 */ + ps->c0t3 = ps->c0t2; +} + +void event2_p5(PokeyState* ps, int p5v, int p4v, int p917v) +{ + if(ps->c2t1) + ps->c2t2 = !ps->c2t2; + ps->c2t1 = p5v; + /* high-pass clock for channel 0 */ + ps->c0t3 = ps->c0t2; +} + +void event2_p4(PokeyState* ps, int p5v, int p4v, int p917v) +{ + ps->c2t2 = p4v; + ps->c2t1 = p5v; + /* high-pass clock for channel 0 */ + ps->c0t3 = ps->c0t2; +} + +void event2_p917(PokeyState* ps, int p5v, int p4v, int p917v) +{ + ps->c2t2 = p917v; + ps->c2t1 = p5v; + /* high-pass clock for channel 0 */ + ps->c0t3 = ps->c0t2; +} + +void event2_p4_p5(PokeyState* ps, int p5v, int p4v, int p917v) +{ + if(ps->c2t1) + ps->c2t2 = p4v; + ps->c2t1 = p5v; + /* high-pass clock for channel 0 */ + ps->c0t3 = ps->c0t2; +} + +void event2_p917_p5(PokeyState* ps, int p5v, int p4v, int p917v) +{ + if(ps->c2t1) + ps->c2t2 = p917v; + ps->c2t1 = p5v; + /* high-pass clock for channel 0 */ + ps->c0t3 = ps->c0t2; +} + +/*********************************** + + EVENT CHANNEL 3 + + ************************************/ + +void event3_pure(PokeyState* ps, int p5v, int p4v, int p917v) +{ + ps->c3t2 = !ps->c3t2; + ps->c3t1 = p5v; + /* high-pass clock for channel 1 */ + ps->c1t3 = ps->c1t2; +} + +void event3_p5(PokeyState* ps, int p5v, int p4v, int p917v) +{ + if(ps->c3t1) + ps->c3t2 = !ps->c3t2; + ps->c3t1 = p5v; + /* high-pass clock for channel 1 */ + ps->c1t3 = ps->c1t2; +} + +void event3_p4(PokeyState* ps, int p5v, int p4v, int p917v) +{ + ps->c3t2 = p4v; + ps->c3t1 = p5v; + /* high-pass clock for channel 1 */ + ps->c1t3 = ps->c1t2; +} + +void event3_p917(PokeyState* ps, int p5v, int p4v, int p917v) +{ + ps->c3t2 = p917v; + ps->c3t1 = p5v; + /* high-pass clock for channel 1 */ + ps->c1t3 = ps->c1t2; +} + +void event3_p4_p5(PokeyState* ps, int p5v, int p4v, int p917v) +{ + if(ps->c3t1) + ps->c3t2 = p4v; + ps->c3t1 = p5v; + /* high-pass clock for channel 1 */ + ps->c1t3 = ps->c1t2; +} + +void event3_p917_p5(PokeyState* ps, int p5v, int p4v, int p917v) +{ + if(ps->c3t1) + ps->c3t2 = p917v; + ps->c3t1 = p5v; + /* high-pass clock for channel 1 */ + ps->c1t3 = ps->c1t2; +} + +void advance_ticks(PokeyState* ps, int ticks) +{ + int ta,tbe, tbe0, tbe1, tbe2, tbe3; + int p5v,p4v,p917v; + + qev_t outvol_new; + int need0=0; + int need1=0; + int need2=0; + int need3=0; + + int need=0; + + if (ticks <= 0) return; + if(ps->forcero) + { + ps->forcero = 0; +#ifdef NONLINEAR_MIXING + outvol_new = pokeymix[ps->outvol_0 + ps->outvol_1 + ps->outvol_2 + ps->outvol_3]; +#else + outvol_new = ps->outvol_0 + ps->outvol_1 + ps->outvol_2 + ps->outvol_3; +#endif /* NONLINEAR_MIXING */ + if(outvol_new != ps->outvol_all) + { + ps->outvol_all = outvol_new; + } + } + + while(ticks>0) + { + tbe0 = ps->c0divpos; + tbe1 = ps->c1divpos; + tbe2 = ps->c2divpos; + tbe3 = ps->c3divpos; + + tbe = ticks+1; + +#ifdef NONLINEAR_MIXING + if(tbe0 < tbe) + tbe = tbe0; + if(tbe1 < tbe) + tbe = tbe1; + if(tbe2 < tbe) + tbe = tbe2; + if(tbe3 < tbe) + tbe = tbe3; +#else + if(!ps->c0stop && tbe0 < tbe) + tbe = tbe0; + if(!ps->c1stop && tbe1 < tbe) + tbe = tbe1; + if(!ps->c2stop && tbe2 < tbe) + tbe = tbe2; + if(!ps->c3stop && tbe3 < tbe) + tbe = tbe3; +#endif + + if(tbe>ticks) + ta = ticks; + else + { + ta = tbe; + need = 1; + } + + ticks -= ta; + +#ifdef NONLINEAR_MIXING + ps->c0divpos -= ta; + ps->c1divpos -= ta; + ps->c2divpos -= ta; + ps->c3divpos -= ta; +#else + if(!ps->c0stop) ps->c0divpos -= ta; + if(!ps->c1stop) ps->c1divpos -= ta; + if(!ps->c2stop) ps->c2divpos -= ta; + if(!ps->c3stop) ps->c3divpos -= ta; +#endif + + advance_polies(ps,ta); + + if(need) + { + p5v = poly5tbl[ps->poly5pos] & 1; + p4v = poly4tbl[ps->poly4pos] & 1; + if(ps->selpoly9) + p917v = poly9tbl[ps->poly9pos] & 1; + else + p917v = poly17tbl[ps->poly17pos] & 1; + +#ifdef NONLINEAR_MIXING + if(ta == tbe0) +#else + if(!ps->c0stop && ta == tbe0) +#endif + { + ps->event_0(ps,p5v,p4v,p917v); + ps->c0divpos = ps->c0divstart; + need0 = 1; + } +#ifdef NONLINEAR_MIXING + if(ta == tbe1) +#else + if(!ps->c1stop && ta == tbe1) +#endif + { + ps->event_1(ps,p5v,p4v,p917v); + ps->c1divpos = ps->c1divstart; + if(ps->c1_f0) + ps->c0divpos = ps->c0divstart_p; + need1 = 1; + /*two-tone filter*/ + /*use if send break is on and two-tone mode is on*/ + /*reset channel 1 if channel 2 changed*/ + if((ps->skctl & 0x88) == 0x88) { + ps->c0divpos = ps->c0divstart; + /* it doesn't change the output state */ + /*need0 = 1;*/ + } + } +#ifdef NONLINEAR_MIXING + if(ta == tbe2) +#else + if(!ps->c2stop && ta == tbe2) +#endif + { + ps->event_2(ps,p5v,p4v,p917v); + ps->c2divpos = ps->c2divstart; + need2 = 1; + if(ps->c0sw4) + need0 = 1; + } +#ifdef NONLINEAR_MIXING + if(ta == tbe3) +#else + if(!ps->c3stop && ta == tbe3) +#endif + { + ps->event_3(ps,p5v,p4v,p917v); + ps->c3divpos = ps->c3divstart; + if(ps->c3_f2) + ps->c2divpos = ps->c2divstart_p; + need3 = 1; + if(ps->c1sw4) + need1 = 1; + } + + if(need0) + { +#ifdef NONLINEAR_MIXING + ps->outvol_0 = ps->readout_0(ps); +#else + ps->outvol_0 = 2*ps->readout_0(ps); +#endif + } + if(need1) + { +#ifdef NONLINEAR_MIXING + ps->outvol_1 = ps->readout_1(ps); +#else + ps->outvol_1 = 2*ps->readout_1(ps); +#endif + } + if(need2) + { +#ifdef NONLINEAR_MIXING + ps->outvol_2 = ps->readout_2(ps); +#else + ps->outvol_2 = 2*ps->readout_2(ps); +#endif + } + if(need3) + { +#ifdef NONLINEAR_MIXING + ps->outvol_3 = ps->readout_3(ps); +#else + ps->outvol_3 = 2*ps->readout_3(ps); +#endif + } + +#ifdef NONLINEAR_MIXING + outvol_new = pokeymix[ps->outvol_0 + ps->outvol_1 + ps->outvol_2 + ps->outvol_3]; +#else + outvol_new = ps->outvol_0 + ps->outvol_1 + ps->outvol_2 + ps->outvol_3; +#endif /* NONLINEAR_MIXING */ + if(outvol_new != ps->outvol_all) + { + ps->outvol_all = outvol_new; + } + } + } +} + +double generate_sample(PokeyState* ps) +{ + advance_ticks(ps, 1); + return ps->outvol_all; +} + +/*****************************************************************************/ +/* Module: MZPOKEYSND_Init() */ +/* Purpose: to handle the power-up initialization functions */ +/* these functions should only be executed on a cold-restart */ +/* */ +/* Authors: Michael Borisov, Krzystof Nikiel */ +/* */ +/* Inputs: freq17 - the value for the '1.79MHz' Pokey audio clock */ +/* playback_freq - the playback frequency in samples per second */ +/* */ +/* Outputs: Adjusts local globals - no return value */ +/* */ +/*****************************************************************************/ + +int MZPOKEYSND_Init(PokeyState* ps) +{ + build_poly4(); + build_poly5(); + build_poly9(); + build_poly17(); + ResetPokeyState(ps); + return 0; /* OK */ +} + + +void Update_readout_0(PokeyState* ps) +{ + if(ps->c0vo) + ps->readout_0 = readout0_vo; + else if(ps->c0sw4) + ps->readout_0 = readout0_hipass; + else + ps->readout_0 = readout0_normal; +} + +void Update_readout_1(PokeyState* ps) +{ + if(ps->c1vo) + ps->readout_1 = readout1_vo; + else if(ps->c1sw4) + ps->readout_1 = readout1_hipass; + else + ps->readout_1 = readout1_normal; +} + +void Update_readout_2(PokeyState* ps) +{ + if(ps->c2vo) + ps->readout_2 = readout2_vo; + else + ps->readout_2 = readout2_normal; +} + +void Update_readout_3(PokeyState* ps) +{ + if(ps->c3vo) + ps->readout_3 = readout3_vo; + else + ps->readout_3 = readout3_normal; +} + +void Update_event0(PokeyState* ps) +{ + if(ps->c0sw3) + { + if(ps->c0sw2) + ps->event_0 = event0_pure; + else + { + if(ps->c0sw1) + ps->event_0 = event0_p4; + else + ps->event_0 = event0_p917; + } + } + else + { + if(ps->c0sw2) + ps->event_0 = event0_p5; + else + { + if(ps->c0sw1) + ps->event_0 = event0_p4_p5; + else + ps->event_0 = event0_p917_p5; + } + } +} + +void Update_event1(PokeyState* ps) +{ + if(ps->c1sw3) + { + if(ps->c1sw2) + ps->event_1 = event1_pure; + else + { + if(ps->c1sw1) + ps->event_1 = event1_p4; + else + ps->event_1 = event1_p917; + } + } + else + { + if(ps->c1sw2) + ps->event_1 = event1_p5; + else + { + if(ps->c1sw1) + ps->event_1 = event1_p4_p5; + else + ps->event_1 = event1_p917_p5; + } + } +} + +void Update_event2(PokeyState* ps) +{ + if(ps->c2sw3) + { + if(ps->c2sw2) + ps->event_2 = event2_pure; + else + { + if(ps->c2sw1) + ps->event_2 = event2_p4; + else + ps->event_2 = event2_p917; + } + } + else + { + if(ps->c2sw2) + ps->event_2 = event2_p5; + else + { + if(ps->c2sw1) + ps->event_2 = event2_p4_p5; + else + ps->event_2 = event2_p917_p5; + } + } +} + +void Update_event3(PokeyState* ps) +{ + if(ps->c3sw3) + { + if(ps->c3sw2) + ps->event_3 = event3_pure; + else + { + if(ps->c3sw1) + ps->event_3 = event3_p4; + else + ps->event_3 = event3_p917; + } + } + else + { + if(ps->c3sw2) + ps->event_3 = event3_p5; + else + { + if(ps->c3sw1) + ps->event_3 = event3_p4_p5; + else + ps->event_3 = event3_p917_p5; + } + } +} + +void Update_c0divstart(PokeyState* ps) +{ + if(ps->c1_f0) + { + if(ps->c0_hf) + { + ps->c0divstart = 256; + ps->c0divstart_p = ps->c0diva + 7; + } + else + { + ps->c0divstart = 256 * ps->mdivk; + ps->c0divstart_p = (ps->c0diva+1)*ps->mdivk; + } + } + else + { + if(ps->c0_hf) + ps->c0divstart = ps->c0diva + 4; + else + ps->c0divstart = (ps->c0diva+1) * ps->mdivk; + } +} + +void Update_c1divstart(PokeyState* ps) +{ + if(ps->c1_f0) + { + if(ps->c0_hf) + ps->c1divstart = ps->c0diva + 256*ps->c1diva + 7; + else + ps->c1divstart = (ps->c0diva + 256*ps->c1diva + 1) * ps->mdivk; + } + else + ps->c1divstart = (ps->c1diva + 1) * ps->mdivk; +} + +void Update_c2divstart(PokeyState* ps) +{ + if(ps->c3_f2) + { + if(ps->c2_hf) + { + ps->c2divstart = 256; + ps->c2divstart_p = ps->c2diva + 7; + } + else + { + ps->c2divstart = 256 * ps->mdivk; + ps->c2divstart_p = (ps->c2diva+1)*ps->mdivk; + } + } + else + { + if(ps->c2_hf) + ps->c2divstart = ps->c2diva + 4; + else + ps->c2divstart = (ps->c2diva+1) * ps->mdivk; + } +} + +void Update_c3divstart(PokeyState* ps) +{ + if(ps->c3_f2) + { + if(ps->c2_hf) + ps->c3divstart = ps->c2diva + 256*ps->c3diva + 7; + else + ps->c3divstart = (ps->c2diva + 256*ps->c3diva + 1) * ps->mdivk; + } + else + ps->c3divstart = (ps->c3diva + 1) * ps->mdivk; +} + +void Update_audctl(PokeyState* ps, unsigned char val) +{ + int nc0_hf,nc2_hf,nc1_f0,nc3_f2,nc0sw4,nc1sw4,new_divk; + int recalc0=0; + int recalc1=0; + int recalc2=0; + int recalc3=0; + + unsigned int cnt0 = 0; + unsigned int cnt1 = 0; + unsigned int cnt2 = 0; + unsigned int cnt3 = 0; + + nc0_hf = (val & 0x40) != 0; + nc2_hf = (val & 0x20) != 0; + nc1_f0 = (val & 0x10) != 0; + nc3_f2 = (val & 0x08) != 0; + nc0sw4 = (val & 0x04) != 0; + nc1sw4 = (val & 0x02) != 0; + if(val & 0x01) + new_divk = 114; + else + new_divk = 28; + + if(new_divk != ps->mdivk) + { + recalc0 = recalc1 = recalc2 = recalc3 = 1; + } + if(nc1_f0 != ps->c1_f0) + { + recalc0 = recalc1 = 1; + } + if(nc3_f2 != ps->c3_f2) + { + recalc2 = recalc3 = 1; + } + if(nc0_hf != ps->c0_hf) + { + recalc0 = 1; + if(nc1_f0) + recalc1 = 1; + } + if(nc2_hf != ps->c2_hf) + { + recalc2 = 1; + if(nc3_f2) + recalc3 = 1; + } + + if(recalc0) + { + if(ps->c0_hf) + cnt0 = ps->c0divpos; + else + cnt0 = ps->c0divpos/ps->mdivk; + } + if(recalc1) + { + if(ps->c1_f0) + { + if(ps->c0_hf) + cnt1 = ps->c1divpos/256; + else + cnt1 = ps->c1divpos/256/ps->mdivk; + } + else + { + cnt1 = ps->c1divpos/ps->mdivk; + } + } + if(recalc2) + { + if(ps->c2_hf) + cnt2 = ps->c2divpos; + else + cnt2 = ps->c2divpos/ps->mdivk; + } + if(recalc3) + { + if(ps->c3_f2) + { + if(ps->c2_hf) + cnt3 = ps->c3divpos/256; + else + cnt3 = ps->c3divpos/256/ps->mdivk; + } + } + + if(recalc0) + { + if(nc0_hf) + ps->c0divpos = cnt0; + else + ps->c0divpos = cnt0*new_divk; + } + if(recalc1) + { + if(nc1_f0) + { + if(nc0_hf) + ps->c1divpos = cnt1*256+cnt0; + else + ps->c1divpos = (cnt1*256+cnt0)*new_divk; + } + else + { + ps->c1divpos = cnt1*new_divk; + } + } + + if(recalc2) + { + if(nc2_hf) + ps->c2divpos = cnt2; + else + ps->c2divpos = cnt2*new_divk; + } + if(recalc3) + { + if(nc3_f2) + { + if(nc2_hf) + ps->c3divpos = cnt3*256+cnt2; + else + ps->c3divpos = (cnt3*256+cnt2)*new_divk; + } + } + + ps->c0_hf = nc0_hf; + ps->c2_hf = nc2_hf; + ps->c1_f0 = nc1_f0; + ps->c3_f2 = nc3_f2; + ps->c0sw4 = nc0sw4; + ps->c1sw4 = nc1sw4; + ps->mdivk = new_divk; +} + +/* SKCTL for two-tone mode */ +void Update_skctl(PokeyState* ps, unsigned char val) +{ + ps->skctl = val; +} + +/* if using nonlinear mixing, don't stop ultrasounds */ +#ifdef NONLINEAR_MIXING +void Update_c0stop(PokeyState* ps) +{ + ps->outvol_0 = ps->readout_0(ps); +} +void Update_c1stop(PokeyState* ps) +{ + ps->outvol_1 = ps->readout_1(ps); +} +void Update_c2stop(PokeyState* ps) +{ + ps->outvol_2 = ps->readout_2(ps); +} +void Update_c3stop(PokeyState* ps) +{ + ps->outvol_3 = ps->readout_3(ps); +} +#else +void Update_c0stop(PokeyState* ps) +{ + int lim = 1; + + int hfa = 0; + ps->c0stop = 0; + + if(ps->c0vo || ps->vol0 == 0) + ps->c0stop = 1; + else if(!ps->c0sw4 && ps->c0sw3 && ps->c0sw2) /* If channel 0 is a pure tone... */ + { + if(ps->c1_f0) + { + if(ps->c1divstart <= lim) + { + ps->c0stop = 1; + hfa = 1; + } + } + else + { + if(ps->c0divstart <= lim) + { + ps->c0stop = 1; + hfa = 1; + } + } + } + else if(!ps->c0sw4 && ps->c0sw3 && !ps->c0sw2 && ps->c0sw1) /* if channel 0 is poly4... */ + { + /* period for poly4 signal is 15 cycles */ + if(ps->c1_f0) + { + if(ps->c1divstart <= lim*2/15) /* all poly4 signal is above Nyquist */ + { + ps->c0stop = 1; + hfa = 1; + } + } + else + { + if(ps->c0divstart <= lim*2/15) + { + ps->c0stop = 1; + hfa = 1; + } + } + } + + ps->outvol_0 = 2*ps->readout_0(ps); + if(hfa) + ps->outvol_0 = ps->vol0; +} + +void Update_c1stop(PokeyState* ps) +{ + int lim = 1; + + int hfa = 0; + ps->c1stop = 0; + + if(!ps->c1_f0 && (ps->c1vo || ps->vol1 == 0)) + ps->c1stop = 1; + else if(!ps->c1sw4 && ps->c1sw3 && ps->c1sw2 && ps->c1divstart <= lim) /* If channel 1 is a pure tone */ + { + ps->c1stop = 1; + hfa = 1; + } + else if(!ps->c1sw4 && ps->c1sw3 && !ps->c1sw2 && ps->c1sw1 && ps->c1divstart <= lim*2/15) /* all poly4 signal is above Nyquist */ + { + ps->c1stop = 1; + hfa = 1; + } + + ps->outvol_1 = 2*ps->readout_1(ps); + if(hfa) + ps->outvol_1 = ps->vol1; +} + +void Update_c2stop(PokeyState* ps) +{ + int lim = 1; + + int hfa = 0; + ps->c2stop = 0; + + if(!ps->c0sw4 && (ps->c2vo || ps->vol2 == 0)) + ps->c2stop = 1; + /* If channel 2 is a pure tone and no filter for c0... */ + else if(ps->c2sw3 && ps->c2sw2 && !ps->c0sw4) + { + if(ps->c3_f2) + { + if(ps->c3divstart <= lim) + { + ps->c2stop = 1; + hfa = 1; + } + } + else + { + if(ps->c2divstart <= lim) + { + ps->c2stop = 1; + hfa = 1; + } + } + } + else if(ps->c2sw3 && !ps->c2sw2 && ps->c2sw1 && !ps->c0sw4) /* if channel 2 is poly4 and no filter for c0... */ + { + /* period for poly4 signal is 15 cycles */ + if(ps->c3_f2) + { + if(ps->c3divstart <= lim*2/15) /* all poly4 signal is above Nyquist */ + { + ps->c2stop = 1; + hfa = 1; + } + } + else + { + if(ps->c2divstart <= lim*2/15) + { + ps->c2stop = 1; + hfa = 1; + } + } + } + + ps->outvol_2 = 2*ps->readout_2(ps); + if(hfa) + ps->outvol_2 = ps->vol2; +} + +void Update_c3stop(PokeyState* ps) +{ + int lim = 1; + int hfa = 0; + ps->c3stop = 0; + + if(!ps->c1sw4 && !ps->c3_f2 && (ps->c3vo || ps->vol3 == 0)) + ps->c3stop = 1; + /* If channel 3 is a pure tone */ + else if(ps->c3sw3 && ps->c3sw2 && !ps->c1sw4 && ps->c3divstart <= lim) + { + ps->c3stop = 1; + hfa = 1; + } + else if(ps->c3sw3 && !ps->c3sw2 && ps->c3sw1 && !ps->c1sw4 && ps->c3divstart <= lim*2/15) /* all poly4 signal is above Nyquist */ + { + ps->c3stop = 1; + hfa = 1; + } + + ps->outvol_3 = 2*ps->readout_3(ps); + if(hfa) + ps->outvol_3 = ps->vol3; +} +#endif /*NONLINEAR_MIXING*/ + +/*****************************************************************************/ +/* Function: Update_pokey_sound_mz() */ +/* */ +/* Inputs: addr - the address of the parameter to be changed */ +/* val - the new value to be placed in the specified address */ +/* chip - chip # for stereo */ +/* gain - specified as an 8-bit fixed point number - use 1 for no */ +/* amplification (output is multiplied by gain) */ +/* */ +/* Outputs: Adjusts local globals - no return value */ +/* */ +/*****************************************************************************/ +void Update_pokey_sound_mz(PokeyState* ps, unsigned short addr, unsigned char val, unsigned char gain) +{ + switch(addr & 0x0f) + { + case POKEY_OFFSET_AUDF1: + ps->c0diva = val; + Update_c0divstart(ps); + if(ps->c1_f0) + { + Update_c1divstart(ps); + Update_c1stop(ps); + } + Update_c0stop(ps); + ps->forcero = 1; + break; + case POKEY_OFFSET_AUDC1: + ps->c0sw1 = (val & 0x40) != 0; + ps->c0sw2 = (val & 0x20) != 0; + ps->c0sw3 = (val & 0x80) != 0; + ps->vol0 = (val & 0xF); + ps->c0vo = (val & 0x10) != 0; + Update_readout_0(ps); + Update_event0(ps); + Update_c0stop(ps); + ps->forcero = 1; + break; + case POKEY_OFFSET_AUDF2: + ps->c1diva = val; + Update_c1divstart(ps); + if(ps->c1_f0) + { + Update_c0divstart(ps); + Update_c0stop(ps); + } + Update_c1stop(ps); + ps->forcero = 1; + break; + case POKEY_OFFSET_AUDC2: + ps->c1sw1 = (val & 0x40) != 0; + ps->c1sw2 = (val & 0x20) != 0; + ps->c1sw3 = (val & 0x80) != 0; + ps->vol1 = (val & 0xF); + ps->c1vo = (val & 0x10) != 0; + Update_readout_1(ps); + Update_event1(ps); + Update_c1stop(ps); + ps->forcero = 1; + break; + case POKEY_OFFSET_AUDF3: + ps->c2diva = val; + Update_c2divstart(ps); + if(ps->c3_f2) + { + Update_c3divstart(ps); + Update_c3stop(ps); + } + Update_c2stop(ps); + ps->forcero = 1; + break; + case POKEY_OFFSET_AUDC3: + ps->c2sw1 = (val & 0x40) != 0; + ps->c2sw2 = (val & 0x20) != 0; + ps->c2sw3 = (val & 0x80) != 0; + ps->vol2 = (val & 0xF); + ps->c2vo = (val & 0x10) != 0; + Update_readout_2(ps); + Update_event2(ps); + Update_c2stop(ps); + ps->forcero = 1; + break; + case POKEY_OFFSET_AUDF4: + ps->c3diva = val; + Update_c3divstart(ps); + if(ps->c3_f2) + { + Update_c2divstart(ps); + Update_c2stop(ps); + } + Update_c3stop(ps); + ps->forcero = 1; + break; + case POKEY_OFFSET_AUDC4: + ps->c3sw1 = (val & 0x40) != 0; + ps->c3sw2 = (val & 0x20) != 0; + ps->c3sw3 = (val & 0x80) != 0; + ps->vol3 = val & 0xF; + ps->c3vo = (val & 0x10) != 0; + Update_readout_3(ps); + Update_event3(ps); + Update_c3stop(ps); + ps->forcero = 1; + break; + case POKEY_OFFSET_AUDCTL: + ps->selpoly9 = (val & 0x80) != 0; + Update_audctl(ps,val); + Update_readout_0(ps); + Update_readout_1(ps); + Update_readout_2(ps); + Update_readout_3(ps); + Update_c0divstart(ps); + Update_c1divstart(ps); + Update_c2divstart(ps); + Update_c3divstart(ps); + Update_c0stop(ps); + Update_c1stop(ps); + Update_c2stop(ps); + Update_c3stop(ps); + ps->forcero = 1; + break; + case POKEY_OFFSET_STIMER: + if(ps->c1_f0) + ps->c0divpos = ps->c0divstart_p; + else + ps->c0divpos = ps->c0divstart; + ps->c1divpos = ps->c1divstart; + if(ps->c3_f2) + ps->c2divpos = ps->c2divstart_p; + else + ps->c2divpos = ps->c2divstart; + + ps->c3divpos = ps->c3divstart; + /*Documentation is wrong about which voices are on after STIMER*/ + /*It is 3&4 which are on, tested on a real atari*/ + ps->c0t2 = 0; + ps->c1t2 = 0; + ps->c2t2 = 1; + ps->c3t2 = 1; + break; + case POKEY_OFFSET_SKCTL: + Update_skctl(ps,val); + break; + } +} + + +/************************************************************** + + Master gain and DC offset calculation + by Michael Borisov + + In order to use the available 8-bit or 16-bit dynamic range + to full extent, reducing the influence of quantization + noise while simultaneously avoiding overflows, gain + and DC offset should be set to appropriate value. + + All Pokey-generated sounds have maximal amplitude of 15. + When all four channels sound simultaneously and in the + same phase, amplidudes would add up to 60. + + If Pokey is generating a 'pure tone', it always has a DC + offset of half its amplitude. For other signals (produced + by poly generators) DC offset varies, but it is always near + to half amplitude and never exceeds this value. + + In case that pure tone base frequency is outside of audible + range (ultrasound frequency for high sample rates and above- + Nyquist frequency for low sample rates), to speed up the engine, + the generator is stopped while having only DC offset on the + output (half of corresponding AUDV value). In order that this + DC offset can be always represented as integer, AUDV values + are multiplied by 2 when the generator works. + + Therefore maximum integer value before resampling filters + would be 60*2 = 120 while having maximum DC offset of 60. + Resampling does not change the DC offset, therefore we may + subtract it from the signal either before or after resampling. + In mzpokeysnd, DC offset is subtracted after resampling, however + for better understanding in further measurements I assume + subtracting DC before. So, input range for the resampler + becomes [-60 .. 60]. + + Resampling filter removes some harmonics from the signal as if + the rectangular wave was Fourier transformed forth-and-back, + while zeroing all harmonics above cutoff frequency. In case + of high-frequency pure tone (above samplerate/8), only first + harmonic of the Fourier transofm will remain. As it + is known, Fourier-transform of the rectangular function of + amplitude 1 has first oscillation component of amplitude 4/M_PI. + Therefore, maximum sample values for filtered rectangular + signal may exceed the amplitude of rectangular signal + by up to 4/M_PI times. + + Since our range before resampler is -60 .. 60, taking into + account mentioned effect with band limiting, range of values + on the resampler output appears to be in the following bounds: + [-60*4/M_PI .. 60*4/M_PI] + + In order to map this into signed 8-bit range [-128 .. 127], we + should multiply the resampler output by 127/60/4*M_PI. + + As it is common for sound hardware to have 8-bit sound unsigned, + additional DC offset of 128 must be added. + + For 16-bit case the output range is [-32768 .. 32767], and + we should multiply the resampler output by 32767/60/4*M_PI + + To make some room for numerical errors, filter ripples and + quantization noise, so that they do not cause overflows in + quantization, dynamic range is reduced in mzpokeysnd by + multiplying the output amplitude with 0.95, reserving 5% + of the total range for such effects, which is about 0.51db. + + Mentioned gain and DC values were tested with 17kHz audio + playing synchronously on 4 channels, which showed to be + utilizing 95% of the sample values range. + + Since any other gain value will be not optimal, I removed + user gain setting and hard-coded the gain into mzpokeysnd + + --- + + A note from Piotr Fusik: + I've added support for the key click sound generated by GTIA. Its + volume seems to be pretty much like 8 on single POKEY's channel. + So, the volumes now can sum up to 136 (4 channels * 15 * 2 + + 8 * 2 for GTIA), not 120. + + A note from Mark Grebe: + I've added back in the console and sio sounds from the old + pokey version. So, now the volumes can sum up to 152 + (4 channesl * 15 * 2 + 8 * 4 for old sound), not 120 or 136. + + ******************************************************************/ + + +/****************************************************************** + + Quantization effects and dithering + by Michael Borisov + + Quantization error in the signal has an expectation value of half + the LSB, when the rounding is performed properly. Sometimes they + express quantization error as a random function with even + distribution over the range [-0.5 to 0.5]. Spectrum of this function + is flat, because it's a white noise. + + Power of a discrete signal (including noise) is calculated as + mean square of its samples. For the mentioned above noise + this is approximately 0.33. Therefore, in decibels for 8-bit case, + our noise will have power of 10*log10(0.33/256/256) = -53dB + + Because noise is white, this power of -53dB will be evenly + distributed over the whole signal band upto Nyquist frequency. + The larger the band is (higher sampling frequency), less + is the quantisation noise floor. For 8000Hz noise floor is + 10*log10(0.33/256/256/4000) = -89dB/Hz, and for 44100Hz noise + floor is 10*log10(0.33/256/256/22050) = -96.4dB/Hz. + This shows that higher sampling rates are better in sense of + quantization noise. Moreover, as large part of quantization noise + in case of 44100Hz will fall into ultrasound and hi-frequency + area 10-20kHz where human ear is less sensitive, this will + show up as great improvement in quantization noise performance + compared to 8000Hz. + + I was plotting spectral analysis for sounds produced by mzpokeysnd + to check these measures. And it showed up that in 8-bit case + there is no expected flat noise floor of -89db/Hz for 8000Hz, + but some distortion spectral peaks had higher amplitude than + the aliasing peaks in 16-bit case. This was a proof to another + statement which says that quantization noise tends to become + correlated with the signal. Correlation is especially strong + for simple signals generated by Pokey. Correlation means that + the noise power of -53db is no longer evenly distributed + across the whole frequency range, but concentrates in larger + peaks at locations which depend on the Pokey signal. + + To decorrelate quantization distortion and make it again + white noise, which would improve the sound spectrum, since + the maximum distortion peaks will have less amplitude, + dithering is used. Another white noise is added to the signal + before quantization. Since this added noise is not correlated + with the signal, it shows itself as a flat noise floor. + Quantization noise now tries to correlate with the dithering + noise, but this does not lead to appearance of sharp + spectral peaks any more :) + + Another thing is that for listening, white noise is better than + distortion. This is because human hearing has some 'noise + reduction' system which makes it easier to percept sounds + on the white noise background. + + From the other point of view, if a signal has high and low + spectral peaks, it is desirable that there is no distortion + component with peaks of amplitude comparable to those of + the true signal. Otherwise, perception of background low- + amplitude signals will be disrupted. That's why they say + that dithering extends dynamic range. + + Dithering does not eliminate correlation of quantization noise + completely. Degree of reduction of this effect depends on + the dithering noise power. The higher is dithering noise, + the more quantization noise is decorrelated. But this also + leads to increase of noise percepted by the listener. So, an + optimum value should be selected. My experiments show that + unbiased rand() noise of amplitude 0.25 LSB is doing well. + + Test spectral pictures for 8-bit sound, 8kHz sampling rate, + dithered, show a noise floor of approx. -87dB/Hz. + + ******************************************************************/ + +#define MAX_SAMPLE 152 + +void mzpokeysnd_process_16(PokeyState* ps, void* sndbuffer, int sndn) +{ + int i; + int nsam = sndn; + short *buffer = (short *) sndbuffer; + + /* if there are two pokeys, then the signal is stereo + we assume even sndn */ + while(nsam >= (int) 1) + { + buffer[0] = (short)floor(generate_sample(ps) + * (65535.0 / 2 / MAX_SAMPLE / 4 * M_PI * 0.95)); + buffer += 1; + nsam -= 1; + } +} diff --git a/src/engine/platform/sound/pokey/mzpokeysnd.h b/src/engine/platform/sound/pokey/mzpokeysnd.h new file mode 100644 index 00000000..01da7aff --- /dev/null +++ b/src/engine/platform/sound/pokey/mzpokeysnd.h @@ -0,0 +1,159 @@ +#ifndef MZPOKEYSND_H_ +#define MZPOKEYSND_H_ + +#include + +struct stPokeyState; + +typedef int (*readout_t)(struct stPokeyState* ps); +typedef void (*event_t)(struct stPokeyState* ps, int p5v, int p4v, int p917v); + +#ifdef NONLINEAR_MIXING +/* Change queue event value type */ +typedef double qev_t; +#else +typedef unsigned char qev_t; +#endif + +/* State variables for single Pokey Chip */ +typedef struct stPokeyState +{ + int curtick; + /* Poly positions */ + int poly4pos; + int poly5pos; + int poly17pos; + int poly9pos; + + /* Main divider (64khz/15khz) */ + int mdivk; /* 28 for 64khz, 114 for 15khz */ + + /* Main switches */ + int selpoly9; + int c0_hf; + int c1_f0; + int c2_hf; + int c3_f2; + + /* SKCTL for two-tone mode */ + int skctl; + + /* Main output state */ + qev_t outvol_all; + int forcero; /* Force readout */ + + /* channel 0 state */ + + readout_t readout_0; + event_t event_0; + + int c0divpos; + int c0divstart; /* AUDF0 recalculated */ + int c0divstart_p; /* start value when c1_f0 */ + int c0diva; /* AUDF0 register */ + + int c0t1; /* D - 5bit, Q goes to sw3 */ + int c0t2; /* D - out sw2, Q goes to sw4 and t3 */ + int c0t3; /* D - out t2, q goes to xor */ + + int c0sw1; /* in1 - 4bit, in2 - 17bit, out goes to sw2 */ + int c0sw2; /* in1 - /Q t2, in2 - out sw1, out goes to t2 */ + int c0sw3; /* in1 - +5, in2 - Q t1, out goes to C t2 */ + int c0sw4; /* hi-pass sw */ + int c0vo; /* volume only */ + +#ifndef NONLINEAR_MIXING + int c0stop; /* channel counter stopped */ +#endif + + int vol0; + + int outvol_0; + + /* channel 1 state */ + + readout_t readout_1; + event_t event_1; + + int c1divpos; + int c1divstart; + int c1diva; + + int c1t1; + int c1t2; + int c1t3; + + int c1sw1; + int c1sw2; + int c1sw3; + int c1sw4; + int c1vo; + +#ifndef NONLINEAR_MIXING + int c1stop; /* channel counter stopped */ +#endif + + int vol1; + + int outvol_1; + + /* channel 2 state */ + + readout_t readout_2; + event_t event_2; + + int c2divpos; + int c2divstart; + int c2divstart_p; /* start value when c1_f0 */ + int c2diva; + + int c2t1; + int c2t2; + + int c2sw1; + int c2sw2; + int c2sw3; + int c2vo; + +#ifndef NONLINEAR_MIXING + int c2stop; /* channel counter stopped */ +#endif + + int vol2; + + int outvol_2; + + /* channel 3 state */ + + readout_t readout_3; + event_t event_3; + + int c3divpos; + int c3divstart; + int c3diva; + + int c3t1; + int c3t2; + + int c3sw1; + int c3sw2; + int c3sw3; + int c3vo; + +#ifndef NONLINEAR_MIXING + int c3stop; /* channel counter stopped */ +#endif + + int vol3; + + int outvol_3; +} PokeyState; + +void mzpokeysnd_process_16(PokeyState* ps, void* sndbuffer, int sndn); +void Update_pokey_sound_mz(PokeyState* ps, unsigned short addr, unsigned char val, unsigned char gain); + +void ResetPokeyState(PokeyState* ps); + +int MZPOKEYSND_Init(PokeyState* ps); + +#endif /* MZPOKEYSND_H_ */ diff --git a/src/engine/platform/sound/pokey/pokey.h b/src/engine/platform/sound/pokey/pokey.h new file mode 100644 index 00000000..22fd108d --- /dev/null +++ b/src/engine/platform/sound/pokey/pokey.h @@ -0,0 +1,78 @@ +#ifndef POKEY_H_ +#define POKEY_H_ + +#define POKEY_OFFSET_AUDF1 0x00 +#define POKEY_OFFSET_AUDC1 0x01 +#define POKEY_OFFSET_AUDF2 0x02 +#define POKEY_OFFSET_AUDC2 0x03 +#define POKEY_OFFSET_AUDF3 0x04 +#define POKEY_OFFSET_AUDC3 0x05 +#define POKEY_OFFSET_AUDF4 0x06 +#define POKEY_OFFSET_AUDC4 0x07 +#define POKEY_OFFSET_AUDCTL 0x08 +#define POKEY_OFFSET_STIMER 0x09 +#define POKEY_OFFSET_SKRES 0x0a +#define POKEY_OFFSET_POTGO 0x0b +#define POKEY_OFFSET_SEROUT 0x0d +#define POKEY_OFFSET_IRQEN 0x0e +#define POKEY_OFFSET_SKCTL 0x0f + +#define POKEY_OFFSET_POT0 0x00 +#define POKEY_OFFSET_POT1 0x01 +#define POKEY_OFFSET_POT2 0x02 +#define POKEY_OFFSET_POT3 0x03 +#define POKEY_OFFSET_POT4 0x04 +#define POKEY_OFFSET_POT5 0x05 +#define POKEY_OFFSET_POT6 0x06 +#define POKEY_OFFSET_POT7 0x07 +#define POKEY_OFFSET_ALLPOT 0x08 +#define POKEY_OFFSET_KBCODE 0x09 +#define POKEY_OFFSET_RANDOM 0x0a +#define POKEY_OFFSET_SERIN 0x0d +#define POKEY_OFFSET_IRQST 0x0e +#define POKEY_OFFSET_SKSTAT 0x0f + +/* CONSTANT DEFINITIONS */ + +/* definitions for AUDCx (D201, D203, D205, D207) */ +#define POKEY_NOTPOLY5 0x80 /* selects POLY5 or direct CLOCK */ +#define POKEY_POLY4 0x40 /* selects POLY4 or POLY17 */ +#define POKEY_PURETONE 0x20 /* selects POLY4/17 or PURE tone */ +#define POKEY_VOL_ONLY 0x10 /* selects VOLUME OUTPUT ONLY */ +#define POKEY_VOLUME_MASK 0x0f /* volume mask */ + +/* definitions for AUDCTL (D208) */ +#define POKEY_POLY9 0x80 /* selects POLY9 or POLY17 */ +#define POKEY_CH1_179 0x40 /* selects 1.78979 MHz for Ch 1 */ +#define POKEY_CH3_179 0x20 /* selects 1.78979 MHz for Ch 3 */ +#define POKEY_CH1_CH2 0x10 /* clocks channel 1 w/channel 2 */ +#define POKEY_CH3_CH4 0x08 /* clocks channel 3 w/channel 4 */ +#define POKEY_CH1_FILTER 0x04 /* selects channel 1 high pass filter */ +#define POKEY_CH2_FILTER 0x02 /* selects channel 2 high pass filter */ +#define POKEY_CLOCK_15 0x01 /* selects 15.6999kHz or 63.9210kHz */ + +/* for accuracy, the 64kHz and 15kHz clocks are exact divisions of + the 1.79MHz clock */ +#define POKEY_DIV_64 28 /* divisor for 1.79MHz clock to 64 kHz */ +#define POKEY_DIV_15 114 /* divisor for 1.79MHz clock to 15 kHz */ + +/* the size (in entries) of the 4 polynomial tables */ +#define POKEY_POLY4_SIZE 0x000f +#define POKEY_POLY5_SIZE 0x001f +#define POKEY_POLY9_SIZE 0x01ff +#define POKEY_POLY17_SIZE 0x0001ffff + +#define POKEY_MAXPOKEYS 2 /* max number of emulated chips */ + +/* channel/chip definitions */ +#define POKEY_CHAN1 0 +#define POKEY_CHAN2 1 +#define POKEY_CHAN3 2 +#define POKEY_CHAN4 3 +#define POKEY_CHIP1 0 +#define POKEY_CHIP2 4 +#define POKEY_CHIP3 8 +#define POKEY_CHIP4 12 +#define POKEY_SAMPLE 127 + +#endif /* POKEY_H_ */ diff --git a/src/engine/platform/tx81z.cpp b/src/engine/platform/tx81z.cpp index 0598dfb0..88266114 100644 --- a/src/engine/platform/tx81z.cpp +++ b/src/engine/platform/tx81z.cpp @@ -306,9 +306,9 @@ void DivPlatformTX81Z::tick(bool sysTick) { chan[i].freq=chan[i].baseFreq+(chan[i].pitch>>1)-64+chan[i].pitch2; if (!parent->song.oldArpStrategy) { if (chan[i].fixedArp) { - chan[i].freq=(chan[i].baseNoteOverride<<7)+(chan[i].pitch>>1)-64+chan[i].pitch2; + chan[i].freq=(chan[i].baseNoteOverride<<6)+(chan[i].pitch>>1)-64+chan[i].pitch2; } else { - chan[i].freq+=chan[i].arpOff<<7; + chan[i].freq+=chan[i].arpOff<<6; } } if (chan[i].freq<0) chan[i].freq=0; diff --git a/src/engine/platform/ym2203ext.cpp b/src/engine/platform/ym2203ext.cpp index add360b0..12605a93 100644 --- a/src/engine/platform/ym2203ext.cpp +++ b/src/engine/platform/ym2203ext.cpp @@ -41,15 +41,30 @@ int DivPlatformYM2203Ext::dispatch(DivCommand c) { switch (c.cmd) { case DIV_CMD_NOTE_ON: { DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM); + + if (opChan[ch].insChanged) { + chan[2].state.alg=ins->fm.alg; + chan[2].state.fb=ins->fm.fb; + chan[2].state.op[ordch]=ins->fm.op[ordch]; + } + + if (noExtMacros) { + opChan[ch].macroInit(NULL); + } else { + opChan[ch].macroInit(ins); + } + if (!opChan[ch].std.vol.will) { + opChan[ch].outVol=opChan[ch].vol; + } unsigned short baseAddr=chanOffs[2]|opOffs[ordch]; - DivInstrumentFM::Operator op=ins->fm.op[ordch]; + DivInstrumentFM::Operator& op=chan[2].state.op[ordch]; // TODO: how does this work?! if (isOpMuted[ch]) { rWrite(baseAddr+0x40,127); } else { if (opChan[ch].insChanged) { - rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch].vol&0x7f,127)); + rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch].outVol&0x7f,127)); } } if (opChan[ch].insChanged) { @@ -62,13 +77,14 @@ int DivPlatformYM2203Ext::dispatch(DivCommand c) { opChan[ch].mask=op.enable; } if (opChan[ch].insChanged) { // TODO how does this work? - rWrite(chanOffs[2]+0xb0,(ins->fm.alg&7)|(ins->fm.fb<<3)); + rWrite(chanOffs[2]+0xb0,(chan[2].state.alg&7)|(chan[2].state.fb<<3)); } opChan[ch].insChanged=false; if (c.value!=DIV_NOTE_NULL) { opChan[ch].baseFreq=NOTE_FNUM_BLOCK(c.value,11); opChan[ch].portaPause=false; + opChan[ch].note=c.value; opChan[ch].freqChanged=true; } opChan[ch].keyOn=true; @@ -80,15 +96,28 @@ int DivPlatformYM2203Ext::dispatch(DivCommand c) { opChan[ch].keyOn=false; opChan[ch].active=false; break; + case DIV_CMD_NOTE_OFF_ENV: + if (noExtMacros) break; + opChan[ch].keyOff=true; + opChan[ch].keyOn=false; + opChan[ch].active=false; + opChan[ch].std.release(); + break; + case DIV_CMD_ENV_RELEASE: + if (noExtMacros) break; + opChan[ch].std.release(); + break; case DIV_CMD_VOLUME: { opChan[ch].vol=c.value; - DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM); + if (!opChan[ch].std.vol.has) { + opChan[ch].outVol=c.value; + } unsigned short baseAddr=chanOffs[2]|opOffs[ordch]; - DivInstrumentFM::Operator op=ins->fm.op[ordch]; + DivInstrumentFM::Operator& op=chan[2].state.op[ordch]; if (isOpMuted[ch]) { rWrite(baseAddr+0x40,127); } else { - rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch].vol&0x7f,127)); + rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch].outVol&0x7f,127)); } break; } @@ -144,29 +173,28 @@ int DivPlatformYM2203Ext::dispatch(DivCommand c) { immWrite(0x27,extMode?0x40:0); break; } - case DIV_CMD_FM_LFO: { - rWrite(0x22,(c.value&7)|((c.value>>4)<<3)); - break; - } case DIV_CMD_FM_FB: { chan[2].state.fb=c.value&7; rWrite(chanOffs[2]+ADDR_FB_ALG,(chan[2].state.alg&7)|(chan[2].state.fb<<3)); break; } - case DIV_CMD_FM_MULT: { // TODO + case DIV_CMD_FM_MULT: { unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]]; - DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM); - DivInstrumentFM::Operator op=ins->fm.op[orderedOps[c.value]]; - rWrite(baseAddr+0x30,(c.value2&15)|(dtTable[op.dt&7]<<4)); + DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]]; + op.mult=c.value2&15; + rWrite(baseAddr+0x30,(op.mult&15)|(dtTable[op.dt&7]<<4)); break; } - case DIV_CMD_FM_TL: { // TODO + case DIV_CMD_FM_TL: { unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]]; - DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM); - if (isOutput[ins->fm.alg][c.value]) { - rWrite(baseAddr+0x40,127-(((127-c.value2)*(opChan[ch].vol&0x7f))/127)); + DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]]; + op.tl=c.value2; + if (isOpMuted[ch]) { + rWrite(baseAddr+0x40,127); + } else if (KVS(2,c.value)) { + rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch].outVol&0x7f,127)); } else { - rWrite(baseAddr+0x40,c.value2); + rWrite(baseAddr+0x40,op.tl); } break; } @@ -360,6 +388,92 @@ void DivPlatformYM2203Ext::tick(bool sysTick) { } } + if (extMode && !noExtMacros) for (int i=0; i<4; i++) { + opChan[i].std.next(); + + if (opChan[i].std.vol.had) { + opChan[i].outVol=VOL_SCALE_LOG_BROKEN(opChan[i].vol,MIN(127,opChan[i].std.vol.val),127); + unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[i]]; + DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[i]]; + if (isOpMuted[i]) { + rWrite(baseAddr+ADDR_TL,127); + } else { + rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[i].outVol&0x7f,127)); + } + } + + if (opChan[i].std.arp.had) { + if (!opChan[i].inPorta) { + opChan[i].baseFreq=NOTE_FNUM_BLOCK(parent->calcArp(opChan[i].note,opChan[i].std.arp.val),11); + } + opChan[i].freqChanged=true; + } + + if (opChan[i].std.pitch.had) { + if (opChan[i].std.pitch.mode) { + opChan[i].pitch2+=opChan[i].std.pitch.val; + CLAMP_VAR(opChan[i].pitch2,-32768,32767); + } else { + opChan[i].pitch2=opChan[i].std.pitch.val; + } + opChan[i].freqChanged=true; + } + + // param macros + unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[i]]; + DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[i]]; + DivMacroInt::IntOp& m=opChan[i].std.op[orderedOps[i]]; + if (m.am.had) { + op.am=m.am.val; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + if (m.ar.had) { + op.ar=m.ar.val; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + if (m.dr.had) { + op.dr=m.dr.val; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + if (m.mult.had) { + op.mult=m.mult.val; + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + } + if (m.rr.had) { + op.rr=m.rr.val; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + if (m.sl.had) { + op.sl=m.sl.val; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + if (m.tl.had) { + op.tl=127-m.tl.val; + if (isOpMuted[i]) { + rWrite(baseAddr+ADDR_TL,127); + } else { + rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[i].outVol&0x7f,127)); + } + } + if (m.rs.had) { + op.rs=m.rs.val; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + if (m.dt.had) { + op.dt=m.dt.val; + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + } + if (m.d2r.had) { + op.d2r=m.d2r.val; + rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); + } + if (m.ssg.had) { + op.ssgEnv=m.ssg.val; + rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); + } + } + + DivPlatformYM2203::tick(sysTick); bool writeNoteOn=false; @@ -411,15 +525,17 @@ void DivPlatformYM2203Ext::muteChannel(int ch, bool mute) { isOpMuted[ch-2]=mute; int ordch=orderedOps[ch-2]; - DivInstrument* ins=parent->getIns(opChan[ch-2].ins,DIV_INS_FM); unsigned short baseAddr=chanOffs[2]|opOffs[ordch]; - DivInstrumentFM::Operator op=ins->fm.op[ordch]; + DivInstrumentFM::Operator op=chan[2].state.op[ordch]; if (isOpMuted[ch-2]) { rWrite(baseAddr+0x40,127); - } else if (isOutput[ins->fm.alg][ordch]) { - rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-2].vol&0x7f,127)); + immWrite(baseAddr+0x40,127); + } else if (KVS(2,ordch)) { + rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-2].outVol&0x7f,127)); + immWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-2].outVol&0x7f,127)); } else { rWrite(baseAddr+0x40,op.tl); + immWrite(baseAddr+0x40,op.tl); } } @@ -428,13 +544,23 @@ void DivPlatformYM2203Ext::forceIns() { for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[i]|opOffs[j]; DivInstrumentFM::Operator& op=chan[i].state.op[j]; - if (isMuted[i]) { - rWrite(baseAddr+ADDR_TL,127); - } else { - if (KVS(i,j)) { - rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG_BROKEN(127-op.tl,chan[i].outVol&0x7f,127)); + if (i==2 && extMode) { // extended channel + if (isOpMuted[j]) { + rWrite(baseAddr+0x40,127); + } else if (KVS(i,j)) { + rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[j].outVol&0x7f,127)); } else { - rWrite(baseAddr+ADDR_TL,op.tl); + rWrite(baseAddr+0x40,op.tl); + } + } else { + if (isMuted[i]) { + rWrite(baseAddr+ADDR_TL,127); + } else { + if (KVS(i,j)) { + rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG_BROKEN(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)); @@ -475,7 +601,7 @@ void* DivPlatformYM2203Ext::getChanState(int ch) { DivMacroInt* DivPlatformYM2203Ext::getChanMacroInt(int ch) { if (ch>=6) return ay->getChanMacroInt(ch-6); - if (ch>=2) return NULL; // currently not implemented + if (ch>=2) return &opChan[ch-2].std; return &chan[ch].std; } @@ -490,7 +616,9 @@ void DivPlatformYM2203Ext::reset() { for (int i=0; i<4; i++) { opChan[i]=DivPlatformOPN::OPNOpChannel(); + opChan[i].std.setEngine(parent); opChan[i].vol=127; + opChan[i].outVol=127; } // channel 2 mode diff --git a/src/engine/platform/ym2608.cpp b/src/engine/platform/ym2608.cpp index 9b43cfc5..98248472 100644 --- a/src/engine/platform/ym2608.cpp +++ b/src/engine/platform/ym2608.cpp @@ -950,7 +950,9 @@ int DivPlatformYM2608::dispatch(DivCommand c) { break; } case DIV_CMD_FM_LFO: { - rWrite(0x22,(c.value&7)|((c.value>>4)<<3)); + if (c.chan>=6) break; + lfoValue=(c.value&7)|((c.value>>4)<<3); + rWrite(0x22,lfoValue); break; } case DIV_CMD_FM_FB: { @@ -1200,6 +1202,7 @@ void DivPlatformYM2608::forceIns() { chan[i].freqChanged=true; } } + immWrite(0x22,lfoValue); for (int i=9; i<16; i++) { chan[i].insChanged=true; if (i>14) { // ADPCM-B @@ -1276,6 +1279,7 @@ void DivPlatformYM2608::reset() { } lastBusy=60; + lfoValue=8; sampleBank=0; writeRSSOff=0; writeRSSOn=0; @@ -1286,7 +1290,7 @@ void DivPlatformYM2608::reset() { extMode=false; // LFO - immWrite(0x22,0x08); + immWrite(0x22,lfoValue); // PCM volume immWrite(0x11,globalRSSVolume); // A diff --git a/src/engine/platform/ym2608ext.cpp b/src/engine/platform/ym2608ext.cpp index 179a632a..47a01d13 100644 --- a/src/engine/platform/ym2608ext.cpp +++ b/src/engine/platform/ym2608ext.cpp @@ -41,15 +41,32 @@ int DivPlatformYM2608Ext::dispatch(DivCommand c) { switch (c.cmd) { case DIV_CMD_NOTE_ON: { DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM); + + if (opChan[ch].insChanged) { + chan[2].state.alg=ins->fm.alg; + chan[2].state.fb=ins->fm.fb; + chan[2].state.fms=ins->fm.fms; + chan[2].state.ams=ins->fm.ams; + chan[2].state.op[ordch]=ins->fm.op[ordch]; + } + + if (noExtMacros) { + opChan[ch].macroInit(NULL); + } else { + opChan[ch].macroInit(ins); + } + if (!opChan[ch].std.vol.will) { + opChan[ch].outVol=opChan[ch].vol; + } unsigned short baseAddr=chanOffs[2]|opOffs[ordch]; - DivInstrumentFM::Operator op=ins->fm.op[ordch]; + DivInstrumentFM::Operator& op=chan[2].state.op[ordch]; // TODO: how does this work?! if (isOpMuted[ch]) { rWrite(baseAddr+0x40,127); } else { if (opChan[ch].insChanged) { - rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch].vol&0x7f,127)); + rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch].outVol&0x7f,127)); } } if (opChan[ch].insChanged) { @@ -62,14 +79,15 @@ int DivPlatformYM2608Ext::dispatch(DivCommand c) { opChan[ch].mask=op.enable; } if (opChan[ch].insChanged) { // TODO how does this work? - rWrite(chanOffs[2]+0xb0,(ins->fm.alg&7)|(ins->fm.fb<<3)); - rWrite(chanOffs[2]+0xb4,(opChan[ch].pan<<6)|(ins->fm.fms&7)|((ins->fm.ams&3)<<4)); + rWrite(chanOffs[2]+0xb0,(chan[2].state.alg&7)|(chan[2].state.fb<<3)); + rWrite(chanOffs[2]+0xb4,(IS_EXTCH_MUTED?0:(opChan[ch].pan<<6))|(chan[2].state.fms&7)|((chan[2].state.ams&3)<<4)); } opChan[ch].insChanged=false; if (c.value!=DIV_NOTE_NULL) { opChan[ch].baseFreq=NOTE_FNUM_BLOCK(c.value,11); opChan[ch].portaPause=false; + opChan[ch].note=c.value; opChan[ch].freqChanged=true; } opChan[ch].keyOn=true; @@ -81,15 +99,28 @@ int DivPlatformYM2608Ext::dispatch(DivCommand c) { opChan[ch].keyOn=false; opChan[ch].active=false; break; + case DIV_CMD_NOTE_OFF_ENV: + if (noExtMacros) break; + opChan[ch].keyOff=true; + opChan[ch].keyOn=false; + opChan[ch].active=false; + opChan[ch].std.release(); + break; + case DIV_CMD_ENV_RELEASE: + if (noExtMacros) break; + opChan[ch].std.release(); + break; case DIV_CMD_VOLUME: { opChan[ch].vol=c.value; - DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM); + if (!opChan[ch].std.vol.has) { + opChan[ch].outVol=c.value; + } unsigned short baseAddr=chanOffs[2]|opOffs[ordch]; - DivInstrumentFM::Operator op=ins->fm.op[ordch]; + DivInstrumentFM::Operator& op=chan[2].state.op[ordch]; if (isOpMuted[ch]) { rWrite(baseAddr+0x40,127); } else { - rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch].vol&0x7f,127)); + rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch].outVol&0x7f,127)); } break; } @@ -109,14 +140,13 @@ int DivPlatformYM2608Ext::dispatch(DivCommand c) { } else { opChan[ch].pan=(c.value2>0)|((c.value>0)<<1); } - DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM); if (parent->song.sharedExtStat) { for (int i=0; i<4; i++) { if (ch==i) continue; opChan[i].pan=opChan[ch].pan; } } - rWrite(chanOffs[2]+0xb4,(opChan[ch].pan<<6)|(ins->fm.fms&7)|((ins->fm.ams&3)<<4)); + rWrite(chanOffs[2]+0xb4,(IS_EXTCH_MUTED?0:(opChan[ch].pan<<6))|(chan[2].state.fms&7)|((chan[2].state.ams&3)<<4)); break; } case DIV_CMD_PITCH: { @@ -162,7 +192,8 @@ int DivPlatformYM2608Ext::dispatch(DivCommand c) { break; } case DIV_CMD_FM_LFO: { - rWrite(0x22,(c.value&7)|((c.value>>4)<<3)); + lfoValue=(c.value&7)|((c.value>>4)<<3); + rWrite(0x22,lfoValue); break; } case DIV_CMD_FM_FB: { @@ -170,20 +201,23 @@ int DivPlatformYM2608Ext::dispatch(DivCommand c) { rWrite(chanOffs[2]+ADDR_FB_ALG,(chan[2].state.alg&7)|(chan[2].state.fb<<3)); break; } - case DIV_CMD_FM_MULT: { // TODO + case DIV_CMD_FM_MULT: { unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]]; - DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM); - DivInstrumentFM::Operator op=ins->fm.op[orderedOps[c.value]]; - rWrite(baseAddr+0x30,(c.value2&15)|(dtTable[op.dt&7]<<4)); + DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]]; + op.mult=c.value2&15; + rWrite(baseAddr+0x30,(op.mult&15)|(dtTable[op.dt&7]<<4)); break; } - case DIV_CMD_FM_TL: { // TODO + case DIV_CMD_FM_TL: { unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]]; - DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM); - if (isOutput[ins->fm.alg][c.value]) { - rWrite(baseAddr+0x40,127-(((127-c.value2)*(opChan[ch].vol&0x7f))/127)); + DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]]; + op.tl=c.value2; + if (isOpMuted[ch]) { + rWrite(baseAddr+0x40,127); + } else if (KVS(2,c.value)) { + rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch].outVol&0x7f,127)); } else { - rWrite(baseAddr+0x40,c.value2); + rWrite(baseAddr+0x40,op.tl); } break; } @@ -377,6 +411,91 @@ void DivPlatformYM2608Ext::tick(bool sysTick) { } } + if (extMode && !noExtMacros) for (int i=0; i<4; i++) { + opChan[i].std.next(); + + if (opChan[i].std.vol.had) { + opChan[i].outVol=VOL_SCALE_LOG_BROKEN(opChan[i].vol,MIN(127,opChan[i].std.vol.val),127); + unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[i]]; + DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[i]]; + if (isOpMuted[i]) { + rWrite(baseAddr+ADDR_TL,127); + } else { + rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[i].outVol&0x7f,127)); + } + } + + if (opChan[i].std.arp.had) { + if (!opChan[i].inPorta) { + opChan[i].baseFreq=NOTE_FNUM_BLOCK(parent->calcArp(opChan[i].note,opChan[i].std.arp.val),11); + } + opChan[i].freqChanged=true; + } + + if (opChan[i].std.pitch.had) { + if (opChan[i].std.pitch.mode) { + opChan[i].pitch2+=opChan[i].std.pitch.val; + CLAMP_VAR(opChan[i].pitch2,-32768,32767); + } else { + opChan[i].pitch2=opChan[i].std.pitch.val; + } + opChan[i].freqChanged=true; + } + + // param macros + unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[i]]; + DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[i]]; + DivMacroInt::IntOp& m=opChan[i].std.op[orderedOps[i]]; + if (m.am.had) { + op.am=m.am.val; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + if (m.ar.had) { + op.ar=m.ar.val; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + if (m.dr.had) { + op.dr=m.dr.val; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + if (m.mult.had) { + op.mult=m.mult.val; + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + } + if (m.rr.had) { + op.rr=m.rr.val; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + if (m.sl.had) { + op.sl=m.sl.val; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + if (m.tl.had) { + op.tl=127-m.tl.val; + if (isOpMuted[i]) { + rWrite(baseAddr+ADDR_TL,127); + } else { + rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[i].outVol&0x7f,127)); + } + } + if (m.rs.had) { + op.rs=m.rs.val; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + if (m.dt.had) { + op.dt=m.dt.val; + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + } + if (m.d2r.had) { + op.d2r=m.d2r.val; + rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); + } + if (m.ssg.had) { + op.ssgEnv=m.ssg.val; + rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); + } + } + DivPlatformYM2608::tick(sysTick); bool writeNoteOn=false; @@ -428,16 +547,20 @@ void DivPlatformYM2608Ext::muteChannel(int ch, bool mute) { isOpMuted[ch-2]=mute; int ordch=orderedOps[ch-2]; - DivInstrument* ins=parent->getIns(opChan[ch-2].ins,DIV_INS_FM); unsigned short baseAddr=chanOffs[2]|opOffs[ordch]; - DivInstrumentFM::Operator op=ins->fm.op[ordch]; + DivInstrumentFM::Operator op=chan[2].state.op[ordch]; if (isOpMuted[ch-2]) { rWrite(baseAddr+0x40,127); - } else if (isOutput[ins->fm.alg][ordch]) { - rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-2].vol&0x7f,127)); + immWrite(baseAddr+0x40,127); + } else if (KVS(2,ordch)) { + rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-2].outVol&0x7f,127)); + immWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-2].outVol&0x7f,127)); } else { rWrite(baseAddr+0x40,op.tl); + immWrite(baseAddr+0x40,op.tl); } + + rWrite(chanOffs[2]+0xb4,(IS_EXTCH_MUTED?0:(opChan[ch-2].pan<<6))|(chan[2].state.fms&7)|((chan[2].state.ams&3)<<4)); } void DivPlatformYM2608Ext::forceIns() { @@ -445,11 +568,11 @@ void DivPlatformYM2608Ext::forceIns() { for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[i]|opOffs[j]; DivInstrumentFM::Operator& op=chan[i].state.op[j]; - if (i==2) { // extended channel + if (i==2 && extMode) { // extended channel if (isOpMuted[j]) { rWrite(baseAddr+0x40,127); } else if (KVS(i,j)) { - rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[j].vol&0x7f,127)); + rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[j].outVol&0x7f,127)); } else { rWrite(baseAddr+0x40,op.tl); } @@ -472,7 +595,11 @@ void DivPlatformYM2608Ext::forceIns() { rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); } rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); - rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); + if (i==2) { + rWrite(chanOffs[i]+ADDR_LRAF,(IS_EXTCH_MUTED?0:(opChan[0].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); + } else { + rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); + } if (chan[i].active) { chan[i].keyOn=true; chan[i].freqChanged=true; @@ -493,6 +620,7 @@ void DivPlatformYM2608Ext::forceIns() { immWrite(i.addr&15,i.val); } ay->getRegisterWrites().clear(); + immWrite(0x22,lfoValue); for (int i=0; i<4; i++) { opChan[i].insChanged=true; if (opChan[i].active) { @@ -511,7 +639,7 @@ void* DivPlatformYM2608Ext::getChanState(int ch) { DivMacroInt* DivPlatformYM2608Ext::getChanMacroInt(int ch) { if (ch>=9 && ch<12) return ay->getChanMacroInt(ch-9); if (ch>=6) return &chan[ch-3].std; - if (ch>=2) return NULL; // currently not implemented + if (ch>=2) return &opChan[ch-2].std; return &chan[ch].std; } @@ -526,7 +654,9 @@ void DivPlatformYM2608Ext::reset() { for (int i=0; i<4; i++) { opChan[i]=DivPlatformOPN::OPNOpChannelStereo(); + opChan[i].std.setEngine(parent); opChan[i].vol=127; + opChan[i].outVol=127; } // channel 2 mode diff --git a/src/engine/platform/ym2610.cpp b/src/engine/platform/ym2610.cpp index e3735e3a..7a0a4df7 100644 --- a/src/engine/platform/ym2610.cpp +++ b/src/engine/platform/ym2610.cpp @@ -930,7 +930,9 @@ int DivPlatformYM2610::dispatch(DivCommand c) { break; } case DIV_CMD_FM_LFO: { - rWrite(0x22,(c.value&7)|((c.value>>4)<<3)); + if (c.chan>=psgChanOffs) break; + lfoValue=(c.value&7)|((c.value>>4)<<3); + rWrite(0x22,lfoValue); break; } case DIV_CMD_FM_FB: { @@ -1173,6 +1175,7 @@ void DivPlatformYM2610::forceIns() { chan[i].freqChanged=true; } } + immWrite(0x22,lfoValue); for (int i=adpcmAChanOffs; i<=adpcmBChanOffs; i++) { chan[i].insChanged=true; } @@ -1247,6 +1250,7 @@ void DivPlatformYM2610::reset() { } lastBusy=60; + lfoValue=8; sampleBank=0; DivPlatformYM2610Base::reset(); @@ -1255,7 +1259,7 @@ void DivPlatformYM2610::reset() { extMode=false; // LFO - immWrite(0x22,0x08); + immWrite(0x22,lfoValue); // PCM volume immWrite(0x101,globalADPCMAVolume); // A diff --git a/src/engine/platform/ym2610b.cpp b/src/engine/platform/ym2610b.cpp index 13dccd22..cb30063a 100644 --- a/src/engine/platform/ym2610b.cpp +++ b/src/engine/platform/ym2610b.cpp @@ -993,7 +993,9 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { break; } case DIV_CMD_FM_LFO: { - rWrite(0x22,(c.value&7)|((c.value>>4)<<3)); + if (c.chan>=psgChanOffs) break; + lfoValue=(c.value&7)|((c.value>>4)<<3); + rWrite(0x22,lfoValue); break; } case DIV_CMD_FM_FB: { @@ -1236,6 +1238,7 @@ void DivPlatformYM2610B::forceIns() { chan[i].freqChanged=true; } } + immWrite(0x22,lfoValue); for (int i=adpcmAChanOffs; i<=adpcmBChanOffs; i++) { chan[i].insChanged=true; } @@ -1310,6 +1313,7 @@ void DivPlatformYM2610B::reset() { } lastBusy=60; + lfoValue=8; sampleBank=0; DivPlatformYM2610Base::reset(); @@ -1318,7 +1322,7 @@ void DivPlatformYM2610B::reset() { extMode=false; // LFO - immWrite(0x22,0x08); + immWrite(0x22,lfoValue); // PCM volume immWrite(0x101,0x3f); // A diff --git a/src/engine/platform/ym2610bext.cpp b/src/engine/platform/ym2610bext.cpp index adde670a..207708bd 100644 --- a/src/engine/platform/ym2610bext.cpp +++ b/src/engine/platform/ym2610bext.cpp @@ -37,15 +37,32 @@ int DivPlatformYM2610BExt::dispatch(DivCommand c) { switch (c.cmd) { case DIV_CMD_NOTE_ON: { DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM); + + if (opChan[ch].insChanged) { + chan[extChanOffs].state.alg=ins->fm.alg; + chan[extChanOffs].state.fb=ins->fm.fb; + chan[extChanOffs].state.fms=ins->fm.fms; + chan[extChanOffs].state.ams=ins->fm.ams; + chan[extChanOffs].state.op[ordch]=ins->fm.op[ordch]; + } + + if (noExtMacros) { + opChan[ch].macroInit(NULL); + } else { + opChan[ch].macroInit(ins); + } + if (!opChan[ch].std.vol.will) { + opChan[ch].outVol=opChan[ch].vol; + } unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[ordch]; - DivInstrumentFM::Operator op=ins->fm.op[ordch]; + DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[ordch]; // TODO: how does this work?! if (isOpMuted[ch]) { rWrite(baseAddr+0x40,127); } else { if (opChan[ch].insChanged) { - rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch].vol&0x7f,127)); + rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch].outVol&0x7f,127)); } } if (opChan[ch].insChanged) { @@ -58,14 +75,15 @@ int DivPlatformYM2610BExt::dispatch(DivCommand c) { opChan[ch].mask=op.enable; } if (opChan[ch].insChanged) { // TODO how does this work? - rWrite(chanOffs[extChanOffs]+0xb0,(ins->fm.alg&7)|(ins->fm.fb<<3)); - rWrite(chanOffs[extChanOffs]+0xb4,(opChan[ch].pan<<6)|(ins->fm.fms&7)|((ins->fm.ams&3)<<4)); + rWrite(chanOffs[extChanOffs]+0xb0,(chan[extChanOffs].state.alg&7)|(chan[extChanOffs].state.fb<<3)); + rWrite(chanOffs[extChanOffs]+0xb4,(IS_EXTCH_MUTED?0:(opChan[ch].pan<<6))|(chan[extChanOffs].state.fms&7)|((chan[extChanOffs].state.ams&3)<<4)); } opChan[ch].insChanged=false; if (c.value!=DIV_NOTE_NULL) { opChan[ch].baseFreq=NOTE_FNUM_BLOCK(c.value,11); opChan[ch].portaPause=false; + opChan[ch].note=c.value; opChan[ch].freqChanged=true; } opChan[ch].keyOn=true; @@ -77,15 +95,28 @@ int DivPlatformYM2610BExt::dispatch(DivCommand c) { opChan[ch].keyOn=false; opChan[ch].active=false; break; + case DIV_CMD_NOTE_OFF_ENV: + if (noExtMacros) break; + opChan[ch].keyOff=true; + opChan[ch].keyOn=false; + opChan[ch].active=false; + opChan[ch].std.release(); + break; + case DIV_CMD_ENV_RELEASE: + if (noExtMacros) break; + opChan[ch].std.release(); + break; case DIV_CMD_VOLUME: { opChan[ch].vol=c.value; - DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM); + if (!opChan[ch].std.vol.has) { + opChan[ch].outVol=c.value; + } unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[ordch]; - DivInstrumentFM::Operator op=ins->fm.op[ordch]; + DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[ordch]; if (isOpMuted[ch]) { rWrite(baseAddr+0x40,127); } else { - rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch].vol&0x7f,127)); + rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch].outVol&0x7f,127)); } break; } @@ -105,14 +136,13 @@ int DivPlatformYM2610BExt::dispatch(DivCommand c) { } else { opChan[ch].pan=(c.value2>0)|((c.value>0)<<1); } - DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM); if (parent->song.sharedExtStat) { for (int i=0; i<4; i++) { if (ch==i) continue; opChan[i].pan=opChan[ch].pan; } } - rWrite(chanOffs[extChanOffs]+0xb4,(opChan[ch].pan<<6)|(ins->fm.fms&7)|((ins->fm.ams&3)<<4)); + rWrite(chanOffs[extChanOffs]+0xb4,(IS_EXTCH_MUTED?0:(opChan[ch].pan<<6))|(chan[extChanOffs].state.fms&7)|((chan[extChanOffs].state.ams&3)<<4)); break; } case DIV_CMD_PITCH: { @@ -158,7 +188,8 @@ int DivPlatformYM2610BExt::dispatch(DivCommand c) { break; } case DIV_CMD_FM_LFO: { - rWrite(0x22,(c.value&7)|((c.value>>4)<<3)); + lfoValue=(c.value&7)|((c.value>>4)<<3); + rWrite(0x22,lfoValue); break; } case DIV_CMD_FM_FB: { @@ -166,20 +197,23 @@ int DivPlatformYM2610BExt::dispatch(DivCommand c) { rWrite(chanOffs[extChanOffs]+ADDR_FB_ALG,(chan[extChanOffs].state.alg&7)|(chan[extChanOffs].state.fb<<3)); break; } - case DIV_CMD_FM_MULT: { // TODO + case DIV_CMD_FM_MULT: { unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[orderedOps[c.value]]; - DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM); - DivInstrumentFM::Operator op=ins->fm.op[orderedOps[c.value]]; - rWrite(baseAddr+0x30,(c.value2&15)|(dtTable[op.dt&7]<<4)); + DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[orderedOps[c.value]]; + op.mult=c.value2&15; + rWrite(baseAddr+0x30,(op.mult&15)|(dtTable[op.dt&7]<<4)); break; } - case DIV_CMD_FM_TL: { // TODO + case DIV_CMD_FM_TL: { unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[orderedOps[c.value]]; - DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM); - if (isOutput[ins->fm.alg][c.value]) { - rWrite(baseAddr+0x40,127-(((127-c.value2)*(opChan[ch].vol&0x7f))/127)); + DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[orderedOps[c.value]]; + op.tl=c.value2; + if (isOpMuted[ch]) { + rWrite(baseAddr+0x40,127); + } else if (KVS(2,c.value)) { + rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch].outVol&0x7f,127)); } else { - rWrite(baseAddr+0x40,c.value2); + rWrite(baseAddr+0x40,op.tl); } break; } @@ -373,6 +407,91 @@ void DivPlatformYM2610BExt::tick(bool sysTick) { } } + if (extMode && !noExtMacros) for (int i=0; i<4; i++) { + opChan[i].std.next(); + + if (opChan[i].std.vol.had) { + opChan[i].outVol=VOL_SCALE_LOG_BROKEN(opChan[i].vol,MIN(127,opChan[i].std.vol.val),127); + unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[orderedOps[i]]; + DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[orderedOps[i]]; + if (isOpMuted[i]) { + rWrite(baseAddr+ADDR_TL,127); + } else { + rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[i].outVol&0x7f,127)); + } + } + + if (opChan[i].std.arp.had) { + if (!opChan[i].inPorta) { + opChan[i].baseFreq=NOTE_FNUM_BLOCK(parent->calcArp(opChan[i].note,opChan[i].std.arp.val),11); + } + opChan[i].freqChanged=true; + } + + if (opChan[i].std.pitch.had) { + if (opChan[i].std.pitch.mode) { + opChan[i].pitch2+=opChan[i].std.pitch.val; + CLAMP_VAR(opChan[i].pitch2,-32768,32767); + } else { + opChan[i].pitch2=opChan[i].std.pitch.val; + } + opChan[i].freqChanged=true; + } + + // param macros + unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[orderedOps[i]]; + DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[orderedOps[i]]; + DivMacroInt::IntOp& m=opChan[i].std.op[orderedOps[i]]; + if (m.am.had) { + op.am=m.am.val; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + if (m.ar.had) { + op.ar=m.ar.val; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + if (m.dr.had) { + op.dr=m.dr.val; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + if (m.mult.had) { + op.mult=m.mult.val; + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + } + if (m.rr.had) { + op.rr=m.rr.val; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + if (m.sl.had) { + op.sl=m.sl.val; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + if (m.tl.had) { + op.tl=127-m.tl.val; + if (isOpMuted[i]) { + rWrite(baseAddr+ADDR_TL,127); + } else { + rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[i].outVol&0x7f,127)); + } + } + if (m.rs.had) { + op.rs=m.rs.val; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + if (m.dt.had) { + op.dt=m.dt.val; + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + } + if (m.d2r.had) { + op.d2r=m.d2r.val; + rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); + } + if (m.ssg.had) { + op.ssgEnv=m.ssg.val; + rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); + } + } + DivPlatformYM2610B::tick(sysTick); bool writeNoteOn=false; @@ -424,16 +543,20 @@ void DivPlatformYM2610BExt::muteChannel(int ch, bool mute) { isOpMuted[ch-extChanOffs]=mute; int ordch=orderedOps[ch-extChanOffs]; - DivInstrument* ins=parent->getIns(opChan[ch-extChanOffs].ins,DIV_INS_FM); unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[ordch]; - DivInstrumentFM::Operator op=ins->fm.op[ordch]; + DivInstrumentFM::Operator op=chan[extChanOffs].state.op[ordch]; if (isOpMuted[ch-extChanOffs]) { rWrite(baseAddr+0x40,127); - } else if (isOutput[ins->fm.alg][ordch]) { - rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-extChanOffs].vol&0x7f,127)); + immWrite(baseAddr+0x40,127); + } else if (KVS(2,ordch)) { + rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-extChanOffs].outVol&0x7f,127)); + immWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-extChanOffs].outVol&0x7f,127)); } else { rWrite(baseAddr+0x40,op.tl); + immWrite(baseAddr+0x40,op.tl); } + + rWrite(chanOffs[extChanOffs]+0xb4,(IS_EXTCH_MUTED?0:(opChan[ch-extChanOffs].pan<<6))|(chan[extChanOffs].state.fms&7)|((chan[extChanOffs].state.ams&3)<<4)); } void DivPlatformYM2610BExt::forceIns() { @@ -441,11 +564,11 @@ void DivPlatformYM2610BExt::forceIns() { for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[i]|opOffs[j]; DivInstrumentFM::Operator& op=chan[i].state.op[j]; - if (i==2 && extMode) { // extended channel + if (i==extChanOffs && extMode) { // extended channel if (isOpMuted[j]) { rWrite(baseAddr+0x40,127); } else if (KVS(i,j)) { - rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[j].vol&0x7f,127)); + rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[j].outVol&0x7f,127)); } else { rWrite(baseAddr+0x40,op.tl); } @@ -468,7 +591,11 @@ void DivPlatformYM2610BExt::forceIns() { rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); } rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); - rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); + if (i==extChanOffs) { + rWrite(chanOffs[i]+ADDR_LRAF,(IS_EXTCH_MUTED?0:(opChan[0].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); + } else { + rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); + } if (chan[i].active) { chan[i].keyOn=true; chan[i].freqChanged=true; @@ -484,6 +611,7 @@ void DivPlatformYM2610BExt::forceIns() { immWrite(i.addr&15,i.val); } ay->getRegisterWrites().clear(); + immWrite(0x22,lfoValue); for (int i=0; i<4; i++) { opChan[i].insChanged=true; if (opChan[i].active) { @@ -502,7 +630,7 @@ void* DivPlatformYM2610BExt::getChanState(int ch) { DivMacroInt* DivPlatformYM2610BExt::getChanMacroInt(int ch) { if (ch>=(psgChanOffs+3) && ch<(adpcmAChanOffs+3)) return ay->getChanMacroInt(ch-psgChanOffs-3); if (ch>=(extChanOffs+4)) return &chan[ch-3].std; - if (ch>=extChanOffs) return NULL; // currently not implemented + if (ch>=extChanOffs) return &opChan[ch-extChanOffs].std; return &chan[ch].std; } @@ -517,7 +645,9 @@ void DivPlatformYM2610BExt::reset() { for (int i=0; i<4; i++) { opChan[i]=DivPlatformOPN::OPNOpChannelStereo(); + opChan[i].std.setEngine(parent); opChan[i].vol=127; + opChan[i].outVol=127; } // channel 2 mode diff --git a/src/engine/platform/ym2610ext.cpp b/src/engine/platform/ym2610ext.cpp index a86df585..b4da0878 100644 --- a/src/engine/platform/ym2610ext.cpp +++ b/src/engine/platform/ym2610ext.cpp @@ -37,15 +37,32 @@ int DivPlatformYM2610Ext::dispatch(DivCommand c) { switch (c.cmd) { case DIV_CMD_NOTE_ON: { DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM); + + if (opChan[ch].insChanged) { + chan[extChanOffs].state.alg=ins->fm.alg; + chan[extChanOffs].state.fb=ins->fm.fb; + chan[extChanOffs].state.fms=ins->fm.fms; + chan[extChanOffs].state.ams=ins->fm.ams; + chan[extChanOffs].state.op[ordch]=ins->fm.op[ordch]; + } + + if (noExtMacros) { + opChan[ch].macroInit(NULL); + } else { + opChan[ch].macroInit(ins); + } + if (!opChan[ch].std.vol.will) { + opChan[ch].outVol=opChan[ch].vol; + } unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[ordch]; - DivInstrumentFM::Operator op=ins->fm.op[ordch]; + DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[ordch]; // TODO: how does this work?! if (isOpMuted[ch]) { rWrite(baseAddr+0x40,127); } else { if (opChan[ch].insChanged) { - rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch].vol&0x7f,127)); + rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch].outVol&0x7f,127)); } } if (opChan[ch].insChanged) { @@ -58,14 +75,15 @@ int DivPlatformYM2610Ext::dispatch(DivCommand c) { opChan[ch].mask=op.enable; } if (opChan[ch].insChanged) { // TODO how does this work? - rWrite(chanOffs[extChanOffs]+0xb0,(ins->fm.alg&7)|(ins->fm.fb<<3)); - rWrite(chanOffs[extChanOffs]+0xb4,(opChan[ch].pan<<6)|(ins->fm.fms&7)|((ins->fm.ams&3)<<4)); + rWrite(chanOffs[extChanOffs]+0xb0,(chan[extChanOffs].state.alg&7)|(chan[extChanOffs].state.fb<<3)); + rWrite(chanOffs[extChanOffs]+0xb4,(IS_EXTCH_MUTED?0:(opChan[ch].pan<<6))|(chan[extChanOffs].state.fms&7)|((chan[extChanOffs].state.ams&3)<<4)); } opChan[ch].insChanged=false; if (c.value!=DIV_NOTE_NULL) { opChan[ch].baseFreq=NOTE_FNUM_BLOCK(c.value,11); opChan[ch].portaPause=false; + opChan[ch].note=c.value; opChan[ch].freqChanged=true; } opChan[ch].keyOn=true; @@ -77,15 +95,28 @@ int DivPlatformYM2610Ext::dispatch(DivCommand c) { opChan[ch].keyOn=false; opChan[ch].active=false; break; + case DIV_CMD_NOTE_OFF_ENV: + if (noExtMacros) break; + opChan[ch].keyOff=true; + opChan[ch].keyOn=false; + opChan[ch].active=false; + opChan[ch].std.release(); + break; + case DIV_CMD_ENV_RELEASE: + if (noExtMacros) break; + opChan[ch].std.release(); + break; case DIV_CMD_VOLUME: { opChan[ch].vol=c.value; - DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM); + if (!opChan[ch].std.vol.has) { + opChan[ch].outVol=c.value; + } unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[ordch]; - DivInstrumentFM::Operator op=ins->fm.op[ordch]; + DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[ordch]; if (isOpMuted[ch]) { rWrite(baseAddr+0x40,127); } else { - rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch].vol&0x7f,127)); + rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch].outVol&0x7f,127)); } break; } @@ -105,14 +136,13 @@ int DivPlatformYM2610Ext::dispatch(DivCommand c) { } else { opChan[ch].pan=(c.value2>0)|((c.value>0)<<1); } - DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM); if (parent->song.sharedExtStat) { for (int i=0; i<4; i++) { if (ch==i) continue; opChan[i].pan=opChan[ch].pan; } } - rWrite(chanOffs[extChanOffs]+0xb4,(opChan[ch].pan<<6)|(ins->fm.fms&7)|((ins->fm.ams&3)<<4)); + rWrite(chanOffs[extChanOffs]+0xb4,(IS_EXTCH_MUTED?0:(opChan[ch].pan<<6))|(chan[extChanOffs].state.fms&7)|((chan[extChanOffs].state.ams&3)<<4)); break; } case DIV_CMD_PITCH: { @@ -158,7 +188,8 @@ int DivPlatformYM2610Ext::dispatch(DivCommand c) { break; } case DIV_CMD_FM_LFO: { - rWrite(0x22,(c.value&7)|((c.value>>4)<<3)); + lfoValue=(c.value&7)|((c.value>>4)<<3); + rWrite(0x22,lfoValue); break; } case DIV_CMD_FM_FB: { @@ -166,20 +197,23 @@ int DivPlatformYM2610Ext::dispatch(DivCommand c) { rWrite(chanOffs[extChanOffs]+ADDR_FB_ALG,(chan[extChanOffs].state.alg&7)|(chan[extChanOffs].state.fb<<3)); break; } - case DIV_CMD_FM_MULT: { // TODO + case DIV_CMD_FM_MULT: { unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[orderedOps[c.value]]; - DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM); - DivInstrumentFM::Operator op=ins->fm.op[orderedOps[c.value]]; - rWrite(baseAddr+0x30,(c.value2&15)|(dtTable[op.dt&7]<<4)); + DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[orderedOps[c.value]]; + op.mult=c.value2&15; + rWrite(baseAddr+0x30,(op.mult&15)|(dtTable[op.dt&7]<<4)); break; } - case DIV_CMD_FM_TL: { // TODO + case DIV_CMD_FM_TL: { unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[orderedOps[c.value]]; - DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM); - if (isOutput[ins->fm.alg][c.value]) { - rWrite(baseAddr+0x40,127-(((127-c.value2)*(opChan[ch].vol&0x7f))/127)); + DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[orderedOps[c.value]]; + op.tl=c.value2; + if (isOpMuted[ch]) { + rWrite(baseAddr+0x40,127); + } else if (KVS(2,c.value)) { + rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch].outVol&0x7f,127)); } else { - rWrite(baseAddr+0x40,c.value2); + rWrite(baseAddr+0x40,op.tl); } break; } @@ -373,6 +407,91 @@ void DivPlatformYM2610Ext::tick(bool sysTick) { } } + if (extMode && !noExtMacros) for (int i=0; i<4; i++) { + opChan[i].std.next(); + + if (opChan[i].std.vol.had) { + opChan[i].outVol=VOL_SCALE_LOG_BROKEN(opChan[i].vol,MIN(127,opChan[i].std.vol.val),127); + unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[orderedOps[i]]; + DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[orderedOps[i]]; + if (isOpMuted[i]) { + rWrite(baseAddr+ADDR_TL,127); + } else { + rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[i].outVol&0x7f,127)); + } + } + + if (opChan[i].std.arp.had) { + if (!opChan[i].inPorta) { + opChan[i].baseFreq=NOTE_FNUM_BLOCK(parent->calcArp(opChan[i].note,opChan[i].std.arp.val),11); + } + opChan[i].freqChanged=true; + } + + if (opChan[i].std.pitch.had) { + if (opChan[i].std.pitch.mode) { + opChan[i].pitch2+=opChan[i].std.pitch.val; + CLAMP_VAR(opChan[i].pitch2,-32768,32767); + } else { + opChan[i].pitch2=opChan[i].std.pitch.val; + } + opChan[i].freqChanged=true; + } + + // param macros + unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[orderedOps[i]]; + DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[orderedOps[i]]; + DivMacroInt::IntOp& m=opChan[i].std.op[orderedOps[i]]; + if (m.am.had) { + op.am=m.am.val; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + if (m.ar.had) { + op.ar=m.ar.val; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + if (m.dr.had) { + op.dr=m.dr.val; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + if (m.mult.had) { + op.mult=m.mult.val; + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + } + if (m.rr.had) { + op.rr=m.rr.val; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + if (m.sl.had) { + op.sl=m.sl.val; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + if (m.tl.had) { + op.tl=127-m.tl.val; + if (isOpMuted[i]) { + rWrite(baseAddr+ADDR_TL,127); + } else { + rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[i].outVol&0x7f,127)); + } + } + if (m.rs.had) { + op.rs=m.rs.val; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + if (m.dt.had) { + op.dt=m.dt.val; + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + } + if (m.d2r.had) { + op.d2r=m.d2r.val; + rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); + } + if (m.ssg.had) { + op.ssgEnv=m.ssg.val; + rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); + } + } + DivPlatformYM2610::tick(sysTick); bool writeNoteOn=false; @@ -424,16 +543,20 @@ void DivPlatformYM2610Ext::muteChannel(int ch, bool mute) { isOpMuted[ch-extChanOffs]=mute; int ordch=orderedOps[ch-extChanOffs]; - DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM); unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[ordch]; - DivInstrumentFM::Operator op=ins->fm.op[ordch]; - if (isOpMuted[ch]) { + DivInstrumentFM::Operator op=chan[extChanOffs].state.op[ordch]; + if (isOpMuted[ch-extChanOffs]) { rWrite(baseAddr+0x40,127); - } else if (isOutput[ins->fm.alg][ordch]) { - rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-extChanOffs].vol&0x7f,127)); + immWrite(baseAddr+0x40,127); + } else if (KVS(2,ordch)) { + rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-extChanOffs].outVol&0x7f,127)); + immWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-extChanOffs].outVol&0x7f,127)); } else { rWrite(baseAddr+0x40,op.tl); + immWrite(baseAddr+0x40,op.tl); } + + rWrite(chanOffs[extChanOffs]+0xb4,(IS_EXTCH_MUTED?0:(opChan[ch-extChanOffs].pan<<6))|(chan[extChanOffs].state.fms&7)|((chan[extChanOffs].state.ams&3)<<4)); } void DivPlatformYM2610Ext::forceIns() { @@ -441,11 +564,11 @@ void DivPlatformYM2610Ext::forceIns() { for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[i]|opOffs[j]; DivInstrumentFM::Operator& op=chan[i].state.op[j]; - if (i==1 && extMode) { // extended channel + if (i==extChanOffs && extMode) { // extended channel if (isOpMuted[j]) { rWrite(baseAddr+0x40,127); } else if (KVS(i,j)) { - rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[j].vol&0x7f,127)); + rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[j].outVol&0x7f,127)); } else { rWrite(baseAddr+0x40,op.tl); } @@ -468,7 +591,11 @@ void DivPlatformYM2610Ext::forceIns() { rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); } rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); - rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); + if (i==extChanOffs) { + rWrite(chanOffs[i]+ADDR_LRAF,(IS_EXTCH_MUTED?0:(opChan[0].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); + } else { + rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); + } if (chan[i].active) { chan[i].keyOn=true; chan[i].freqChanged=true; @@ -484,6 +611,7 @@ void DivPlatformYM2610Ext::forceIns() { immWrite(i.addr&15,i.val); } ay->getRegisterWrites().clear(); + immWrite(0x22,lfoValue); for (int i=0; i<4; i++) { opChan[i].insChanged=true; if (opChan[i].active) { @@ -502,7 +630,7 @@ void* DivPlatformYM2610Ext::getChanState(int ch) { DivMacroInt* DivPlatformYM2610Ext::getChanMacroInt(int ch) { if (ch>=(psgChanOffs+3) && ch<(adpcmAChanOffs+3)) return ay->getChanMacroInt(ch-psgChanOffs-3); if (ch>=(extChanOffs+4)) return &chan[ch-3].std; - if (ch>=extChanOffs) return NULL; // currently not implemented + if (ch>=extChanOffs) return &opChan[ch-extChanOffs].std; return &chan[ch].std; } @@ -517,7 +645,9 @@ void DivPlatformYM2610Ext::reset() { for (int i=0; i<4; i++) { opChan[i]=DivPlatformOPN::OPNOpChannelStereo(); + opChan[i].std.setEngine(parent); opChan[i].vol=127; + opChan[i].outVol=127; } // channel 2 mode diff --git a/src/engine/song.h b/src/engine/song.h index d0d386c6..d9702215 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -64,10 +64,10 @@ enum DivSystem { DIV_SYSTEM_FDS, DIV_SYSTEM_MMC5, DIV_SYSTEM_N163, - DIV_SYSTEM_OPN, - DIV_SYSTEM_OPN_EXT, - DIV_SYSTEM_PC98, - DIV_SYSTEM_PC98_EXT, + DIV_SYSTEM_YM2203, + DIV_SYSTEM_YM2203_EXT, + DIV_SYSTEM_YM2608, + DIV_SYSTEM_YM2608_EXT, DIV_SYSTEM_OPL, DIV_SYSTEM_OPL2, DIV_SYSTEM_OPL3, @@ -111,8 +111,8 @@ enum DivSystem { DIV_SYSTEM_NAMCO, DIV_SYSTEM_NAMCO_15XX, DIV_SYSTEM_NAMCO_CUS30, - DIV_SYSTEM_YM2612_FRAC, - DIV_SYSTEM_YM2612_FRAC_EXT, + DIV_SYSTEM_YM2612_DUALPCM, + DIV_SYSTEM_YM2612_DUALPCM_EXT, DIV_SYSTEM_MSM5232, DIV_SYSTEM_T6W28, DIV_SYSTEM_K007232, @@ -120,6 +120,11 @@ enum DivSystem { DIV_SYSTEM_PCM_DAC, DIV_SYSTEM_PONG, DIV_SYSTEM_DUMMY, + DIV_SYSTEM_YM2612_CSM, + DIV_SYSTEM_YM2610_CSM, + DIV_SYSTEM_YM2610B_CSM, + DIV_SYSTEM_YM2203_CSM, + DIV_SYSTEM_YM2608_CSM, DIV_SYSTEM_MAX // boundary for max system number }; diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index e4a7c1fe..c6478ca7 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -1014,7 +1014,7 @@ void DivEngine::registerSystems() { } ); - sysDefs[DIV_SYSTEM_OPN]=new DivSysDef( + sysDefs[DIV_SYSTEM_YM2203]=new DivSysDef( "Yamaha YM2203 (OPN)", NULL, 0x8d, 0, 6, true, true, 0x151, false, 1U<writeC(2|baseAddr1); w->writeC(0x80+i); @@ -252,8 +252,8 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write w->writeC(0); } break; - case DIV_SYSTEM_OPN: - case DIV_SYSTEM_OPN_EXT: + case DIV_SYSTEM_YM2203: + case DIV_SYSTEM_YM2203_EXT: for (int i=0; i<3; i++) { // set SL and RR to highest w->writeC(5|baseAddr1); w->writeC(0x80+i); @@ -333,6 +333,13 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write w->writeC(0); } break; + case DIV_SYSTEM_POKEY: + for (int i=0; i<9; i++) { + w->writeC(0xbb); + w->writeC(i|baseAddr2); + w->writeC(0); + } + break; case DIV_SYSTEM_LYNX: w->writeC(0x4e); w->writeC(0x44); @@ -600,8 +607,8 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write switch (sys) { case DIV_SYSTEM_YM2612: case DIV_SYSTEM_YM2612_EXT: - case DIV_SYSTEM_YM2612_FRAC: - case DIV_SYSTEM_YM2612_FRAC_EXT: + case DIV_SYSTEM_YM2612_DUALPCM: + case DIV_SYSTEM_YM2612_DUALPCM_EXT: switch (write.addr>>8) { case 0: // port 0 w->writeC(2|baseAddr1); @@ -693,14 +700,14 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write break; } break; - case DIV_SYSTEM_OPN: - case DIV_SYSTEM_OPN_EXT: + case DIV_SYSTEM_YM2203: + case DIV_SYSTEM_YM2203_EXT: w->writeC(5|baseAddr1); w->writeC(write.addr&0xff); w->writeC(write.val); break; - case DIV_SYSTEM_PC98: - case DIV_SYSTEM_PC98_EXT: + case DIV_SYSTEM_YM2608: + case DIV_SYSTEM_YM2608_EXT: switch (write.addr>>8) { case 0: // port 0 w->writeC(6|baseAddr1); @@ -732,6 +739,11 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write w->writeC(baseAddr2|(write.addr&0xff)); w->writeC(write.val); break; + case DIV_SYSTEM_POKEY: + w->writeC(0xbb); + w->writeC(baseAddr2|(write.addr&0x0f)); + w->writeC(write.val&0xff); + break; case DIV_SYSTEM_LYNX: w->writeC(0x4e); w->writeC(write.addr&0xff); @@ -1208,8 +1220,8 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p break; case DIV_SYSTEM_YM2612: case DIV_SYSTEM_YM2612_EXT: - case DIV_SYSTEM_YM2612_FRAC: - case DIV_SYSTEM_YM2612_FRAC_EXT: + case DIV_SYSTEM_YM2612_DUALPCM: + case DIV_SYSTEM_YM2612_DUALPCM_EXT: if (!hasOPN2) { hasOPN2=disCont[i].dispatch->chipClock; willExport[i]=true; @@ -1232,8 +1244,8 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p howManyChips++; } break; - case DIV_SYSTEM_OPN: - case DIV_SYSTEM_OPN_EXT: + case DIV_SYSTEM_YM2203: + case DIV_SYSTEM_YM2203_EXT: if (!hasOPN) { hasOPN=disCont[i].dispatch->chipClock; willExport[i]=true; @@ -1245,8 +1257,8 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p howManyChips++; } break; - case DIV_SYSTEM_PC98: - case DIV_SYSTEM_PC98_EXT: + case DIV_SYSTEM_YM2608: + case DIV_SYSTEM_YM2608_EXT: if (!hasOPNA) { hasOPNA=disCont[i].dispatch->chipClock; willExport[i]=true; @@ -1286,6 +1298,17 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p howManyChips++; } break; + case DIV_SYSTEM_POKEY: + if (!hasPOKEY) { + hasPOKEY=disCont[i].dispatch->chipClock; + willExport[i]=true; + } else if (!(hasPOKEY&0x40000000)) { + isSecond[i]=true; + willExport[i]=true; + hasPOKEY|=0x40000000; + howManyChips++; + } + break; case DIV_SYSTEM_LYNX: if (!hasLynx) { hasLynx=disCont[i].dispatch->chipClock; @@ -1892,8 +1915,8 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p switch (song.system[i]) { case DIV_SYSTEM_YM2612: case DIV_SYSTEM_YM2612_EXT: - case DIV_SYSTEM_YM2612_FRAC: - case DIV_SYSTEM_YM2612_FRAC_EXT: + case DIV_SYSTEM_YM2612_DUALPCM: + case DIV_SYSTEM_YM2612_DUALPCM_EXT: w->writeC(0x90); w->writeC(streamID); w->writeC(0x02); diff --git a/src/gui/about.cpp b/src/gui/about.cpp index b840d7c7..d7346638 100644 --- a/src/gui/about.cpp +++ b/src/gui/about.cpp @@ -71,6 +71,7 @@ const char* aboutLine[]={ "DeMOSic", "DevEd", "Dippy", + "dumbut", "FΛDE", "Forte", "Fragmare", @@ -78,6 +79,7 @@ const char* aboutLine[]={ "iyatemu", "JayBOB18", "Jimmy-DS", + "Kagamiin~", "kleeder", "jaezu", "Laggy", @@ -152,6 +154,9 @@ const char* aboutLine[]={ "QSound emulator by superctr and Valley Bell", "VICE VIC-20 sound core by Rami Rasanen and viznut", "VERA sound core by Frank van den Hoef", + "mzpokeysnd POKEY emulator by Michael Borisov", + "ASAP POKEY emulator by Piotr Fusik", + "ported by laoo to C++", "K005289 emulator by cam900", "Namco 163 emulator by cam900", "Seta X1-010 emulator by cam900", diff --git a/src/gui/debug.cpp b/src/gui/debug.cpp index 106b1cff..13a6cbf4 100644 --- a/src/gui/debug.cpp +++ b/src/gui/debug.cpp @@ -242,8 +242,8 @@ void putDispatchChip(void* data, int type) { switch (type) { case DIV_SYSTEM_YM2612: case DIV_SYSTEM_YM2612_EXT: - case DIV_SYSTEM_YM2612_FRAC: - case DIV_SYSTEM_YM2612_FRAC_EXT: { + case DIV_SYSTEM_YM2612_DUALPCM: + case DIV_SYSTEM_YM2612_DUALPCM_EXT: { GENESIS_CHIP_DEBUG; break; } @@ -257,8 +257,8 @@ void putDispatchChip(void* data, int type) { SMS_CHIP_DEBUG; break; } - case DIV_SYSTEM_OPN: - case DIV_SYSTEM_OPN_EXT: { + case DIV_SYSTEM_YM2203: + case DIV_SYSTEM_YM2203_EXT: { DivPlatformYM2203* ch=(DivPlatformYM2203*)data; ImGui::Text("> YM2203"); FM_OPN_CHIP_DEBUG; @@ -268,8 +268,8 @@ void putDispatchChip(void* data, int type) { ImGui::TextColored(ch->extMode?colorOn:colorOff,">> ExtMode"); break; } - case DIV_SYSTEM_PC98: - case DIV_SYSTEM_PC98_EXT: { + case DIV_SYSTEM_YM2608: + case DIV_SYSTEM_YM2608_EXT: { DivPlatformYM2608* ch=(DivPlatformYM2608*)data; ImGui::Text("> YM2608"); FM_OPN_CHIP_DEBUG; @@ -560,12 +560,12 @@ void putDispatchChan(void* data, int chanNum, int type) { break; } case DIV_SYSTEM_YM2612: - case DIV_SYSTEM_YM2612_FRAC: { + case DIV_SYSTEM_YM2612_DUALPCM: { GENESIS_CHAN_DEBUG; break; } case DIV_SYSTEM_YM2612_EXT: - case DIV_SYSTEM_YM2612_FRAC_EXT: { + case DIV_SYSTEM_YM2612_DUALPCM_EXT: { if (chanNum>=2 && chanNum<=5) { DivPlatformOPN::OPNOpChannelStereo* ch=(DivPlatformOPN::OPNOpChannelStereo*)data; ImGui::Text("> YM2612 (per operator)"); @@ -579,11 +579,11 @@ void putDispatchChan(void* data, int chanNum, int type) { SMS_CHAN_DEBUG; break; } - case DIV_SYSTEM_OPN: { + case DIV_SYSTEM_YM2203: { OPN_CHAN_DEBUG; break; } - case DIV_SYSTEM_OPN_EXT: { + case DIV_SYSTEM_YM2203_EXT: { if (chanNum>=2 && chanNum<=5) { OPN_OPCHAN_DEBUG; } else { @@ -591,13 +591,13 @@ void putDispatchChan(void* data, int chanNum, int type) { } break; } - case DIV_SYSTEM_PC98: { + case DIV_SYSTEM_YM2608: { DivPlatformOPN::OPNChannelStereo* ch=(DivPlatformOPN::OPNChannelStereo*)data; ImGui::Text("> YM2608"); OPNB_CHAN_DEBUG; break; } - case DIV_SYSTEM_PC98_EXT: { + case DIV_SYSTEM_YM2608_EXT: { if (chanNum>=2 && chanNum<=5) { DivPlatformOPN::OPNOpChannelStereo* ch=(DivPlatformOPN::OPNOpChannelStereo*)data; ImGui::Text("> YM2608 (per operator)"); diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 6753fdfd..6fbc1681 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -592,12 +592,14 @@ void FurnaceGUI::updateWindowTitle() { void FurnaceGUI::autoDetectSystem() { std::map sysCountMap; + std::map sysConfMap; for (int i=0; isong.systemLen; i++) { try { sysCountMap.at(e->song.system[i])++; } catch (std::exception& ex) { sysCountMap[e->song.system[i]]=1; } + sysConfMap[e->song.system[i]]=e->song.systemFlags[i]; } logV("sysCountMap:"); @@ -607,16 +609,20 @@ void FurnaceGUI::autoDetectSystem() { bool isMatch=false; std::map defCountMap; + std::map defConfMap; for (FurnaceGUISysCategory& i: sysCategories) { for (FurnaceGUISysDef& j: i.systems) { defCountMap.clear(); - for (size_t k=0; k l: defConfMap.at(k.first).configMap()) { + if (!sysDC.has(l.first)) { + isMatch=false; + break; + } + if (sysDC.getString(l.first,"")!=l.second) { + isMatch=false; + break; + } + } + if (!isMatch) break; } catch (std::exception& ex) { isMatch=false; break; @@ -1825,6 +1843,7 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { int FurnaceGUI::save(String path, int dmfVersion) { SafeWriter* w; + logD("saving file..."); if (dmfVersion) { if (dmfVersion<24) dmfVersion=24; w=e->saveDMF(dmfVersion); @@ -1833,11 +1852,14 @@ int FurnaceGUI::save(String path, int dmfVersion) { } if (w==NULL) { lastError=e->getLastError(); + logE("couldn't save! %s",lastError); return 3; } + logV("opening file for writing..."); FILE* outFile=ps_fopen(path.c_str(),"wb"); if (outFile==NULL) { lastError=strerror(errno); + logE("couldn't save! %s",lastError); w->finish(); return 1; } @@ -1918,6 +1940,7 @@ int FurnaceGUI::save(String path, int dmfVersion) { showWarning(e->getWarnings(),GUI_WARN_GENERIC); } pushRecentFile(path); + logD("save complete."); return 0; } @@ -4070,7 +4093,6 @@ bool FurnaceGUI::loop() { } break; case GUI_FILE_SAVE: { - logD("saving: %s",copyOfName.c_str()); bool saveWasSuccessful=true; if (save(copyOfName,0)>0) { showError(fmt::sprintf("Error while saving file! (%s)",lastError)); @@ -5019,8 +5041,12 @@ bool FurnaceGUI::loop() { ImGui::Checkbox("Big endian",&pendingRawSampleBigEndian); ImGui::EndDisabled(); + ImGui::BeginDisabled(pendingRawSampleDepth==DIV_SAMPLE_DEPTH_16BIT); + ImGui::Checkbox("Swap nibbles",&pendingRawSampleSwapNibbles); + ImGui::EndDisabled(); + if (ImGui::Button("OK")) { - DivSample* s=e->sampleFromFileRaw(pendingRawSample.c_str(),(DivSampleDepth)pendingRawSampleDepth,pendingRawSampleChannels,pendingRawSampleBigEndian,pendingRawSampleUnsigned); + DivSample* s=e->sampleFromFileRaw(pendingRawSample.c_str(),(DivSampleDepth)pendingRawSampleDepth,pendingRawSampleChannels,pendingRawSampleBigEndian,pendingRawSampleUnsigned,pendingRawSampleSwapNibbles); if (s==NULL) { showError(e->getLastError()); } else { @@ -5053,6 +5079,7 @@ bool FurnaceGUI::loop() { } logD("saving backup..."); SafeWriter* w=e->saveFur(true); + logV("writing file..."); if (w!=NULL) { FILE* outFile=ps_fopen(backupPath.c_str(),"wb"); @@ -5067,6 +5094,7 @@ bool FurnaceGUI::loop() { w->finish(); } } + logD("backup saved."); backupTimer=30.0; return true; }); @@ -5684,6 +5712,7 @@ FurnaceGUI::FurnaceGUI(): pendingRawSampleChannels(1), pendingRawSampleUnsigned(false), pendingRawSampleBigEndian(false), + pendingRawSampleSwapNibbles(false), globalWinFlags(0), curFileDialog(GUI_FILE_OPEN), warnAction(GUI_WARN_OPEN), diff --git a/src/gui/gui.h b/src/gui/gui.h index aa0ff29a..cd2c3247 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -948,6 +948,7 @@ struct FurnaceGUISysDef { const char* name; const char* extra; String definition; + std::vector orig; FurnaceGUISysDef(const char* n, std::initializer_list def, const char* e=NULL); }; @@ -1116,7 +1117,7 @@ class FurnaceGUI { String pendingRawSample; int pendingRawSampleDepth, pendingRawSampleChannels; - bool pendingRawSampleUnsigned, pendingRawSampleBigEndian; + bool pendingRawSampleUnsigned, pendingRawSampleBigEndian, pendingRawSampleSwapNibbles; ImGuiWindowFlags globalWinFlags; @@ -1177,6 +1178,7 @@ class FurnaceGUI { int nesCore; int fdsCore; int c64Core; + int pokeyCore; int pcSpeakerOutMethod; String yrw801Path; String tg100Path; @@ -1309,6 +1311,7 @@ class FurnaceGUI { nesCore(0), fdsCore(0), c64Core(1), + pokeyCore(1), pcSpeakerOutMethod(0), yrw801Path(""), tg100Path(""), diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index ade7fa23..fc6f46dc 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -913,8 +913,9 @@ const FurnaceGUIColorDef guiColors[GUI_COLOR_MAX]={ const int availableSystems[]={ DIV_SYSTEM_YM2612, DIV_SYSTEM_YM2612_EXT, - DIV_SYSTEM_YM2612_FRAC, - DIV_SYSTEM_YM2612_FRAC_EXT, + DIV_SYSTEM_YM2612_CSM, + DIV_SYSTEM_YM2612_DUALPCM, + DIV_SYSTEM_YM2612_DUALPCM_EXT, DIV_SYSTEM_SMS, DIV_SYSTEM_GB, DIV_SYSTEM_PCE, @@ -939,10 +940,10 @@ const int availableSystems[]={ DIV_SYSTEM_YMU759, DIV_SYSTEM_DUMMY, DIV_SYSTEM_SOUND_UNIT, - DIV_SYSTEM_OPN, - DIV_SYSTEM_OPN_EXT, - DIV_SYSTEM_PC98, - DIV_SYSTEM_PC98_EXT, + DIV_SYSTEM_YM2203, + DIV_SYSTEM_YM2203_EXT, + DIV_SYSTEM_YM2608, + DIV_SYSTEM_YM2608_EXT, DIV_SYSTEM_OPLL, DIV_SYSTEM_OPLL_DRUMS, DIV_SYSTEM_VRC7, @@ -958,6 +959,7 @@ const int availableSystems[]={ DIV_SYSTEM_TIA, DIV_SYSTEM_SAA1099, DIV_SYSTEM_AY8930, + DIV_SYSTEM_POKEY, DIV_SYSTEM_LYNX, DIV_SYSTEM_QSOUND, DIV_SYSTEM_X1_010, @@ -994,8 +996,9 @@ const int availableSystems[]={ const int chipsFM[]={ DIV_SYSTEM_YM2612, DIV_SYSTEM_YM2612_EXT, - DIV_SYSTEM_YM2612_FRAC, - DIV_SYSTEM_YM2612_FRAC_EXT, + DIV_SYSTEM_YM2612_CSM, + DIV_SYSTEM_YM2612_DUALPCM, + DIV_SYSTEM_YM2612_DUALPCM_EXT, DIV_SYSTEM_YM2151, DIV_SYSTEM_YM2610, DIV_SYSTEM_YM2610_EXT, @@ -1004,10 +1007,10 @@ const int chipsFM[]={ DIV_SYSTEM_YM2610B, DIV_SYSTEM_YM2610B_EXT, DIV_SYSTEM_YMU759, - DIV_SYSTEM_OPN, - DIV_SYSTEM_OPN_EXT, - DIV_SYSTEM_PC98, - DIV_SYSTEM_PC98_EXT, + DIV_SYSTEM_YM2203, + DIV_SYSTEM_YM2203_EXT, + DIV_SYSTEM_YM2608, + DIV_SYSTEM_YM2608_EXT, DIV_SYSTEM_OPLL, DIV_SYSTEM_OPLL_DRUMS, DIV_SYSTEM_VRC7, @@ -1064,6 +1067,7 @@ const int chipsSpecial[]={ DIV_SYSTEM_SOUND_UNIT, DIV_SYSTEM_TIA, DIV_SYSTEM_AY8930, + DIV_SYSTEM_POKEY, DIV_SYSTEM_LYNX, DIV_SYSTEM_VERA, DIV_SYSTEM_PET, diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 1865adaa..f1d8bc77 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -255,6 +255,10 @@ const char* c64SpecialBits[3]={ "sync", "ring", NULL }; +const char* pokeyCtlBits[9]={ + "15KHz", "filter 2+4", "filter 1+3", "16-bit 3+4", "16-bit 1+2", "high3", "high1", "poly9", NULL +}; + const char* mikeyFeedbackBits[11] = { "0", "1", "2", "3", "4", "5", "7", "10", "11", "int", NULL }; @@ -5003,6 +5007,10 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930 || ins->type==DIV_INS_FM || ins->type==DIV_INS_OPM) { dutyLabel="Noise Freq"; } + if (ins->type==DIV_INS_POKEY) { + dutyLabel="AUDCTL"; + dutyMax=8; + } if (ins->type==DIV_INS_MIKEY) { dutyLabel="Duty/Int"; dutyMax=ins->amiga.useSample?0:10; @@ -5131,7 +5139,7 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_K007232) waveMax=0; if (ins->type==DIV_INS_GA20) waveMax=0; if (ins->type==DIV_INS_POKEMINI) waveMax=0; - if (ins->type==DIV_INS_SU) waveMax=7; + if (ins->type==DIV_INS_SU || ins->type==DIV_INS_POKEY) waveMax=7; if (ins->type==DIV_INS_PET) { waveMax=8; waveBitMode=true; @@ -5265,6 +5273,8 @@ void FurnaceGUI::drawInsEdit() { if (dutyMax>0) { if (ins->type==DIV_INS_MIKEY) { macroList.push_back(FurnaceGUIMacroDesc(dutyLabel,&ins->std.dutyMacro,0,dutyMax,160,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,mikeyFeedbackBits)); + } else if (ins->type==DIV_INS_POKEY) { + macroList.push_back(FurnaceGUIMacroDesc(dutyLabel,&ins->std.dutyMacro,0,dutyMax,160,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,pokeyCtlBits)); } else if (ins->type==DIV_INS_MSM5232) { macroList.push_back(FurnaceGUIMacroDesc(dutyLabel,&ins->std.dutyMacro,0,dutyMax,160,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,msm5232ControlBits)); } else if (ins->type==DIV_INS_ES5506) { diff --git a/src/gui/presets.cpp b/src/gui/presets.cpp index a303344b..3e74c00e 100644 --- a/src/gui/presets.cpp +++ b/src/gui/presets.cpp @@ -55,15 +55,21 @@ void FurnaceGUI::initSystemPresets() { CH(DIV_SYSTEM_SMS, 32, 0, "") } ); + ENTRY( + "Sega Genesis (CSM)", { + CH(DIV_SYSTEM_YM2612_CSM, 64, 0, ""), + CH(DIV_SYSTEM_SMS, 32, 0, "") + } + ); ENTRY( "Sega Genesis (DualPCM)", { - CH(DIV_SYSTEM_YM2612_FRAC, 64, 0, ""), + CH(DIV_SYSTEM_YM2612_DUALPCM, 64, 0, ""), CH(DIV_SYSTEM_SMS, 32, 0, "") } ); ENTRY( "Sega Genesis (DualPCM, extended channel 3)", { - CH(DIV_SYSTEM_YM2612_FRAC_EXT, 64, 0, ""), + CH(DIV_SYSTEM_YM2612_DUALPCM_EXT, 64, 0, ""), CH(DIV_SYSTEM_SMS, 32, 0, "") } ); @@ -87,6 +93,16 @@ void FurnaceGUI::initSystemPresets() { ) } ); + ENTRY( + "Sega Genesis (CSM with Sega CD)", { + CH(DIV_SYSTEM_YM2612_CSM, 64, 0, ""), + CH(DIV_SYSTEM_SMS, 32, 0, ""), + CH(DIV_SYSTEM_RF5C68, 64, 0, + "clockSel=2\n" + "chipType=1\n" + ) + } + ); ENTRY( "Sega Master System", { CH(DIV_SYSTEM_SMS, 64, 0, "") @@ -114,6 +130,19 @@ void FurnaceGUI::initSystemPresets() { CH(DIV_SYSTEM_GB, 64, 0, "") } ); + ENTRY( + "Neo Geo Pocket", { + CH(DIV_SYSTEM_T6W28, 64, 0, ""), + CH(DIV_SYSTEM_PCM_DAC, 64, -127, + "rate=11025\n" + "outDepth=5\n" + ), + CH(DIV_SYSTEM_PCM_DAC, 64, 127, + "rate=11025\n" + "outDepth=5\n" + ) // don't know what the actual sample rate is + } + ); ENTRY( "NEC PC Engine/TurboGrafx-16", { CH(DIV_SYSTEM_PCE, 64, 0, "") @@ -190,6 +219,12 @@ void FurnaceGUI::initSystemPresets() { CH(DIV_SYSTEM_TIA, 64, 0, "") } ); + ENTRY( + "Atari 7800 + Ballblazer/Commando", { + CH(DIV_SYSTEM_TIA, 64, 0, ""), + CH(DIV_SYSTEM_POKEY, 64, 0, "") + } + ); ENTRY( "Atari Lynx", { CH(DIV_SYSTEM_LYNX, 64, 0, "") @@ -311,7 +346,7 @@ void FurnaceGUI::initSystemPresets() { ); ENTRY( "MSX", { - CH(DIV_SYSTEM_AY8910, 64, 0, "chipType=1") + CH(DIV_SYSTEM_AY8910, 64, 0, "clockSel=0\nchipType=1") } ); ENTRY( @@ -411,77 +446,286 @@ void FurnaceGUI::initSystemPresets() { ) // variable rate, Mono DAC } ); + ENTRY( + "NEC PC-88 (with PC-8801-10)", { + CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1"), + CH(DIV_SYSTEM_AY8910, 64, 0, "clockSel=15"), // external + CH(DIV_SYSTEM_AY8910, 64, 0, "clockSel=15") // "" + } + ); + ENTRY( + "NEC PC-88 (with PC-8801-11)", { + CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1"), + CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=4") // external + } + ); + ENTRY( + "NEC PC-88 (with PC-8801-11; extended channel 3)", { + CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1"), + CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=4") // external + } + ); + ENTRY( + "NEC PC-88 (with PC-8801-23)", { + CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1"), + CH(DIV_SYSTEM_YM2608, 64, 0, "clockSel=1") // external + } + ); + ENTRY( + "NEC PC-88 (with PC-8801-23; extended channel 3)", { + CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1"), + CH(DIV_SYSTEM_YM2608_EXT, 64, 0, "clockSel=1") // external + } + ); + ENTRY( + "NEC PC-88 (with HMB-20 HIBIKI-8800)", { + CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1"), + CH(DIV_SYSTEM_YM2151, 64, 0, "clockSel=2") // external; 4.0000MHz + } + ); + ENTRY( + "NEC PC-8801mk2SR (with PC-8801-10)", { + CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1"), + CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=4"), // internal + CH(DIV_SYSTEM_AY8910, 64, 0, "clockSel=15"), // external + CH(DIV_SYSTEM_AY8910, 64, 0, "clockSel=15") // "" + } + ); + ENTRY( + "NEC PC-8801mk2SR (with PC-8801-10; extended channel 3)", { + CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1"), + CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=4"), // internal + CH(DIV_SYSTEM_AY8910, 64, 0, "clockSel=15"), // external + CH(DIV_SYSTEM_AY8910, 64, 0, "clockSel=15") // "" + } + ); + ENTRY( + "NEC PC-8801mk2SR (with PC-8801-11)", { + CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1"), + CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=4"), // internal + CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=4") // external + } + ); + ENTRY( + "NEC PC-8801mk2SR (with PC-8801-11; extended channel 3 on internal OPN)", { + CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1"), + CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=4"), // internal + CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=4") // external + } + ); + ENTRY( + "NEC PC-8801mk2SR (with PC-8801-11; extended channel 3 on external OPN)", { + CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1"), + CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=4"), // internal + CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=4") // external + } + ); + ENTRY( + "NEC PC-8801mk2SR (with PC-8801-11; extended channel 3 on both OPNs)", { + CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1"), + CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=4"), // internal + CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=4") // external + } + ); + ENTRY( + "NEC PC-8801mk2SR (with PC-8801-23)", { + CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1"), + CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=4"), // internal + CH(DIV_SYSTEM_YM2608, 64, 0, "clockSel=1") // external + } + ); + ENTRY( + "NEC PC-8801mk2SR (with PC-8801-23; extended channel 3 on internal OPN)", { + CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1"), + CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=4"), // internal + CH(DIV_SYSTEM_YM2608, 64, 0, "clockSel=1") // external + } + ); + ENTRY( + "NEC PC-8801mk2SR (with PC-8801-23; extended channel 3 on external OPN)", { + CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1"), + CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=4"), // internal + CH(DIV_SYSTEM_YM2608_EXT, 64, 0, "clockSel=1") // external + } + ); + ENTRY( + "NEC PC-8801mk2SR (with PC-8801-23; extended channel 3 on both OPNs)", { + CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1"), + CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=4"), // internal + CH(DIV_SYSTEM_YM2608_EXT, 64, 0, "clockSel=1") // external + } + ); + ENTRY( + "NEC PC-8801mk2SR (with HMB-20 HIBIKI-8800)", { + CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1"), + CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=4"), // internal + CH(DIV_SYSTEM_YM2151, 64, 0, "clockSel=2") // external; 4.0000MHz + } + ); + ENTRY( + "NEC PC-8801mk2SR (with HMB-20 HIBIKI-8800; extended channel 3)", { + CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1"), + CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=4"), // internal + CH(DIV_SYSTEM_YM2151, 64, 0, "clockSel=2") // external; 4.0000MHz + } + ); + ENTRY( + "NEC PC-8801FA (with PC-8801-10)", { + CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1"), + CH(DIV_SYSTEM_YM2608, 64, 0, "clockSel=4"), // internal + CH(DIV_SYSTEM_AY8910, 64, 0, "clockSel=15"), // external + CH(DIV_SYSTEM_AY8910, 64, 0, "clockSel=15") // "" + } + ); + ENTRY( + "NEC PC-8801FA (with PC-8801-10; extended channel 3)", { + CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1"), + CH(DIV_SYSTEM_YM2608_EXT, 64, 0, "clockSel=4"), // internal + CH(DIV_SYSTEM_AY8910, 64, 0, "clockSel=15"), // external + CH(DIV_SYSTEM_AY8910, 64, 0, "clockSel=15") // "" + } + ); + ENTRY( + "NEC PC-8801FA (with PC-8801-11)", { + CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1"), + CH(DIV_SYSTEM_YM2608, 64, 0, "clockSel=1"), // internal + CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=4") // external + } + ); + ENTRY( + "NEC PC-8801FA (with PC-8801-11; extended channel 3 on internal OPN)", { + CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1"), + CH(DIV_SYSTEM_YM2608_EXT, 64, 0, "clockSel=1"), // internal + CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=4") // external + } + ); + ENTRY( + "NEC PC-8801FA (with PC-8801-11; extended channel 3 on external OPN)", { + CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1"), + CH(DIV_SYSTEM_YM2608, 64, 0, "clockSel=1"), // internal + CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=4") // external + } + ); + ENTRY( + "NEC PC-8801FA (with PC-8801-11; extended channel 3 on both OPNs)", { + CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1"), + CH(DIV_SYSTEM_YM2608_EXT, 64, 0, "clockSel=1"), // internal + CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=4") // external + } + ); + ENTRY( + "NEC PC-8801FA (with PC-8801-23)", { + CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1"), + CH(DIV_SYSTEM_YM2608, 64, 0, "clockSel=1"), // internal + CH(DIV_SYSTEM_YM2608, 64, 0, "clockSel=1") // external + } + ); + ENTRY( + "NEC PC-8801FA (with PC-8801-23; extended channel 3 on internal OPN)", { + CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1"), + CH(DIV_SYSTEM_YM2608_EXT, 64, 0, "clockSel=1"), // internal + CH(DIV_SYSTEM_YM2608, 64, 0, "clockSel=1") // external + } + ); + ENTRY( + "NEC PC-8801FA (with PC-8801-23; extended channel 3 on external OPN)", { + CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1"), + CH(DIV_SYSTEM_YM2608, 64, 0, "clockSel=1"), // internal + CH(DIV_SYSTEM_YM2608_EXT, 64, 0, "clockSel=1") // external + } + ); + ENTRY( + "NEC PC-8801FA (with PC-8801-23; extended channel 3 on both OPNs)", { + CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1"), + CH(DIV_SYSTEM_YM2608_EXT, 64, 0, "clockSel=1"), // internal + CH(DIV_SYSTEM_YM2608_EXT, 64, 0, "clockSel=1") // external + } + ); + ENTRY( + "NEC PC-8801FA (with HMB-20 HIBIKI-8800)", { + CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1"), + CH(DIV_SYSTEM_YM2608, 64, 0, "clockSel=1"), // internal + CH(DIV_SYSTEM_YM2151, 64, 0, "clockSel=2") // external; 4.0000MHz + } + ); + ENTRY( + "NEC PC-8801FA (with HMB-20 HIBIKI-8800; extended channel 3)", { + CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1"), + CH(DIV_SYSTEM_YM2608_EXT, 64, 0, "clockSel=1"), // internal + CH(DIV_SYSTEM_YM2151, 64, 0, "clockSel=2") // external; 4.0000MHz + } + ); ENTRY( "NEC PC-98 (with PC-9801-26/K)", { - CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=4"), // 3.9936MHz but some compatible card has 4MHz + CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=4"), // 3.9936MHz but some compatible card has 4MHz CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1") } ); ENTRY( "NEC PC-98 (with PC-9801-26/K; extended channel 3)", { - CH(DIV_SYSTEM_OPN_EXT, 64, 0, "clockSel=4"), // 3.9936MHz but some compatible card has 4MHz + CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=4"), // 3.9936MHz but some compatible card has 4MHz CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1") } ); ENTRY( "NEC PC-98 (with Sound Orchestra)", { - CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=4"), + CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=4"), CH(DIV_SYSTEM_OPL2, 64, 0, "clockSel=4"), CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1") } ); ENTRY( "NEC PC-98 (with Sound Orchestra; extended channel 3)", { - CH(DIV_SYSTEM_OPN_EXT, 64, 0, "clockSel=4"), + CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=4"), CH(DIV_SYSTEM_OPL2, 64, 0, "clockSel=4"), CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1") } ); ENTRY( "NEC PC-98 (with Sound Orchestra in drums mode)", { - CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=4"), + CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=4"), CH(DIV_SYSTEM_OPL2_DRUMS, 64, 0, "clockSel=4"), CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1") } ); ENTRY( "NEC PC-98 (with Sound Orchestra in drums mode; extended channel 3)", { - CH(DIV_SYSTEM_OPN_EXT, 64, 0, "clockSel=4"), + CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=4"), CH(DIV_SYSTEM_OPL2_DRUMS, 64, 0, "clockSel=4"), CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1") } ); ENTRY( "NEC PC-98 (with Sound Orchestra V)", { - CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=4"), + CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=4"), CH(DIV_SYSTEM_Y8950, 64, 0, "clockSel=4"), CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1") } ); ENTRY( "NEC PC-98 (with Sound Orchestra V; extended channel 3)", { - CH(DIV_SYSTEM_OPN_EXT, 64, 0, "clockSel=4"), + CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=4"), CH(DIV_SYSTEM_Y8950, 64, 0, "clockSel=4"), CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1") } ); ENTRY( "NEC PC-98 (with Sound Orchestra V in drums mode)", { - CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=4"), + CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=4"), CH(DIV_SYSTEM_Y8950_DRUMS, 64, 0, "clockSel=4"), CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1") } ); ENTRY( "NEC PC-98 (with Sound Orchestra V in drums mode; extended channel 3)", { - CH(DIV_SYSTEM_OPN_EXT, 64, 0, "clockSel=4"), + CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=4"), CH(DIV_SYSTEM_Y8950_DRUMS, 64, 0, "clockSel=4"), CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1") } ); ENTRY( "NEC PC-98 (with PC-9801-86)", { // -73 also has OPNA - CH(DIV_SYSTEM_PC98, 64, 0, "clockSel=1"), + CH(DIV_SYSTEM_YM2608, 64, 0, "clockSel=1"), CH(DIV_SYSTEM_PCM_DAC, 64, 0, // 2x 16-bit Burr Brown DAC "rate=44100\n" "outDepth=15\n" @@ -495,7 +739,7 @@ void FurnaceGUI::initSystemPresets() { ); ENTRY( "NEC PC-98 (with PC-9801-86; extended channel 3)", { // -73 also has OPNA - CH(DIV_SYSTEM_PC98_EXT, 64, 0, "clockSel=1"), + CH(DIV_SYSTEM_YM2608_EXT, 64, 0, "clockSel=1"), CH(DIV_SYSTEM_PCM_DAC, 64, 0, "rate=44100\n" "outDepth=15\n" @@ -509,19 +753,19 @@ void FurnaceGUI::initSystemPresets() { ); ENTRY( "NEC PC-98 (with PC-9801-73)", { - CH(DIV_SYSTEM_PC98, 64, 0, "clockSel=1"), + CH(DIV_SYSTEM_YM2608, 64, 0, "clockSel=1"), CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1") } ); ENTRY( "NEC PC-98 (with PC-9801-73; extended channel 3)", { - CH(DIV_SYSTEM_PC98_EXT, 64, 0, "clockSel=1"), + CH(DIV_SYSTEM_YM2608_EXT, 64, 0, "clockSel=1"), CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1") } ); ENTRY( "NEC PC-98 (with Sound Blaster 16 for PC-9800 w/PC-9801-26/K compatible)", { - CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=2"), // 4MHz + CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=2"), // 4MHz CH(DIV_SYSTEM_PCM_DAC, 64, 0, "rate=44100\n" "outDepth=15\n" @@ -533,7 +777,7 @@ void FurnaceGUI::initSystemPresets() { ); ENTRY( "NEC PC-98 (with Sound Blaster 16 for PC-9800 w/PC-9801-26/K compatible; extended channel 3)", { - CH(DIV_SYSTEM_OPN_EXT, 64, 0, "clockSel=2"), // 4MHz + CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=2"), // 4MHz CH(DIV_SYSTEM_PCM_DAC, 64, 0, "rate=44100\n" "outDepth=15\n" @@ -545,7 +789,7 @@ void FurnaceGUI::initSystemPresets() { ); ENTRY( "NEC PC-98 (with Sound Blaster 16 for PC-9800 w/PC-9801-26/K compatible in drums mode)", { - CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=2"), // 4MHz + CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=2"), // 4MHz CH(DIV_SYSTEM_PCM_DAC, 64, 0, "rate=44100\n" "outDepth=15\n" @@ -557,7 +801,7 @@ void FurnaceGUI::initSystemPresets() { ); ENTRY( "NEC PC-98 (with Sound Blaster 16 for PC-9800 w/PC-9801-26/K compatible in drums mode; extended channel 3)", { - CH(DIV_SYSTEM_OPN_EXT, 64, 0, "clockSel=2"), // 4MHz + CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=2"), // 4MHz CH(DIV_SYSTEM_PCM_DAC, 64, 0, "rate=44100\n" "outDepth=15\n" @@ -580,29 +824,29 @@ void FurnaceGUI::initSystemPresets() { ENTRY( "ZX Spectrum (128K) with TurboSound FM", { CH(DIV_SYSTEM_AY8910, 64, 0, "clockSel=1"), - CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=1"), - CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=1") + CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=1"), + CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=1") } ); ENTRY( "ZX Spectrum (128K) with TurboSound FM (extended channel 3 on first OPN)", { CH(DIV_SYSTEM_AY8910, 64, 0, "clockSel=1"), - CH(DIV_SYSTEM_OPN_EXT, 64, 0, "clockSel=1"), - CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=1") + CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=1"), + CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=1") } ); ENTRY( "ZX Spectrum (128K) with TurboSound FM (extended channel 3 on second OPN)", { CH(DIV_SYSTEM_AY8910, 64, 0, "clockSel=1"), - CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=1"), - CH(DIV_SYSTEM_OPN_EXT, 64, 0, "clockSel=1") + CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=1"), + CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=1") } ); ENTRY( "ZX Spectrum (128K) with TurboSound FM (extended channel 3 on both OPNs)", { CH(DIV_SYSTEM_AY8910, 64, 0, "clockSel=1"), - CH(DIV_SYSTEM_OPN_EXT, 64, 0, "clockSel=1"), - CH(DIV_SYSTEM_OPN_EXT, 64, 0, "clockSel=1") + CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=1"), + CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=1") } ); ENTRY( @@ -617,6 +861,19 @@ void FurnaceGUI::initSystemPresets() { CH(DIV_SYSTEM_AY8910, 64, 0, "clockSel=5") } ); + ENTRY( + "Atari 800", { + CH(DIV_SYSTEM_POKEY, 64, 0, "clockSel=1") + }, + "tickRate=50" + ); + ENTRY( + "Atari 800 (stereo)", { + CH(DIV_SYSTEM_POKEY, 64, -127, "clockSel=1"), + CH(DIV_SYSTEM_POKEY, 64, 127, "clockSel=1"), + }, + "tickRate=50" + ); ENTRY( "Atari ST", { CH(DIV_SYSTEM_AY8910, 64, 0, @@ -835,6 +1092,12 @@ void FurnaceGUI::initSystemPresets() { CH(DIV_SYSTEM_RF5C68, 64, 0, "") } ); + ENTRY( + "FM Towns (CSM)", { + CH(DIV_SYSTEM_YM2612_CSM, 64, 0, "clockSel=2"), // YM3438 + CH(DIV_SYSTEM_RF5C68, 64, 0, "") + } + ); ENTRY( "Commander X16", { CH(DIV_SYSTEM_VERA, 64, 0, ""), @@ -859,22 +1122,22 @@ void FurnaceGUI::initSystemPresets() { ); ENTRY( "Yamaha YM2203 (OPN)", { - CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=3") + CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=3") } ); ENTRY( "Yamaha YM2203 (extended channel 3)", { - CH(DIV_SYSTEM_OPN_EXT, 64, 0, "clockSel=3") + CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=3") } ); ENTRY( "Yamaha YM2608 (OPNA)", { - CH(DIV_SYSTEM_PC98, 64, 0, "") + CH(DIV_SYSTEM_YM2608, 64, 0, "") } ); ENTRY( "Yamaha YM2608 (extended channel 3)", { - CH(DIV_SYSTEM_PC98_EXT, 64, 0, "") + CH(DIV_SYSTEM_YM2608_EXT, 64, 0, "") } ); ENTRY( @@ -907,14 +1170,19 @@ void FurnaceGUI::initSystemPresets() { CH(DIV_SYSTEM_YM2612_EXT, 64, 0, "ladderEffect=true") } ); + ENTRY( + "Yamaha YM2612 (OPN2) CSM", { + CH(DIV_SYSTEM_YM2612_CSM, 64, 0, "ladderEffect=true") + } + ); ENTRY( "Yamaha YM2612 (OPN2) with DualPCM", { - CH(DIV_SYSTEM_YM2612_FRAC, 64, 0, "ladderEffect=true") + CH(DIV_SYSTEM_YM2612_DUALPCM, 64, 0, "ladderEffect=true") } ); ENTRY( "Yamaha YM2612 (extended channel 3) with DualPCM", { - CH(DIV_SYSTEM_YM2612_FRAC_EXT, 64, 0, "ladderEffect=true") + CH(DIV_SYSTEM_YM2612_DUALPCM_EXT, 64, 0, "ladderEffect=true") } ); ENTRY( @@ -942,14 +1210,19 @@ void FurnaceGUI::initSystemPresets() { CH(DIV_SYSTEM_YM2612_EXT, 64, 0, "") } ); + ENTRY( + "Yamaha YM3438 (OPN2C) CSM", { + CH(DIV_SYSTEM_YM2612_CSM, 64, 0, "") + } + ); ENTRY( "Yamaha YM3438 (OPN2C) with DualPCM", { - CH(DIV_SYSTEM_YM2612_FRAC, 64, 0, "") + CH(DIV_SYSTEM_YM2612_DUALPCM, 64, 0, "") } ); ENTRY( "Yamaha YM3438 (extended channel 3) with DualPCM", { - CH(DIV_SYSTEM_YM2612_FRAC_EXT, 64, 0, "") + CH(DIV_SYSTEM_YM2612_DUALPCM_EXT, 64, 0, "") } ); ENTRY( @@ -1475,13 +1748,13 @@ void FurnaceGUI::initSystemPresets() { ); ENTRY( "Sega Hang-On", { - CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=2"), // 4MHz + CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=2"), // 4MHz CH(DIV_SYSTEM_SEGAPCM, 64, 0, "") // discrete logics, 62.5KHz output rate } ); ENTRY( "Sega Hang-On (extended channel 3)", { - CH(DIV_SYSTEM_OPN_EXT, 64, 0, "clockSel=2"), // 4MHz + CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=2"), // 4MHz CH(DIV_SYSTEM_SEGAPCM, 64, 0, "") // discrete logics, 62.5KHz output rate } ); @@ -1571,26 +1844,26 @@ void FurnaceGUI::initSystemPresets() { ); ENTRY( "Capcom Arcade", { // 1943, Side arms, etc - CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=5"), // 4 or 1.5MHz; various per games - CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=5") + CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=5"), // 4 or 1.5MHz; various per games + CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=5") } ); ENTRY( "Capcom Arcade (extended channel 3 on first OPN)", { - CH(DIV_SYSTEM_OPN_EXT, 64, 0, "clockSel=5"), - CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=5") + CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=5"), + CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=5") } ); ENTRY( "Capcom Arcade (extended channel 3 on second OPN)", { - CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=5"), - CH(DIV_SYSTEM_OPN_EXT, 64, 0, "clockSel=5") + CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=5"), + CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=5") } ); ENTRY( "Capcom Arcade (extended channel 3 on both OPNs)", { - CH(DIV_SYSTEM_OPN_EXT, 64, 0, "clockSel=5"), - CH(DIV_SYSTEM_OPN_EXT, 64, 0, "clockSel=5") + CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=5"), + CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=5") } ); ENTRY( @@ -1625,7 +1898,7 @@ void FurnaceGUI::initSystemPresets() { ); ENTRY( "NMK 16-bit Arcade", { - CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=5"), // 1.5MHz; optional + CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=5"), // 1.5MHz; optional CH(DIV_SYSTEM_MSM6295, 64, 0, "clockSel=2\n" "rateSel=true\n" @@ -1638,7 +1911,7 @@ void FurnaceGUI::initSystemPresets() { ); ENTRY( "NMK 16-bit Arcade (extended channel 3)", { - CH(DIV_SYSTEM_OPN_EXT, 64, 0, "clockSel=5"), // 1.5MHz; optional + CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=5"), // 1.5MHz; optional CH(DIV_SYSTEM_MSM6295, 64, 0, "clockSel=2\n" "rateSel=true\n" @@ -1651,21 +1924,21 @@ void FurnaceGUI::initSystemPresets() { ); ENTRY( "Kaneko DJ Boy", { - CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=3"), // 3MHz + CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=3"), // 3MHz CH(DIV_SYSTEM_MSM6295, 64, -127, "clockSel=12"), // 1.5MHz, Left output CH(DIV_SYSTEM_MSM6295, 64, 127, "clockSel=12"), // 1.5MHz, Right output } ); ENTRY( "Kaneko DJ Boy (extended channel 3)", { - CH(DIV_SYSTEM_OPN_EXT, 64, 0, "clockSel=3"), // 3MHz + CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=3"), // 3MHz CH(DIV_SYSTEM_MSM6295, 64, -127, "clockSel=12"), // 1.5MHz, Left output CH(DIV_SYSTEM_MSM6295, 64, 127, "clockSel=12") // 1.5MHz, Right output } ); ENTRY( "Kaneko Air Buster", { - CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=3"), // 3MHz + CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=3"), // 3MHz CH(DIV_SYSTEM_MSM6295, 64, 0, "clockSel=13\n" "rateSel=true\n" @@ -1674,7 +1947,7 @@ void FurnaceGUI::initSystemPresets() { ); ENTRY( "Kaneko Air Buster (extended channel 3)", { - CH(DIV_SYSTEM_OPN_EXT, 64, 0, "clockSel=3"), // 3MHz + CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=3"), // 3MHz CH(DIV_SYSTEM_MSM6295, 64, 0, "clockSel=13\n" "rateSel=true\n" @@ -1706,29 +1979,29 @@ void FurnaceGUI::initSystemPresets() { ); ENTRY( "Tecmo Ninja Gaiden", { // Ninja Gaiden, Raiga, etc - CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=2"), // 4MHz - CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=2"), // 4MHz + CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=2"), // 4MHz + CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=2"), // 4MHz CH(DIV_SYSTEM_MSM6295, 64, 0, "") // 1MHz } ); ENTRY( "Tecmo Ninja Gaiden (extended channel 3 on first OPN)", { // Ninja Gaiden, Raiga, etc - CH(DIV_SYSTEM_OPN_EXT, 64, 0, "clockSel=2"), // 4MHz - CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=2"), // 4MHz + CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=2"), // 4MHz + CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=2"), // 4MHz CH(DIV_SYSTEM_MSM6295, 64, 0, "") // 1MHz } ); ENTRY( "Tecmo Ninja Gaiden (extended channel 3 on second OPN)", { // Ninja Gaiden, Raiga, etc - CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=2"), // 4MHz - CH(DIV_SYSTEM_OPN_EXT, 64, 0, "clockSel=2"), // 4MHz + CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=2"), // 4MHz + CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=2"), // 4MHz CH(DIV_SYSTEM_MSM6295, 64, 0, "") // 1MHz } ); ENTRY( "Tecmo Ninja Gaiden (extended channel 3 on both OPNs)", { // Ninja Gaiden, Raiga, etc - CH(DIV_SYSTEM_OPN_EXT, 64, 0, "clockSel=2"), // 4MHz - CH(DIV_SYSTEM_OPN_EXT, 64, 0, "clockSel=2"), // 4MHz + CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=2"), // 4MHz + CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=2"), // 4MHz CH(DIV_SYSTEM_MSM6295, 64, 0, "") // 1MHz } ); @@ -1803,68 +2076,95 @@ void FurnaceGUI::initSystemPresets() { CH(DIV_SYSTEM_MSM6295, 64, 127, "clockSel=14") // 1.193MHz (3.579545MHz / 3), Right output } ); + ENTRY( + "Atari Marble Madness", { + CH(DIV_SYSTEM_YM2151, 64, 0, ""), + CH(DIV_SYSTEM_POKEY, 64, 0, "") + } + ); + ENTRY( + "Atari Championship Sprint", { + CH(DIV_SYSTEM_YM2151, 64, 0, ""), + CH(DIV_SYSTEM_POKEY, 64, 0, ""), + CH(DIV_SYSTEM_POKEY, 64, 0, "") + } + ); + ENTRY( + "Atari Tetris", { + CH(DIV_SYSTEM_POKEY, 64, 0, ""), + CH(DIV_SYSTEM_POKEY, 64, 0, "") + } + ); + ENTRY( + "Atari I, Robot", { + CH(DIV_SYSTEM_POKEY, 64, 0, "customClock=1512000"), + CH(DIV_SYSTEM_POKEY, 64, 0, "customClock=1512000"), + CH(DIV_SYSTEM_POKEY, 64, 0, "customClock=1512000"), + CH(DIV_SYSTEM_POKEY, 64, 0, "customClock=1512000") + } + ); ENTRY( "Data East Karnov", { - CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=5"), // 1.5MHz + CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=5"), // 1.5MHz CH(DIV_SYSTEM_OPL, 64, 0, "clockSel=3") // 3MHz } ); ENTRY( "Data East Karnov (extended channel 3)", { - CH(DIV_SYSTEM_OPN_EXT, 64, 0, "clockSel=5"), // 1.5MHz + CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=5"), // 1.5MHz CH(DIV_SYSTEM_OPL, 64, 0, "clockSel=3") // 3MHz } ); ENTRY( "Data East Karnov (drums mode)", { - CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=5"), // 1.5MHz + CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=5"), // 1.5MHz CH(DIV_SYSTEM_OPL_DRUMS, 64, 0, "clockSel=3") // 3MHz } ); ENTRY( "Data East Karnov (extended channel 3; drums mode)", { - CH(DIV_SYSTEM_OPN_EXT, 64, 0, "clockSel=5"), // 1.5MHz + CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=5"), // 1.5MHz CH(DIV_SYSTEM_OPL_DRUMS, 64, 0, "clockSel=3") // 3MHz } ); ENTRY( "Data East Arcade", { // Bad dudes, Robocop, etc - CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=5"), // 1.5MHz + CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=5"), // 1.5MHz CH(DIV_SYSTEM_OPL2, 64, 0, "clockSel=3"), // 3MHz CH(DIV_SYSTEM_MSM6295, 64, 0, "") // 1 to 1.056MHz; various per games or optional } ); ENTRY( "Data East Arcade (extended channel 3)", { // Bad dudes, Robocop, etc - CH(DIV_SYSTEM_OPN_EXT, 64, 0, "clockSel=5"), // 1.5MHz + CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=5"), // 1.5MHz CH(DIV_SYSTEM_OPL2, 64, 0, "clockSel=3"), // 3MHz CH(DIV_SYSTEM_MSM6295, 64, 0, "") // 1 to 1.056MHz; various per games or optional } ); ENTRY( "Data East Arcade (drums mode)", { // Bad dudes, Robocop, etc - CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=5"), // 1.5MHz + CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=5"), // 1.5MHz CH(DIV_SYSTEM_OPL2_DRUMS, 64, 0, "clockSel=3"), // 3MHz CH(DIV_SYSTEM_MSM6295, 64, 0, "") // 1 to 1.056MHz; various per games or optional } ); ENTRY( "Data East Arcade (extended channel 3; drums mode)", { // Bad dudes, Robocop, etc - CH(DIV_SYSTEM_OPN_EXT, 64, 0, "clockSel=5"), // 1.5MHz + CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=5"), // 1.5MHz CH(DIV_SYSTEM_OPL2_DRUMS, 64, 0, "clockSel=3"), // 3MHz CH(DIV_SYSTEM_MSM6295, 64, 0, "") // 1 to 1.056MHz; various per games or optional } ); ENTRY( "Data East PCX", { - CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=5"), // 1.5MHz + CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=5"), // 1.5MHz CH(DIV_SYSTEM_PCE, 64, 0, "") // software controlled MSM5205 } ); ENTRY( "Data East PCX (extended channel 3)", { - CH(DIV_SYSTEM_OPN_EXT, 64, 0, "clockSel=5"), // 1.5MHz + CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=5"), // 1.5MHz CH(DIV_SYSTEM_PCE, 64, 0, "") // software controlled MSM5205 } @@ -1872,7 +2172,7 @@ void FurnaceGUI::initSystemPresets() { ENTRY( "Data East Dark Seal", { // Dark Seal, Crude Buster, Vapor Trail, etc CH(DIV_SYSTEM_YM2151, 64, 0, ""), // 3.580MHz (32.22MHz / 9) - CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=2"), // 4.0275MHz (32.22MHz / 8); optional + CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=2"), // 4.0275MHz (32.22MHz / 8); optional CH(DIV_SYSTEM_MSM6295, 64, 0, ""), // 1.007MHz (32.22MHz / 32) CH(DIV_SYSTEM_MSM6295, 64, 0, "clockSel=8") // 2.014MHz (32.22MHz / 16); optional // HuC6280 is for control them, internal sound isn't used @@ -1881,7 +2181,7 @@ void FurnaceGUI::initSystemPresets() { ENTRY( "Data East Dark Seal (extended channel 3)", { // Dark Seal, Crude Buster, Vapor Trail, etc CH(DIV_SYSTEM_YM2151, 64, 0, ""), // 3.580MHz (32.22MHz / 9) - CH(DIV_SYSTEM_OPN_EXT, 64, 0, "clockSel=2"), // 4.0275MHz (32.22MHz / 8); optional + CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=2"), // 4.0275MHz (32.22MHz / 8); optional CH(DIV_SYSTEM_MSM6295, 64, 0, ""), // 1.007MHz (32.22MHz / 32) CH(DIV_SYSTEM_MSM6295, 64, 0, "clockSel=8") // 2.014MHz (32.22MHz / 16); optional // HuC6280 is for control them, internal sound isn't used @@ -1996,7 +2296,7 @@ void FurnaceGUI::initSystemPresets() { ); ENTRY( "Alpha denshi Alpha-68K", { - CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=3"), // 3MHz + CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=3"), // 3MHz CH(DIV_SYSTEM_OPLL, 64, 0, "clockSel=0"), // 3.58MHz CH(DIV_SYSTEM_PCM_DAC, 64, 0, "rate=7614\n" @@ -2006,7 +2306,7 @@ void FurnaceGUI::initSystemPresets() { ); ENTRY( "Alpha denshi Alpha-68K (extended channel 3)", { - CH(DIV_SYSTEM_OPN_EXT, 64, 0, "clockSel=3"), // 3MHz + CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=3"), // 3MHz CH(DIV_SYSTEM_OPLL, 64, 0, "clockSel=0"), // 3.58MHz CH(DIV_SYSTEM_PCM_DAC, 64, 0, "rate=7614\n" @@ -2016,7 +2316,7 @@ void FurnaceGUI::initSystemPresets() { ); ENTRY( "Alpha denshi Alpha-68K (drums mode)", { - CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=3"), // 3MHz + CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=3"), // 3MHz CH(DIV_SYSTEM_OPLL_DRUMS, 64, 0, "clockSel=0"), // 3.58MHz CH(DIV_SYSTEM_PCM_DAC, 64, 0, "rate=7614\n" @@ -2026,7 +2326,7 @@ void FurnaceGUI::initSystemPresets() { ); ENTRY( "Alpha denshi Alpha-68K (extended channel 3; drums mode)", { - CH(DIV_SYSTEM_OPN_EXT, 64, 0, "clockSel=3"), // 3MHz + CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=3"), // 3MHz CH(DIV_SYSTEM_OPLL_DRUMS, 64, 0, "clockSel=0"), // 3.58MHz CH(DIV_SYSTEM_PCM_DAC, 64, 0, "rate=7614\n" @@ -2034,6 +2334,20 @@ void FurnaceGUI::initSystemPresets() { ) // software controlled 8 bit DAC } ); + ENTRY( + "Alpha denshi Equites", { + CH(DIV_SYSTEM_MSM5232, 64, 0, "customClock=6144000"), + CH(DIV_SYSTEM_AY8910, 64, 0, "clockSel=14"), + CH(DIV_SYSTEM_PCM_DAC, 64, 0, + "rate=11025\n" + "outDepth=5\n" + ), + CH(DIV_SYSTEM_PCM_DAC, 64, 0, + "rate=11025\n" + "outDepth=5\n" + ) // don't know what the actual sample rate is + } + ); ENTRY( "Neo Geo MVS", { CH(DIV_SYSTEM_YM2610_FULL, 64, 0, "") @@ -2106,6 +2420,43 @@ void FurnaceGUI::initSystemPresets() { CH(DIV_SYSTEM_YM2610B_EXT, 64, 0, "") } ); + ENTRY( + "Taito Metal Soldier Isaac II", { + CH(DIV_SYSTEM_MSM5232, 64, 0, ""), + CH(DIV_SYSTEM_AY8910, 64, 0, "clockSel=3"), + CH(DIV_SYSTEM_AY8910, 64, 0, "clockSel=3") + } + ); + ENTRY( + "Taito The Fairyland Story", { + CH(DIV_SYSTEM_MSM5232, 64, 0, ""), + CH(DIV_SYSTEM_AY8910, 64, 0, + "clockSel=3\n" + "chipType=1\n" + ), + CH(DIV_SYSTEM_PCM_DAC, 64, 0, + "rate=11025\n" + "outDepth=7\n" + ) // don't know what the actual sample rate is + } + ); + ENTRY( + "Taito Wyvern F-0", { + CH(DIV_SYSTEM_MSM5232, 64, 0, ""), + CH(DIV_SYSTEM_AY8910, 64, 0, + "clockSel=3\n" + "chipType=1\n" + ), + CH(DIV_SYSTEM_AY8910, 64, 0, + "clockSel=3\n" + "chipType=1\n" + ), + CH(DIV_SYSTEM_PCM_DAC, 64, 0, + "rate=11025\n" + "outDepth=7\n" + ) // don't know what the actual sample rate is + } + ); ENTRY( "Seta 1", { CH(DIV_SYSTEM_X1_010, 64, 0, "") @@ -2302,9 +2653,9 @@ void FurnaceGUI::initSystemPresets() { FurnaceGUISysDef::FurnaceGUISysDef(const char* n, std::initializer_list def, const char* e): name(n), extra(e) { - std::vector uncompiled=def; + orig=def; int index=0; - for (FurnaceGUISysDefChip& i: uncompiled) { + for (FurnaceGUISysDefChip& i: orig) { definition+=fmt::sprintf( "id%d=%d\nvol%d=%d\npan%d=%d\nflags%d=%s\n", index, diff --git a/src/gui/sampleEdit.cpp b/src/gui/sampleEdit.cpp index ac03aae6..a8de4dac 100644 --- a/src/gui/sampleEdit.cpp +++ b/src/gui/sampleEdit.cpp @@ -227,11 +227,11 @@ void FurnaceGUI::drawSampleEdit() { if (sampleDepths[i]==NULL) continue; if (ImGui::Selectable(sampleDepths[i])) { sample->prepareUndo(true); - e->lockEngine([sample]() { + e->lockEngine([this,sample,i]() { sample->render(); + sample->depth=(DivSampleDepth)i; + e->renderSamples(); }); - sample->depth=(DivSampleDepth)i; - e->renderSamplesP(); updateSampleTex=true; MARK_MODIFIED; } diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index d40e4dac..41e87d19 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -102,6 +102,11 @@ const char* c64Cores[]={ "reSIDfp" }; +const char* pokeyCores[]={ + "Atari800 (mzpokeysnd)", + "ASAP (C++ port)" +}; + const char* pcspkrOutMethods[]={ "evdev SND_TONE", "KIOCSOUND on /dev/tty1", @@ -1069,6 +1074,10 @@ void FurnaceGUI::drawSettings() { ImGui::SameLine(); ImGui::Combo("##C64Core",&settings.c64Core,c64Cores,2); + ImGui::Text("POKEY core"); + ImGui::SameLine(); + ImGui::Combo("##POKEYCore",&settings.pokeyCore,pokeyCores,2); + ImGui::Separator(); ImGui::Text("PC Speaker strategy"); @@ -2336,6 +2345,7 @@ void FurnaceGUI::syncSettings() { settings.nesCore=e->getConfInt("nesCore",0); settings.fdsCore=e->getConfInt("fdsCore",0); settings.c64Core=e->getConfInt("c64Core",1); + settings.pokeyCore=e->getConfInt("pokeyCore",1); settings.pcSpeakerOutMethod=e->getConfInt("pcSpeakerOutMethod",0); settings.yrw801Path=e->getConfString("yrw801Path",""); settings.tg100Path=e->getConfString("tg100Path",""); @@ -2461,6 +2471,7 @@ void FurnaceGUI::syncSettings() { clampSetting(settings.nesCore,0,1); clampSetting(settings.fdsCore,0,1); clampSetting(settings.c64Core,0,1); + clampSetting(settings.pokeyCore,0,1); clampSetting(settings.pcSpeakerOutMethod,0,4); clampSetting(settings.mainFont,0,6); clampSetting(settings.patFont,0,6); @@ -2604,7 +2615,8 @@ void FurnaceGUI::commitSettings() { settings.snCore!=e->getConfInt("snCore",0) || settings.nesCore!=e->getConfInt("nesCore",0) || settings.fdsCore!=e->getConfInt("fdsCore",0) || - settings.c64Core!=e->getConfInt("c64Core",1) + settings.c64Core!=e->getConfInt("c64Core",1) || + settings.pokeyCore!=e->getConfInt("pokeyCore",1) ); e->setConf("mainFontSize",settings.mainFontSize); @@ -2624,6 +2636,7 @@ void FurnaceGUI::commitSettings() { e->setConf("nesCore",settings.nesCore); e->setConf("fdsCore",settings.fdsCore); e->setConf("c64Core",settings.c64Core); + e->setConf("pokeyCore",settings.pokeyCore); e->setConf("pcSpeakerOutMethod",settings.pcSpeakerOutMethod); e->setConf("yrw801Path",settings.yrw801Path); e->setConf("tg100Path",settings.tg100Path); diff --git a/src/gui/sysConf.cpp b/src/gui/sysConf.cpp index 15baa1ff..aeac2de1 100644 --- a/src/gui/sysConf.cpp +++ b/src/gui/sysConf.cpp @@ -30,8 +30,8 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo switch (type) { case DIV_SYSTEM_YM2612: case DIV_SYSTEM_YM2612_EXT: - case DIV_SYSTEM_YM2612_FRAC: - case DIV_SYSTEM_YM2612_FRAC_EXT: { + case DIV_SYSTEM_YM2612_DUALPCM: + case DIV_SYSTEM_YM2612_DUALPCM_EXT: { int clockSel=flags.getInt("clockSel",0); bool ladder=flags.getBool("ladderEffect",0); bool noExtMacros=flags.getBool("noExtMacros",false); @@ -59,7 +59,7 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo if (ImGui::Checkbox("Enable DAC distortion",&ladder)) { altered=true; } - if (type==DIV_SYSTEM_YM2612_EXT || type==DIV_SYSTEM_YM2612_FRAC_EXT) { + if (type==DIV_SYSTEM_YM2612_EXT || type==DIV_SYSTEM_YM2612_DUALPCM_EXT) { if (ImGui::Checkbox("Disable ExtCh FM macros (compatibility)",&noExtMacros)) { altered=true; } @@ -850,8 +850,8 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo } break; } - case DIV_SYSTEM_OPN: - case DIV_SYSTEM_OPN_EXT: { + case DIV_SYSTEM_YM2203: + case DIV_SYSTEM_YM2203_EXT: { int clockSel=flags.getInt("clockSel",0); int prescale=flags.getInt("prescale",0); bool noExtMacros=flags.getBool("noExtMacros",false); @@ -895,7 +895,7 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo altered=true; } - if (type==DIV_SYSTEM_OPN_EXT) { + if (type==DIV_SYSTEM_YM2203_EXT) { if (ImGui::Checkbox("Disable ExtCh FM macros (compatibility)",&noExtMacros)) { altered=true; } @@ -910,8 +910,8 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo } break; } - case DIV_SYSTEM_PC98: - case DIV_SYSTEM_PC98_EXT: { + case DIV_SYSTEM_YM2608: + case DIV_SYSTEM_YM2608_EXT: { int clockSel=flags.getInt("clockSel",0); int prescale=flags.getInt("prescale",0); bool noExtMacros=flags.getBool("noExtMacros",false); @@ -939,7 +939,7 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo altered=true; } - if (type==DIV_SYSTEM_PC98_EXT) { + if (type==DIV_SYSTEM_YM2608_EXT) { if (ImGui::Checkbox("Disable ExtCh FM macros (compatibility)",&noExtMacros)) { altered=true; } @@ -1243,6 +1243,7 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo // default to 44100Hz 16-bit stereo int sampRate=flags.getInt("rate",44100); int bitDepth=flags.getInt("outDepth",15)+1; + int interpolation=flags.getInt("interpolation",0); bool stereo=flags.getBool("stereo",false); ImGui::Text("Output rate:"); @@ -1261,11 +1262,30 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo altered=true; } + ImGui::Text("Interpolation:"); + if (ImGui::RadioButton("None",interpolation==0)) { + interpolation=0; + altered=true; + } + if (ImGui::RadioButton("Linear",interpolation==1)) { + interpolation=1; + altered=true; + } + if (ImGui::RadioButton("Cubic",interpolation==2)) { + interpolation=2; + altered=true; + } + if (ImGui::RadioButton("Sinc",interpolation==3)) { + interpolation=3; + altered=true; + } + if (altered) { e->lockSave([&]() { flags.set("rate",sampRate); flags.set("outDepth",bitDepth-1); flags.set("stereo",stereo); + flags.set("interpolation",interpolation); }); } break; diff --git a/src/log.cpp b/src/log.cpp index ef2750a2..1611e688 100644 --- a/src/log.cpp +++ b/src/log.cpp @@ -18,6 +18,8 @@ */ #include "ta-log.h" +#include +#include #ifdef IS_MOBILE int logLevel=LOGLEVEL_TRACE; @@ -25,18 +27,71 @@ int logLevel=LOGLEVEL_TRACE; int logLevel=LOGLEVEL_INFO; #endif +FILE* logFile; +char* logFileBuf; +unsigned int logFilePosI; +unsigned int logFilePosO; +std::thread* logFileThread; +std::mutex logFileLock; +std::mutex logFileLockI; +std::condition_variable logFileNotify; +bool logFileAvail=false; + std::atomic logPosition; LogEntry logEntries[TA_LOG_SIZE]; static constexpr unsigned int TA_LOG_MASK=TA_LOG_SIZE-1; +static constexpr unsigned int TA_LOGFILE_BUF_MASK=TA_LOGFILE_BUF_SIZE-1; + +const char* logTypes[5]={ + "ERROR", + "warning", + "info", + "debug", + "trace" +}; + +void appendLogBuf(const LogEntry& entry) { + logFileLockI.lock(); + + std::string toWrite=fmt::sprintf( + "%02d:%02d:%02d [%s] %s\n", + entry.time.tm_hour, + entry.time.tm_min, + entry.time.tm_sec, + logTypes[entry.loglevel], + entry.text + ); + + const char* msg=toWrite.c_str(); + size_t len=toWrite.size(); + + int remaining=(logFilePosO-logFilePosI-1)&TA_LOGFILE_BUF_SIZE; + + if (len>=(unsigned int)remaining) { + printf("line too long to fit in log buffer!\n"); + logFileLockI.unlock(); + return; + } + + if ((logFilePosI+len)>=TA_LOGFILE_BUF_SIZE) { + size_t firstWrite=TA_LOGFILE_BUF_SIZE-logFilePosI; + memcpy(logFileBuf+logFilePosI,msg,firstWrite); + memcpy(logFileBuf,msg+firstWrite,len-firstWrite); + } else { + memcpy(logFileBuf+logFilePosI,msg,len); + } + + logFilePosI=(logFilePosI+len)&TA_LOGFILE_BUF_MASK; + logFileLockI.unlock(); +} int writeLog(int level, const char* msg, fmt::printf_args args) { time_t thisMakesNoSense=time(NULL); - int pos=logPosition; - logPosition=(logPosition+1)&TA_LOG_MASK; + int pos=(logPosition.fetch_add(1))&TA_LOG_MASK; - logEntries[pos].text=fmt::vsprintf(msg,args); + logEntries[pos].text.assign(fmt::vsprintf(msg,args)); // why do I have to pass a pointer // can't I just pass the time_t directly?! #ifdef _WIN32 @@ -54,6 +109,12 @@ int writeLog(int level, const char* msg, fmt::printf_args args) { logEntries[pos].loglevel=level; logEntries[pos].ready=true; + // write to log file + if (logFileAvail) { + appendLogBuf(logEntries[pos]); + logFileNotify.notify_one(); + } + if (logLevel lock(logFileLock); + while (true) { + unsigned int logFilePosICopy=logFilePosI; + if (logFilePosICopy!=logFilePosO) { + // write + if (logFilePosO>logFilePosICopy) { + fwrite(logFileBuf+logFilePosO,1,TA_LOGFILE_BUF_SIZE-logFilePosO,logFile); + logFilePosO=0; + } else { + fwrite(logFileBuf+logFilePosO,1,logFilePosICopy-logFilePosO,logFile); + logFilePosO=logFilePosICopy&TA_LOGFILE_BUF_MASK; + } + } else { + // wait + if (!logFileAvail) break; + fflush(logFile); + logFileNotify.wait(lock); + } + } +} + +bool startLogFile(const char* path) { + if (logFileAvail) return true; + + // rotate log file if possible + + // open log file + if ((logFile=fopen(path,"w+"))==NULL) { + logFileAvail=false; + logW("could not open log file! (%s)",strerror(errno)); + return false; + } + + logFileBuf=new char[TA_LOGFILE_BUF_SIZE]; + logFilePosI=0; + logFilePosO=0; + logFileAvail=true; + + logFileThread=new std::thread(_logFileThread); + return true; +} + +bool finishLogFile() { + if (!logFileAvail) return false; + + logFileAvail=false; + + // flush + logFileLockI.lock(); + logFileNotify.notify_one(); + logFileThread->join(); + logFileLockI.unlock(); + + fclose(logFile); + return true; } diff --git a/src/main.cpp b/src/main.cpp index bea535a4..6bc7d960 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -195,6 +195,8 @@ TAParamResult pVersion(String) { printf("- Stella by Stella Team (GPLv2)\n"); printf("- vgsound_emu (second version, modified version) by cam900 (zlib license)\n"); printf("- MAME GA20 core by Acho A. Tang, R. Belmont, Valley Bell (BSD 3-clause)\n"); + printf("- Atari800 mzpokeysnd POKEY emulator by Michael Borisov (GPLv2)\n"); + printf("- ASAP POKEY emulator by Piotr Fusik ported to C++ by laoo (GPLv2)\n"); return TA_PARAM_QUIT; } @@ -416,23 +418,30 @@ int main(int argc, char** argv) { logI("usage: %s file",argv[0]); return 1; } + logI("Furnace version " DIV_VERSION "."); + + e.preInit(); + if (!fileName.empty()) { logI("loading module..."); FILE* f=ps_fopen(fileName.c_str(),"rb"); if (f==NULL) { reportError(fmt::sprintf("couldn't open file! (%s)",strerror(errno))); + finishLogFile(); return 1; } if (fseek(f,0,SEEK_END)<0) { reportError(fmt::sprintf("couldn't open file! (couldn't get file size: %s)",strerror(errno))); fclose(f); + finishLogFile(); return 1; } ssize_t len=ftell(f); if (len==(SIZE_MAX>>1)) { reportError(fmt::sprintf("couldn't open file! (couldn't get file length: %s)",strerror(errno))); fclose(f); + finishLogFile(); return 1; } if (len<1) { @@ -442,6 +451,7 @@ int main(int argc, char** argv) { reportError(fmt::sprintf("couldn't open file! (tell error: %s)",strerror(errno))); } fclose(f); + finishLogFile(); return 1; } unsigned char* file=new unsigned char[len]; @@ -449,23 +459,27 @@ int main(int argc, char** argv) { reportError(fmt::sprintf("couldn't open file! (size error: %s)",strerror(errno))); fclose(f); delete[] file; + finishLogFile(); return 1; } if (fread(file,1,(size_t)len,f)!=(size_t)len) { reportError(fmt::sprintf("couldn't open file! (read error: %s)",strerror(errno))); fclose(f); delete[] file; + finishLogFile(); return 1; } fclose(f); if (!e.load(file,(size_t)len)) { reportError(fmt::sprintf("could not open file! (%s)",e.getLastError())); + finishLogFile(); return 1; } } if (!e.init()) { if (consoleMode) { reportError("could not initialize engine!"); + finishLogFile(); return 1; } else { logE("could not initialize engine!"); @@ -479,6 +493,7 @@ int main(int argc, char** argv) { } else { e.benchmarkPlayback(); } + finishLogFile(); return 0; } if (outName!="" || vgmOutName!="" || cmdOutName!="") { @@ -519,6 +534,7 @@ int main(int argc, char** argv) { e.saveAudio(outName.c_str(),loops,outMode); e.waitAudioFile(); } + finishLogFile(); return 0; } @@ -536,6 +552,7 @@ int main(int argc, char** argv) { cli.loop(); cli.finish(); e.quit(); + finishLogFile(); return 0; } else { #ifdef HAVE_SDL2 @@ -545,6 +562,7 @@ int main(int argc, char** argv) { if (ev.type==SDL_QUIT) break; } e.quit(); + finishLogFile(); return 0; #else while (true) { @@ -562,6 +580,7 @@ int main(int argc, char** argv) { g.bindEngine(&e); if (!g.init()) { reportError(g.getLastError()); + finishLogFile(); return 1; } @@ -584,6 +603,8 @@ int main(int argc, char** argv) { logI("stopping engine."); e.quit(); + finishLogFile(); + #ifdef _WIN32 if (coResult==S_OK || coResult==S_FALSE) { CoUninitialize(); diff --git a/src/ta-log.h b/src/ta-log.h index f7921c71..160201e0 100644 --- a/src/ta-log.h +++ b/src/ta-log.h @@ -35,6 +35,9 @@ // this has to be a power of 2 #define TA_LOG_SIZE 2048 +// this as well +#define TA_LOGFILE_BUF_SIZE 65536 + extern int logLevel; extern std::atomic logPosition; @@ -76,4 +79,6 @@ template int logE(const char* msg, const T&... args) { } void initLog(); +bool startLogFile(const char* path); +bool finishLogFile(); #endif