From e965d1096c96b4358a83b2e75e9e2a124542b895 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 17 Dec 2022 17:27:11 -0500 Subject: [PATCH 01/93] RF5C68: fix per-chan osc --- src/engine/platform/rf5c68.cpp | 5 +++++ 1 file changed, 5 insertions(+) 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]}; From ceb27728d38cf065eb7bce90c7372d7b420d1189 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 17 Dec 2022 22:45:08 -0500 Subject: [PATCH 02/93] add option to swap nibbles on raw sample import issue #791 --- src/engine/engine.cpp | 10 +++++++++- src/engine/engine.h | 2 +- src/gui/gui.cpp | 7 ++++++- src/gui/gui.h | 2 +- 4 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 40f9f8cf..281e7c5d 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -3293,7 +3293,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; @@ -3459,6 +3459,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; } diff --git a/src/engine/engine.h b/src/engine/engine.h index 612bd4be..0e10ff66 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); diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 6753fdfd..76af88fc 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -5019,8 +5019,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 { @@ -5684,6 +5688,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..805ce67c 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -1116,7 +1116,7 @@ class FurnaceGUI { String pendingRawSample; int pendingRawSampleDepth, pendingRawSampleChannels; - bool pendingRawSampleUnsigned, pendingRawSampleBigEndian; + bool pendingRawSampleUnsigned, pendingRawSampleBigEndian, pendingRawSampleSwapNibbles; ImGuiWindowFlags globalWinFlags; From f407ab9d40aa6977d8574cf84ac4337266c71001 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 17 Dec 2022 23:14:44 -0500 Subject: [PATCH 03/93] SegaPCM: fix volume/panning regression issue #786 --- src/engine/platform/segapcm.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/engine/platform/segapcm.cpp b/src/engine/platform/segapcm.cpp index 80f405db..7dd949d9 100644 --- a/src/engine/platform/segapcm.cpp +++ b/src/engine/platform/segapcm.cpp @@ -287,7 +287,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 +311,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; From d897ac32b0a75986e65d0ab5088bead589d19954 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 17 Dec 2022 23:16:24 -0500 Subject: [PATCH 04/93] SegaPCM: what?????????? --- src/engine/platform/segapcm.cpp | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/engine/platform/segapcm.cpp b/src/engine/platform/segapcm.cpp index 7dd949d9..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); } From 51ea3cec2a36337948b62276cf357c60c5cb982e Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 18 Dec 2022 01:55:21 -0500 Subject: [PATCH 05/93] add log file writing --- src/engine/configEngine.cpp | 1 + src/engine/engine.cpp | 7 ++- src/engine/engine.h | 3 + src/log.cpp | 121 ++++++++++++++++++++++++++++++++++++ src/main.cpp | 6 ++ src/ta-log.h | 5 ++ 6 files changed, 142 insertions(+), 1 deletion(-) 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/engine.cpp b/src/engine/engine.cpp index 281e7c5d..6ee8513f 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -4221,7 +4221,7 @@ bool DivEngine::deinitAudioBackend(bool dueToSwitchMaster) { return true; } -bool DivEngine::init() { +void DivEngine::preInit() { // register systems if (!systemsRegistered) registerSystems(); @@ -4229,8 +4229,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 0e10ff66..f57bd8d0 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -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/log.cpp b/src/log.cpp index ef2750a2..32023791 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,11 +27,54 @@ int logLevel=LOGLEVEL_TRACE; int logLevel=LOGLEVEL_INFO; #endif +FILE* logFile; +char* logFileBuf; +unsigned int logFilePosI=0; +unsigned int logFilePosO=0; +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 char* msg, size_t len) { + logFileLockI.lock(); + + int remaining=logFilePosO-logFilePosI; + if (remaining<=0) remaining+=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); @@ -54,6 +99,20 @@ 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) { + std::string toWrite=fmt::sprintf( + "%02d:%02d:%02d [%s] %s\n", + logEntries[pos].time.tm_hour, + logEntries[pos].time.tm_min, + logEntries[pos].time.tm_sec, + logTypes[logEntries[pos].loglevel], + logEntries[pos].text + ); + appendLogBuf(toWrite.c_str(),toWrite.size()); + 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; + } + } 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; +} \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index bea535a4..019ba75d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -416,7 +416,11 @@ 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"); @@ -584,6 +588,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 From e0723c748540164bb453bfafda5b3297db14187e Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 18 Dec 2022 03:54:30 -0500 Subject: [PATCH 06/93] DAC: add interpolation settings --- src/engine/filter.cpp | 23 ++++++++++++++++- src/engine/filter.h | 7 +++++ src/engine/platform/pcmdac.cpp | 47 +++++++++++++++++++++++++++++++++- src/engine/platform/pcmdac.h | 6 +++++ src/gui/sysConf.cpp | 20 +++++++++++++++ 5 files changed, 101 insertions(+), 2 deletions(-) diff --git a/src/engine/filter.cpp b/src/engine/filter.cpp index 8b0e5a2e..88b38148 100644 --- a/src/engine/filter.cpp +++ b/src/engine/filter.cpp @@ -24,6 +24,7 @@ float* DivFilterTables::cubicTable=NULL; float* DivFilterTables::sincTable=NULL; +float* DivFilterTables::sincTable8=NULL; float* DivFilterTables::sincIntegralTable=NULL; // portions from Schism Tracker (scripts/lutgen.c) @@ -44,7 +45,7 @@ float* DivFilterTables::getCubicTable() { return cubicTable; } -float* DivFilterTables:: getSincTable() { +float* DivFilterTables::getSincTable() { if (sincTable==NULL) { logD("initializing sinc table."); sincTable=new float[65536]; @@ -64,6 +65,26 @@ float* DivFilterTables:: getSincTable() { return sincTable; } +float* DivFilterTables::getSincTable8() { + if (sincTable8==NULL) { + logD("initializing sinc table (8)."); + sincTable8=new float[32768]; + + sincTable8[0]=1.0f; + for (int i=1; i<32768; i++) { + int mapped=((i&8191)<<2)|(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/pcmdac.cpp b/src/engine/platform/pcmdac.cpp index 803eef7d..d172b38d 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,7 @@ void DivPlatformPCMDAC::setFlags(const DivConfig& flags) { chipClock=rate; outDepth=(flags.getInt("outDepth",15))&15; outStereo=flags.getBool("stereo",true); + interp=flags.getInt("interpolation",0); } 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/gui/sysConf.cpp b/src/gui/sysConf.cpp index c5825c24..b8e95a71 100644 --- a/src/gui/sysConf.cpp +++ b/src/gui/sysConf.cpp @@ -1227,6 +1227,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:"); @@ -1245,11 +1246,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; From 83abd39c4ef74d95b8099e17a7def6c7537a7433 Mon Sep 17 00:00:00 2001 From: TheDuccinator <66538032+TheDuccinator@users.noreply.github.com> Date: Sun, 18 Dec 2022 00:55:42 -0800 Subject: [PATCH 07/93] Add Remark Music AY Cover Another one to the pile bois. --- demos/ay8910/remark_music_ay3.fur | Bin 0 -> 1372 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 demos/ay8910/remark_music_ay3.fur diff --git a/demos/ay8910/remark_music_ay3.fur b/demos/ay8910/remark_music_ay3.fur new file mode 100644 index 0000000000000000000000000000000000000000..92ad66e0391a7c900190f9ac5ce3ea088c48c308 GIT binary patch literal 1372 zcmV-i1*7_Sob6f7ZyQAv|IO~~uGe1M5uhI0G_ioHR5U1|1=30pF)g8~nh=`wP;rW! ztrFGwsFPIU)HVW%Ge|kY0VEFGkhsHze}OAUjsaTQCI&OJA6a|9X17iV2|LOt^Y*=c z@AuxD`R&>s7+Y^F&DPAy;_}>j-5l5eP=PnbC&%6=5?iP$0j|6=3JtWUZ0QPY-befx z6%4`cI3tNH_IF%27iSx{D--Lh3pHFd8_VWWWyY+Zt<>&boS8Q(m)C2xg{9fGa|6Q{h6ZhM>{|7`QTD$!zpyg%(TX`Tn6K5BYqzJ(I$O@q)^AfMp z`l^{A zoLeK})<_CQ|0F#6xfIdg;x#HeH7XYkRv}fPGC$!_m3RPBZw0}884l)r3NRmVFsEEF z<#MT1EE)!H+iu@@+pNY}og*r19P12aCuV0S1aJ`M)o_?^B!(IAxEb#8%@m1W;fX)* zCSEGCoO#APUzumB*l9MK+uPex4tAD(eJ6W+PaKyOILU^dql0XV%-FTrOw^V;;V_3& zzQktP~9VRe0bFDFWZ%fsgHW&k4+HA~`fqxVFmcLK&yG{{E}E zv;FkR<3Arg{NwlEw!8u<4$N?<9@&&SC@~G&oaR{d9=k|JLDy9p#?>LwKa7Z;hLH`< zbxtrE46)JuUOmtAE3{;n&kDMxP}T<7-jPpsk|n$UNFIAgdJh;$Q(Tj&>dbYWdM_+G ze=$MJ8t&y*+wjXeq>d|w^=cDmPdcuo)r(}!hsTdkvkz*U-Y=(%pEiF(;9r8=fhVY^ zv3gD5-(R0@>!0=iX!Rc+A&>wegx(-1NVf&=enZ5^7j2mR&R@JC;v2>{Onk!+mJqW3 zAMXCQ5rX~gkO*+v1pG1;*o!x8yxvHGhvH+T-Ef(l$wyjt3BmoMbMBx4WpXBG^1;a- zA$Y%i#NA-9SD+m1?-#giM(Ew~ta|?R4vSw9#McG>HJ@H&FT&%u7uQc_AIm)aQTiy^ zZ?F6Ge%WuY`t*J|Y5(1ERQkVH{T0a?RODmNkE@T$KmLB*(MREze0u7S=MjSUgK}I# zM~x6vD3&Z^tHYoBNsT)Cq#K6&D}Wb%w8NgD5_-QYO9K7Bb^qM198Or$Zau>5@9LxK zBkI4Dvfg%o%80_FS$e?rr#tpCdSdGlC2NBG>7L|YW`~r*v-f1(Cu@$(^UH^vUlF14 z2aej7Pa7e~mNO2(Nb$tr0E;mR1_8t~SV5rIL=2aJ&;Inizl{)RB0>-WOv31KfHXnEoZ;gdU{NBP++`@>$J!%_dX1L6J- e9qu4WknVs#`*n9+fIY(SWb165X#EcnGiM%1X}tsh literal 0 HcmV?d00001 From 960e2ba1a7c714285d917ba24eefeb872e88f224 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 18 Dec 2022 04:03:49 -0500 Subject: [PATCH 08/93] DAC: fix chan osc --- src/engine/platform/pcmdac.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/engine/platform/pcmdac.cpp b/src/engine/platform/pcmdac.cpp index d172b38d..53416456 100644 --- a/src/engine/platform/pcmdac.cpp +++ b/src/engine/platform/pcmdac.cpp @@ -443,6 +443,7 @@ void DivPlatformPCMDAC::setFlags(const DivConfig& flags) { 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) { From cb003e843e5aa7423239fdec6309a06bf71c25f0 Mon Sep 17 00:00:00 2001 From: TheDuccinator <66538032+TheDuccinator@users.noreply.github.com> Date: Sun, 18 Dec 2022 01:04:50 -0800 Subject: [PATCH 09/93] hold up --- demos/ay8910/remark_music_ay3.fur | Bin 1372 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 demos/ay8910/remark_music_ay3.fur diff --git a/demos/ay8910/remark_music_ay3.fur b/demos/ay8910/remark_music_ay3.fur deleted file mode 100644 index 92ad66e0391a7c900190f9ac5ce3ea088c48c308..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1372 zcmV-i1*7_Sob6f7ZyQAv|IO~~uGe1M5uhI0G_ioHR5U1|1=30pF)g8~nh=`wP;rW! ztrFGwsFPIU)HVW%Ge|kY0VEFGkhsHze}OAUjsaTQCI&OJA6a|9X17iV2|LOt^Y*=c z@AuxD`R&>s7+Y^F&DPAy;_}>j-5l5eP=PnbC&%6=5?iP$0j|6=3JtWUZ0QPY-befx z6%4`cI3tNH_IF%27iSx{D--Lh3pHFd8_VWWWyY+Zt<>&boS8Q(m)C2xg{9fGa|6Q{h6ZhM>{|7`QTD$!zpyg%(TX`Tn6K5BYqzJ(I$O@q)^AfMp z`l^{A zoLeK})<_CQ|0F#6xfIdg;x#HeH7XYkRv}fPGC$!_m3RPBZw0}884l)r3NRmVFsEEF z<#MT1EE)!H+iu@@+pNY}og*r19P12aCuV0S1aJ`M)o_?^B!(IAxEb#8%@m1W;fX)* zCSEGCoO#APUzumB*l9MK+uPex4tAD(eJ6W+PaKyOILU^dql0XV%-FTrOw^V;;V_3& zzQktP~9VRe0bFDFWZ%fsgHW&k4+HA~`fqxVFmcLK&yG{{E}E zv;FkR<3Arg{NwlEw!8u<4$N?<9@&&SC@~G&oaR{d9=k|JLDy9p#?>LwKa7Z;hLH`< zbxtrE46)JuUOmtAE3{;n&kDMxP}T<7-jPpsk|n$UNFIAgdJh;$Q(Tj&>dbYWdM_+G ze=$MJ8t&y*+wjXeq>d|w^=cDmPdcuo)r(}!hsTdkvkz*U-Y=(%pEiF(;9r8=fhVY^ zv3gD5-(R0@>!0=iX!Rc+A&>wegx(-1NVf&=enZ5^7j2mR&R@JC;v2>{Onk!+mJqW3 zAMXCQ5rX~gkO*+v1pG1;*o!x8yxvHGhvH+T-Ef(l$wyjt3BmoMbMBx4WpXBG^1;a- zA$Y%i#NA-9SD+m1?-#giM(Ew~ta|?R4vSw9#McG>HJ@H&FT&%u7uQc_AIm)aQTiy^ zZ?F6Ge%WuY`t*J|Y5(1ERQkVH{T0a?RODmNkE@T$KmLB*(MREze0u7S=MjSUgK}I# zM~x6vD3&Z^tHYoBNsT)Cq#K6&D}Wb%w8NgD5_-QYO9K7Bb^qM198Or$Zau>5@9LxK zBkI4Dvfg%o%80_FS$e?rr#tpCdSdGlC2NBG>7L|YW`~r*v-f1(Cu@$(^UH^vUlF14 z2aej7Pa7e~mNO2(Nb$tr0E;mR1_8t~SV5rIL=2aJ&;Inizl{)RB0>-WOv31KfHXnEoZ;gdU{NBP++`@>$J!%_dX1L6J- e9qu4WknVs#`*n9+fIY(SWb165X#EcnGiM%1X}tsh From 4194c2684f5467b790b85a324d038adbf8048b01 Mon Sep 17 00:00:00 2001 From: TheDuccinator <66538032+TheDuccinator@users.noreply.github.com> Date: Sun, 18 Dec 2022 01:05:06 -0800 Subject: [PATCH 10/93] there we go --- demos/ay8910/remark_music.fur | Bin 0 -> 1372 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 demos/ay8910/remark_music.fur diff --git a/demos/ay8910/remark_music.fur b/demos/ay8910/remark_music.fur new file mode 100644 index 0000000000000000000000000000000000000000..92ad66e0391a7c900190f9ac5ce3ea088c48c308 GIT binary patch literal 1372 zcmV-i1*7_Sob6f7ZyQAv|IO~~uGe1M5uhI0G_ioHR5U1|1=30pF)g8~nh=`wP;rW! ztrFGwsFPIU)HVW%Ge|kY0VEFGkhsHze}OAUjsaTQCI&OJA6a|9X17iV2|LOt^Y*=c z@AuxD`R&>s7+Y^F&DPAy;_}>j-5l5eP=PnbC&%6=5?iP$0j|6=3JtWUZ0QPY-befx z6%4`cI3tNH_IF%27iSx{D--Lh3pHFd8_VWWWyY+Zt<>&boS8Q(m)C2xg{9fGa|6Q{h6ZhM>{|7`QTD$!zpyg%(TX`Tn6K5BYqzJ(I$O@q)^AfMp z`l^{A zoLeK})<_CQ|0F#6xfIdg;x#HeH7XYkRv}fPGC$!_m3RPBZw0}884l)r3NRmVFsEEF z<#MT1EE)!H+iu@@+pNY}og*r19P12aCuV0S1aJ`M)o_?^B!(IAxEb#8%@m1W;fX)* zCSEGCoO#APUzumB*l9MK+uPex4tAD(eJ6W+PaKyOILU^dql0XV%-FTrOw^V;;V_3& zzQktP~9VRe0bFDFWZ%fsgHW&k4+HA~`fqxVFmcLK&yG{{E}E zv;FkR<3Arg{NwlEw!8u<4$N?<9@&&SC@~G&oaR{d9=k|JLDy9p#?>LwKa7Z;hLH`< zbxtrE46)JuUOmtAE3{;n&kDMxP}T<7-jPpsk|n$UNFIAgdJh;$Q(Tj&>dbYWdM_+G ze=$MJ8t&y*+wjXeq>d|w^=cDmPdcuo)r(}!hsTdkvkz*U-Y=(%pEiF(;9r8=fhVY^ zv3gD5-(R0@>!0=iX!Rc+A&>wegx(-1NVf&=enZ5^7j2mR&R@JC;v2>{Onk!+mJqW3 zAMXCQ5rX~gkO*+v1pG1;*o!x8yxvHGhvH+T-Ef(l$wyjt3BmoMbMBx4WpXBG^1;a- zA$Y%i#NA-9SD+m1?-#giM(Ew~ta|?R4vSw9#McG>HJ@H&FT&%u7uQc_AIm)aQTiy^ zZ?F6Ge%WuY`t*J|Y5(1ERQkVH{T0a?RODmNkE@T$KmLB*(MREze0u7S=MjSUgK}I# zM~x6vD3&Z^tHYoBNsT)Cq#K6&D}Wb%w8NgD5_-QYO9K7Bb^qM198Or$Zau>5@9LxK zBkI4Dvfg%o%80_FS$e?rr#tpCdSdGlC2NBG>7L|YW`~r*v-f1(Cu@$(^UH^vUlF14 z2aej7Pa7e~mNO2(Nb$tr0E;mR1_8t~SV5rIL=2aJ&;Inizl{)RB0>-WOv31KfHXnEoZ;gdU{NBP++`@>$J!%_dX1L6J- e9qu4WknVs#`*n9+fIY(SWb165X#EcnGiM%1X}tsh literal 0 HcmV?d00001 From 8309ce8df81d587a7c60d0d4147602f2ea6f95a6 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 18 Dec 2022 11:29:21 -0500 Subject: [PATCH 11/93] debug... AGAIN --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bcd989c2..420b08f8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,7 +11,7 @@ defaults: shell: bash env: - BUILD_TYPE: Release + BUILD_TYPE: Debug jobs: build: From c4be8c9f5da4b1d0be45a13b46f3817484e76820 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 18 Dec 2022 20:01:29 -0500 Subject: [PATCH 12/93] let's try out mzpokeysnd to-do: - de-Atari800ize the core - remove resampling funcs - make Update* public - - - DivPlatformPOKEY already! --- src/engine/platform/sound/pokey/mzpokeysnd.c | 2301 ++++++++++++++++++ src/engine/platform/sound/pokey/mzpokeysnd.h | 11 + 2 files changed, 2312 insertions(+) create mode 100644 src/engine/platform/sound/pokey/mzpokeysnd.c create mode 100644 src/engine/platform/sound/pokey/mzpokeysnd.h diff --git a/src/engine/platform/sound/pokey/mzpokeysnd.c b/src/engine/platform/sound/pokey/mzpokeysnd.c new file mode 100644 index 00000000..0e17f0bd --- /dev/null +++ b/src/engine/platform/sound/pokey/mzpokeysnd.c @@ -0,0 +1,2301 @@ +/* + * 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 "config.h" +#include +#include + +#include "asap_internal.h" +#include "atari.h" +#include "mzpokeysnd.h" +#include "pokeysnd.h" +#include "remez.h" +#include "antic.h" +#include "gtia.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 + +#define SND_FILTER_SIZE 2048 + +/* Filter */ +static int pokey_frq; /* Hz - for easier resampling */ +static int filter_size; +static double filter_data[SND_FILTER_SIZE]; +static int audible_frq; + +static const int pokey_frq_ideal = 1789790; /* Hz - True */ + +/* Flags and quality */ +static int snd_quality = 0; + +/* Poly tables */ +static int poly4tbl[15]; +static int poly5tbl[31]; +static unsigned char poly17tbl[131071]; +static int poly9tbl[511]; + + +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 + +#ifdef SYNCHRONIZED_SOUND +static double ticks_per_sample; +static double samp_pos; +#endif /* SYNCHRONIZED_SOUND */ + +/* State variables for single Pokey Chip */ +typedef struct stPokeyState +{ + int curtick; + /* Poly positions */ + int poly4pos; + int poly5pos; + int poly17pos; + int poly9pos; + + /* Change queue */ + qev_t ovola; + int qet[1322]; /* maximal length of filter */ + qev_t qev[1322]; + int qebeg; + int qeend; + + /* 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; + + /* GTIA speaker */ + + int speaker; + +} PokeyState; + +// TODO: make this fully struct-ized +PokeyState pokey_states[1]; + +static struct { + double s16; + double s8; +} volume; + +/* Forward declarations for ResetPokeyState */ + +static int readout0_normal(PokeyState* ps); +static void event0_pure(PokeyState* ps, int p5v, int p4v, int p917v); + +static int readout1_normal(PokeyState* ps); +static void event1_pure(PokeyState* ps, int p5v, int p4v, int p917v); + +static int readout2_normal(PokeyState* ps); +static void event2_pure(PokeyState* ps, int p5v, int p4v, int p917v); + +static int readout3_normal(PokeyState* ps); +static void event3_pure(PokeyState* ps, int p5v, int p4v, int p917v); + +static void ResetPokeyState(PokeyState* ps) +{ + /* Poly positions */ + ps->poly4pos = 0; + ps->poly5pos = 0; + ps->poly9pos = 0; + ps->poly17pos = 0; + + /* Change queue */ + ps->ovola = 0; + ps->qebeg = 0; + ps->qeend = 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; + + /* GTIA speaker */ + ps->speaker = 0; +} + + +static double read_resam_all(PokeyState* ps) +{ + int i = ps->qebeg; + qev_t avol,bvol; + double sum; + + if(ps->qebeg == ps->qeend) + { + return ps->ovola * filter_data[0]; /* if no events in the queue */ + } + + avol = ps->ovola; + sum = 0; + + /* Separate two loop cases, for wrap-around and without */ + if(ps->qeend < ps->qebeg) /* With wrap */ + { + while(iqev[i]; + sum += (avol-bvol)*filter_data[ps->curtick - ps->qet[i]]; + avol = bvol; + ++i; + } + i=0; + } + + /* without wrap */ + while(iqeend) + { + bvol = ps->qev[i]; + sum += (avol-bvol)*filter_data[ps->curtick - ps->qet[i]]; + avol = bvol; + ++i; + } + + sum += avol*filter_data[0]; + return sum; +} + +#ifdef SYNCHRONIZED_SOUND +/* linear interpolation of filter data */ +static double interp_filter_data(int pos, double frac) +{ + if (pos+1 >= filter_size) { + return 0.0; + } + return (frac)*filter_data[pos+1]+(1-frac)*(filter_data[pos]-filter_data[filter_size-1]); +} + +/* returns the filtered output sample value using an interpolated filter */ +/* frac is the fractional distance of the output sample point between + * input sample values */ +static double interp_read_resam_all(PokeyState* ps, double frac) +{ + int i = ps->qebeg; + qev_t avol,bvol; + double sum; + + if (ps->qebeg == ps->qeend) + { + return ps->ovola * interp_filter_data(0,frac); /* if no events in the queue */ + } + + avol = ps->ovola; + sum = 0; + + /* Separate two loop cases, for wrap-around and without */ + if (ps->qeend < ps->qebeg) /* With wrap */ + { + while (i < filter_size) + { + bvol = ps->qev[i]; + sum += (avol-bvol)*interp_filter_data(ps->curtick - ps->qet[i],frac); + avol = bvol; + ++i; + } + i = 0; + } + + /* without wrap */ + while (i < ps->qeend) + { + bvol = ps->qev[i]; + sum += (avol-bvol)*interp_filter_data(ps->curtick - ps->qet[i],frac); + avol = bvol; + ++i; + } + + sum += avol*interp_filter_data(0,frac); + + return sum; +} +#endif /* SYNCHRONIZED_SOUND */ + +static void add_change(PokeyState* ps, qev_t a) +{ + ps->qev[ps->qeend] = a; + ps->qet[ps->qeend] = ps->curtick; /*0;*/ + ++ps->qeend; + if(ps->qeend >= filter_size) + ps->qeend = 0; +} + +static void bump_qe_subticks(PokeyState* ps, int subticks) +{ + /* Remove too old events from the queue while bumping */ + int i = ps->qebeg; + /* we must avoid curtick overflow in a 32-bit int, will happen in 20 min */ + static const int tickoverflowlimit = 1000000000; + ps->curtick += subticks; + if (ps->curtick > tickoverflowlimit) { + ps->curtick -= tickoverflowlimit/2; + for (i=0; iqet[i] > tickoverflowlimit/2) { + ps->qet[i] -= tickoverflowlimit/2; + } + } + } + + + if(ps->qeend < ps->qebeg) /* Loop with wrap */ + { + while(iqet[i] += subticks;*/ + if(ps->curtick - ps->qet[i] >= filter_size - 1) + { + ps->ovola = ps->qev[i]; + ++ps->qebeg; + if(ps->qebeg >= filter_size) + ps->qebeg = 0; + } + else { + return; + } + ++i; + } + i=0; + } + /* loop without wrap */ + while(iqeend) + { + /*ps->qet[i] += subticks;*/ + if(ps->curtick - ps->qet[i] >= filter_size - 1) + { + ps->ovola = ps->qev[i]; + ++ps->qebeg; + if(ps->qebeg >= filter_size) + ps->qebeg = 0; + } + else { + return; + } + ++i; + } +} + + + +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; + } +} + +static 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 + + ************************************/ + +static int readout0_vo(PokeyState* ps) +{ + return ps->vol0; +} + +static int readout0_hipass(PokeyState* ps) +{ + if(ps->c0t2 ^ ps->c0t3) + return ps->vol0; + else return 0; +} + +static int readout0_normal(PokeyState* ps) +{ + if(ps->c0t2) + return ps->vol0; + else return 0; +} + +/*********************************** + + READ OUTPUT 1 + + ************************************/ + +static int readout1_vo(PokeyState* ps) +{ + return ps->vol1; +} + +static int readout1_hipass(PokeyState* ps) +{ + if(ps->c1t2 ^ ps->c1t3) + return ps->vol1; + else return 0; +} + +static int readout1_normal(PokeyState* ps) +{ + if(ps->c1t2) + return ps->vol1; + else return 0; +} + +/*********************************** + + READ OUTPUT 2 + + ************************************/ + +static int readout2_vo(PokeyState* ps) +{ + return ps->vol2; +} + +static int readout2_normal(PokeyState* ps) +{ + if(ps->c2t2) + return ps->vol2; + else return 0; +} + +/*********************************** + + READ OUTPUT 3 + + ************************************/ + +static int readout3_vo(PokeyState* ps) +{ + return ps->vol3; +} + +static int readout3_normal(PokeyState* ps) +{ + if(ps->c3t2) + return ps->vol3; + else return 0; +} + + +/*********************************** + + EVENT CHANNEL 0 + + ************************************/ + +static void event0_pure(PokeyState* ps, int p5v, int p4v, int p917v) +{ + ps->c0t2 = !ps->c0t2; + ps->c0t1 = p5v; +} + +static void event0_p5(PokeyState* ps, int p5v, int p4v, int p917v) +{ + if(ps->c0t1) + ps->c0t2 = !ps->c0t2; + ps->c0t1 = p5v; +} + +static void event0_p4(PokeyState* ps, int p5v, int p4v, int p917v) +{ + ps->c0t2 = p4v; + ps->c0t1 = p5v; +} + +static void event0_p917(PokeyState* ps, int p5v, int p4v, int p917v) +{ + ps->c0t2 = p917v; + ps->c0t1 = p5v; +} + +static void event0_p4_p5(PokeyState* ps, int p5v, int p4v, int p917v) +{ + if(ps->c0t1) + ps->c0t2 = p4v; + ps->c0t1 = p5v; +} + +static void event0_p917_p5(PokeyState* ps, int p5v, int p4v, int p917v) +{ + if(ps->c0t1) + ps->c0t2 = p917v; + ps->c0t1 = p5v; +} + +/*********************************** + + EVENT CHANNEL 1 + + ************************************/ + +static void event1_pure(PokeyState* ps, int p5v, int p4v, int p917v) +{ + ps->c1t2 = !ps->c1t2; + ps->c1t1 = p5v; +} + +static void event1_p5(PokeyState* ps, int p5v, int p4v, int p917v) +{ + if(ps->c1t1) + ps->c1t2 = !ps->c1t2; + ps->c1t1 = p5v; +} + +static void event1_p4(PokeyState* ps, int p5v, int p4v, int p917v) +{ + ps->c1t2 = p4v; + ps->c1t1 = p5v; +} + +static void event1_p917(PokeyState* ps, int p5v, int p4v, int p917v) +{ + ps->c1t2 = p917v; + ps->c1t1 = p5v; +} + +static void event1_p4_p5(PokeyState* ps, int p5v, int p4v, int p917v) +{ + if(ps->c1t1) + ps->c1t2 = p4v; + ps->c1t1 = p5v; +} + +static void event1_p917_p5(PokeyState* ps, int p5v, int p4v, int p917v) +{ + if(ps->c1t1) + ps->c1t2 = p917v; + ps->c1t1 = p5v; +} + +/*********************************** + + EVENT CHANNEL 2 + + ************************************/ + +static 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; +} + +static 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; +} + +static 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; +} + +static 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; +} + +static 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; +} + +static 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 + + ************************************/ + +static 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; +} + +static 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; +} + +static 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; +} + +static 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; +} + +static 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; +} + +static 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; +} + +static 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 +#ifdef SYNCHRONIZED_SOUND + outvol_new = pokeymix[ps->outvol_0 + ps->outvol_1 + ps->outvol_2 + ps->outvol_3 + ps->speaker]; +#else + outvol_new = pokeymix[ps->outvol_0 + ps->outvol_1 + ps->outvol_2 + ps->outvol_3]; +#endif /* SYNCHRONIZED_SOUND */ +#else + outvol_new = ps->outvol_0 + ps->outvol_1 + ps->outvol_2 + ps->outvol_3; +#ifdef SYNCHRONIZED_SOUND + outvol_new += ps->speaker; +#endif /* SYNCHRONIZED_SOUND */ +#endif /* NONLINEAR_MIXING */ + if(outvol_new != ps->outvol_all) + { + ps->outvol_all = outvol_new; + add_change(ps, 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); + bump_qe_subticks(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 +#ifdef SYNCHRONIZED_SOUND + outvol_new = pokeymix[ps->outvol_0 + ps->outvol_1 + ps->outvol_2 + ps->outvol_3 + ps->speaker]; +#else + outvol_new = pokeymix[ps->outvol_0 + ps->outvol_1 + ps->outvol_2 + ps->outvol_3]; +#endif /* SYNCHRONIZED_SOUND */ +#else + outvol_new = ps->outvol_0 + ps->outvol_1 + ps->outvol_2 + ps->outvol_3; +#ifdef SYNCHRONIZED_SOUND + outvol_new += ps->speaker; +#endif /* SYNCHRONIZED_SOUND */ +#endif /* NONLINEAR_MIXING */ + if(outvol_new != ps->outvol_all) + { + ps->outvol_all = outvol_new; + add_change(ps, outvol_new); + } + } + } +} + +static double generate_sample(PokeyState* ps) +{ + /*unsigned long ta = (subticks+pokey_frq)/POKEYSND_playback_freq; + subticks = (subticks+pokey_frq)%POKEYSND_playback_freq;*/ + + advance_ticks(ps, pokey_frq/POKEYSND_playback_freq); + return read_resam_all(ps); +} + +/****************************************** + filter table generator by Krzysztof Nikiel + ******************************************/ + +static int remez_filter_table(double resamp_rate, /* output_rate/input_rate */ + double *cutoff, int quality) +{ + int i; + static const int orders[] = {600, 800, 1000, 1200}; + static const struct { + int stop; /* stopband ripple */ + double weight; /* stopband weight */ + double twidth[sizeof(orders)/sizeof(orders[0])]; + } paramtab[] = + { + {70, 90, {4.9e-3, 3.45e-3, 2.65e-3, 2.2e-3}}, + {55, 25, {3.4e-3, 2.7e-3, 2.05e-3, 1.7e-3}}, + {40, 6.0, {2.6e-3, 1.8e-3, 1.5e-3, 1.2e-3}}, + {-1, 0, {0, 0, 0, 0}} + }; + static const double passtab[] = {0.5, 0.6, 0.7}; + int ripple = 0, order = 0; + int size; + double weights[2], desired[2], bands[4]; + static const int interlevel = 5; + double step = 1.0 / interlevel; + + *cutoff = 0.95 * 0.5 * resamp_rate; + + if (quality >= (int) (sizeof(passtab) / sizeof(passtab[0]))) + quality = (int) (sizeof(passtab) / sizeof(passtab[0])) - 1; + + for (ripple = 0; paramtab[ripple].stop > 0; ripple++) + { + for (order = 0; order < (int) (sizeof(orders)/sizeof(orders[0])); order++) + { + if ((*cutoff - paramtab[ripple].twidth[order]) + > passtab[quality] * 0.5 * resamp_rate) + /* transition width OK */ + goto found; + } + } + + /* not found -- use shortest transition */ + ripple--; + order--; + +found: + + size = orders[order] + 1; + + if (size > SND_FILTER_SIZE) /* static table too short */ + return 0; + + desired[0] = 1; + desired[1] = 0; + + weights[0] = 1; + weights[1] = paramtab[ripple].weight; + + bands[0] = 0; + bands[2] = *cutoff; + bands[1] = bands[2] - paramtab[ripple].twidth[order]; + bands[3] = 0.5; + + bands[1] *= (double)interlevel; + bands[2] *= (double)interlevel; + REMEZ_CreateFilter(filter_data, (size / interlevel) + 1, 2, bands, desired, weights, REMEZ_BANDPASS); + for (i = size - interlevel; i >= 0; i -= interlevel) + { + int s; + double h1 = filter_data[i/interlevel]; + double h2 = filter_data[i/interlevel+1]; + + for (s = 0; s < interlevel; s++) + { + double d = (double)s * step; + filter_data[i+s] = (h1*(1.0 - d) + h2 * d) * step; + } + } + + /* compute reversed cumulative sum table */ + for (i = size - 2; i >= 0; i--) + filter_data[i] += filter_data[i + 1]; + + return size; +} + +static void mzpokeysnd_process_8(void* sndbuffer, int sndn); +static void mzpokeysnd_process_16(void* sndbuffer, int sndn); +static void Update_pokey_sound_mz(UWORD addr, UBYTE val, UBYTE gain); +#ifdef VOL_ONLY_SOUND +static void Update_vol_only_sound_mz( void ); +#endif + +/*****************************************************************************/ +/* 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 */ +/* */ +/*****************************************************************************/ + +#ifdef SYNCHRONIZED_SOUND +static void generate_sync(unsigned int num_ticks); + +static void init_syncsound(void) +{ + double samples_per_frame = (double)POKEYSND_playback_freq/(Atari800_tv_mode == Atari800_TV_PAL ? Atari800_FPS_PAL : Atari800_FPS_NTSC); + unsigned int ticks_per_frame = Atari800_tv_mode*114; + ticks_per_sample = (double)ticks_per_frame / samples_per_frame; + samp_pos = 0.0; + POKEYSND_GenerateSync = generate_sync; +} +#endif /* SYNCHRONIZED_SOUND */ + +int MZPOKEYSND_Init(size_t freq17, int playback_freq, + int flags, int quality + , int clear_regs + ) +{ + double cutoff; + + snd_quality = quality; + + POKEYSND_Update_ptr = Update_pokey_sound_mz; +#ifdef VOL_ONLY_SOUND + POKEYSND_UpdateVolOnly = Update_vol_only_sound_mz; +#endif + +#ifdef VOL_ONLY_SOUND + POKEYSND_samp_freq=playback_freq; +#endif /* VOL_ONLY_SOUND */ + + POKEYSND_Process_ptr = (flags & POKEYSND_BIT16) ? mzpokeysnd_process_16 : mzpokeysnd_process_8; + + pokey_frq = (int)(((double)pokey_frq_ideal/POKEYSND_playback_freq) + 0.5) + * POKEYSND_playback_freq; + filter_size = remez_filter_table((double)POKEYSND_playback_freq/pokey_frq, + &cutoff, quality); + audible_frq = (int ) (cutoff * pokey_frq); + + build_poly4(); + build_poly5(); + build_poly9(); + build_poly17(); + + if (clear_regs) + { + ResetPokeyState(pokey_states); + } + +#ifdef SYNCHRONIZED_SOUND + init_syncsound(); +#endif + volume.s8 = POKEYSND_volume * 0xff / 256.0; + volume.s16 = POKEYSND_volume * 0xffff / 256.0; + + return 0; /* OK */ +} + + +static 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; +} + +static 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; +} + +static void Update_readout_2(PokeyState* ps) +{ + if(ps->c2vo) + ps->readout_2 = readout2_vo; + else + ps->readout_2 = readout2_normal; +} + +static void Update_readout_3(PokeyState* ps) +{ + if(ps->c3vo) + ps->readout_3 = readout3_vo; + else + ps->readout_3 = readout3_normal; +} + +static 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; + } + } +} + +static 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; + } + } +} + +static 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; + } + } +} + +static 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; + } + } +} + +static 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; + } +} + +static 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; +} + +static 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; + } +} + +static 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; +} + +static 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 */ +static void Update_skctl(PokeyState* ps, unsigned char val) +{ + ps->skctl = val; +} + +/* if using nonlinear mixing, don't stop ultrasounds */ +#ifdef NONLINEAR_MIXING +static void Update_c0stop(PokeyState* ps) +{ + ps->outvol_0 = ps->readout_0(ps); +} +static void Update_c1stop(PokeyState* ps) +{ + ps->outvol_1 = ps->readout_1(ps); +} +static void Update_c2stop(PokeyState* ps) +{ + ps->outvol_2 = ps->readout_2(ps); +} +static void Update_c3stop(PokeyState* ps) +{ + ps->outvol_3 = ps->readout_3(ps); +} +#else +static void Update_c0stop(PokeyState* ps) +{ + int lim = pokey_frq/2/audible_frq; + + 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; +} + +static void Update_c1stop(PokeyState* ps) +{ + int lim = pokey_frq/2/audible_frq; + + 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; +} + +static void Update_c2stop(PokeyState* ps) +{ + int lim = pokey_frq/2/audible_frq; + + 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; +} + +static void Update_c3stop(PokeyState* ps) +{ + int lim = pokey_frq/2/audible_frq; + 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 */ +/* */ +/*****************************************************************************/ +static void Update_pokey_sound_mz(UWORD addr, UBYTE val, UBYTE gain) +{ + PokeyState* ps = pokey_states; + + 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 + +static void mzpokeysnd_process_8(void* sndbuffer, int sndn) +{ + int i; + int nsam = sndn; + UBYTE *buffer = (UBYTE *) sndbuffer; + + /* if there are two pokeys, then the signal is stereo + we assume even sndn */ + while(nsam >= 1) + { +#ifdef VOL_ONLY_SOUND + if( POKEYSND_sampbuf_rptr!=POKEYSND_sampbuf_ptr ) + { int l; + if( POKEYSND_sampbuf_cnt[POKEYSND_sampbuf_rptr]>0 ) + POKEYSND_sampbuf_cnt[POKEYSND_sampbuf_rptr]-=1280; + while( (l=POKEYSND_sampbuf_cnt[POKEYSND_sampbuf_rptr])<=0 ) + { POKEYSND_sampout=POKEYSND_sampbuf_val[POKEYSND_sampbuf_rptr]; + POKEYSND_sampbuf_rptr++; + if( POKEYSND_sampbuf_rptr>=POKEYSND_SAMPBUF_MAX ) + POKEYSND_sampbuf_rptr=0; + if( POKEYSND_sampbuf_rptr!=POKEYSND_sampbuf_ptr ) + { + POKEYSND_sampbuf_cnt[POKEYSND_sampbuf_rptr]+=l; + } + else break; + } + } +#endif + +#ifdef VOL_ONLY_SOUND + buffer[0] = (UBYTE)floor((generate_sample(pokey_states) + POKEYSND_sampout) + * (255.0 / 2 / MAX_SAMPLE / 4 * M_PI * 0.95) + 128 + 0.5 + 0.5 * rand() / RAND_MAX - 0.25); +#else + buffer[0] = (UBYTE)floor(generate_sample(pokey_states) + * (255.0 / 2 / MAX_SAMPLE / 4 * M_PI * 0.95) + 128 + 0.5 + 0.5 * rand() / RAND_MAX - 0.25); +#endif + buffer += 1; + nsam -= 1; + } +} + +static void mzpokeysnd_process_16(void* sndbuffer, int sndn) +{ + int i; + int nsam = sndn; + SWORD *buffer = (SWORD *) sndbuffer; + + /* if there are two pokeys, then the signal is stereo + we assume even sndn */ + while(nsam >= (int) 1) + { +#ifdef VOL_ONLY_SOUND + if( POKEYSND_sampbuf_rptr!=POKEYSND_sampbuf_ptr ) + { int l; + if( POKEYSND_sampbuf_cnt[POKEYSND_sampbuf_rptr]>0 ) + POKEYSND_sampbuf_cnt[POKEYSND_sampbuf_rptr]-=1280; + while( (l=POKEYSND_sampbuf_cnt[POKEYSND_sampbuf_rptr])<=0 ) + { POKEYSND_sampout=POKEYSND_sampbuf_val[POKEYSND_sampbuf_rptr]; + POKEYSND_sampbuf_rptr++; + if( POKEYSND_sampbuf_rptr>=POKEYSND_SAMPBUF_MAX ) + POKEYSND_sampbuf_rptr=0; + if( POKEYSND_sampbuf_rptr!=POKEYSND_sampbuf_ptr ) + { + POKEYSND_sampbuf_cnt[POKEYSND_sampbuf_rptr]+=l; + } + else break; + } + } +#endif +#ifdef VOL_ONLY_SOUND + buffer[0] = (SWORD)floor((generate_sample(pokey_states) + POKEYSND_sampout) + * (65535.0 / 2 / MAX_SAMPLE / 4 * M_PI * 0.95) + 0.5 + 0.5 * rand() / RAND_MAX - 0.25); +#else + buffer[0] = (SWORD)floor(generate_sample(pokey_states) + * (65535.0 / 2 / MAX_SAMPLE / 4 * M_PI * 0.95) + 0.5 + 0.5 * rand() / RAND_MAX - 0.25); +#endif + buffer += 1; + nsam -= 1; + } +} + +#ifdef SYNCHRONIZED_SOUND +static void generate_sync(unsigned int num_ticks) +{ + double new_samp_pos; + unsigned int ticks; + UBYTE *buffer = POKEYSND_process_buffer + POKEYSND_process_buffer_fill; + UBYTE *buffer_end = POKEYSND_process_buffer + POKEYSND_process_buffer_length; + unsigned int i; + + for (;;) { + double int_part; + new_samp_pos = samp_pos + ticks_per_sample; + new_samp_pos = modf(new_samp_pos, &int_part); + ticks = (unsigned int)int_part; + if (ticks > num_ticks) { + samp_pos -= num_ticks; + break; + } + if (buffer >= buffer_end) + break; + + samp_pos = new_samp_pos; + num_ticks -= ticks; + + /* advance pokey to the new position and produce a sample */ + advance_ticks(pokey_states, ticks); + if (POKEYSND_snd_flags & POKEYSND_BIT16) { + *((SWORD *)buffer) = (SWORD)floor( + interp_read_resam_all(pokey_states, samp_pos) + * (volume.s16 / 2 / MAX_SAMPLE / 4 * M_PI * 0.95) + + 0.5 + 0.5 * rand() / RAND_MAX - 0.25 + ); + buffer += 2; + } + else + *buffer++ = (UBYTE)floor( + interp_read_resam_all(pokey_states, samp_pos) + * (volume.s8 / 2 / MAX_SAMPLE / 4 * M_PI * 0.95) + + 128 + 0.5 + 0.5 * rand() / RAND_MAX - 0.25 + ); + } + + POKEYSND_process_buffer_fill = buffer - POKEYSND_process_buffer; + if (num_ticks > 0) { + /* remaining ticks */ + advance_ticks(pokey_states, num_ticks); + } +} +#endif /* SYNCHRONIZED_SOUND */ + + +#ifdef VOL_ONLY_SOUND +static void Update_vol_only_sound_mz( void ) +{ +} +#endif diff --git a/src/engine/platform/sound/pokey/mzpokeysnd.h b/src/engine/platform/sound/pokey/mzpokeysnd.h new file mode 100644 index 00000000..11e3019e --- /dev/null +++ b/src/engine/platform/sound/pokey/mzpokeysnd.h @@ -0,0 +1,11 @@ +#ifndef MZPOKEYSND_H_ +#define MZPOKEYSND_H_ + +int MZPOKEYSND_Init(size_t freq17, + int playback_freq, + int flags, + int quality + , int clear_regs + ); + +#endif /* MZPOKEYSND_H_ */ From 75b0ed7af1a299317936c2bc54fbca895e99a9de Mon Sep 17 00:00:00 2001 From: cam900 Date: Mon, 19 Dec 2022 18:07:43 +0900 Subject: [PATCH 13/93] Add some PC-88 presets, with external soundcard(s) Added AY clock (1.9968MHz) for PC-88 reference: http://mydocuments.g2.xrea.com/html/p8/soundinfo.html, https://www.dtmstation.com/archives/52016817.html --- src/engine/fileOps.cpp | 3 + src/engine/platform/ay.cpp | 3 + src/gui/presets.cpp | 193 +++++++++++++++++++++++++++++++++++++ 3 files changed, 199 insertions(+) diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index b21d3a0d..a140c424 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -1167,6 +1167,9 @@ void DivEngine::convertOldFlags(unsigned int oldFlags, DivConfig& newFlags, DivS case 14: if (sys==DIV_SYSTEM_AY8910) newFlags.set("clockSel",14); break; + case 15: + if (sys==DIV_SYSTEM_AY8910) newFlags.set("clockSel",15); + break; } if (sys==DIV_SYSTEM_AY8910) switch ((oldFlags>>4)&3) { case 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/gui/presets.cpp b/src/gui/presets.cpp index 29c64f42..54a2476c 100644 --- a/src/gui/presets.cpp +++ b/src/gui/presets.cpp @@ -411,6 +411,199 @@ 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_OPN, 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_OPN_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_PC98, 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_PC98_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_OPN, 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_OPN_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_OPN, 64, 0, "clockSel=4"), // internal + CH(DIV_SYSTEM_OPN, 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_OPN_EXT, 64, 0, "clockSel=4"), // internal + CH(DIV_SYSTEM_OPN, 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_OPN, 64, 0, "clockSel=4"), // internal + CH(DIV_SYSTEM_OPN_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_OPN_EXT, 64, 0, "clockSel=4"), // internal + CH(DIV_SYSTEM_OPN_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_OPN, 64, 0, "clockSel=4"), // internal + CH(DIV_SYSTEM_PC98, 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_OPN_EXT, 64, 0, "clockSel=4"), // internal + CH(DIV_SYSTEM_PC98, 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_OPN, 64, 0, "clockSel=4"), // internal + CH(DIV_SYSTEM_PC98_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_OPN_EXT, 64, 0, "clockSel=4"), // internal + CH(DIV_SYSTEM_PC98_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_OPN, 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_OPN_EXT, 64, 0, "clockSel=4"), // internal + CH(DIV_SYSTEM_YM2151, 64, 0, "clockSel=2") // external; 4.0000MHz + } + ); + ENTRY( + "NEC PC-8801FA (with PC-8801-11)", { + CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1"), + CH(DIV_SYSTEM_PC98, 64, 0, "clockSel=1"), // internal + CH(DIV_SYSTEM_OPN, 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_PC98_EXT, 64, 0, "clockSel=1"), // internal + CH(DIV_SYSTEM_OPN, 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_PC98, 64, 0, "clockSel=1"), // internal + CH(DIV_SYSTEM_OPN_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_PC98_EXT, 64, 0, "clockSel=1"), // internal + CH(DIV_SYSTEM_OPN_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_PC98, 64, 0, "clockSel=1"), // internal + CH(DIV_SYSTEM_PC98, 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_PC98_EXT, 64, 0, "clockSel=1"), // internal + CH(DIV_SYSTEM_PC98, 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_PC98, 64, 0, "clockSel=1"), // internal + CH(DIV_SYSTEM_PC98_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_PC98_EXT, 64, 0, "clockSel=1"), // internal + CH(DIV_SYSTEM_PC98_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_PC98, 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_PC98_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 From 300d817795db0941a882e8b8f89d5ddaa9a1d12c Mon Sep 17 00:00:00 2001 From: cam900 Date: Mon, 19 Dec 2022 18:11:56 +0900 Subject: [PATCH 14/93] Update previous commit --- src/gui/presets.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/gui/presets.cpp b/src/gui/presets.cpp index 54a2476c..818318c8 100644 --- a/src/gui/presets.cpp +++ b/src/gui/presets.cpp @@ -534,6 +534,22 @@ void FurnaceGUI::initSystemPresets() { 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_PC98, 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_PC98_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"), From 8aa5ff099ca708e9a39b921b8b9adcabcd699d9e Mon Sep 17 00:00:00 2001 From: Waverider <33787286+liaminventions@users.noreply.github.com> Date: Mon, 19 Dec 2022 13:50:39 -0500 Subject: [PATCH 15/93] Update about.cpp --- src/gui/about.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gui/about.cpp b/src/gui/about.cpp index b840d7c7..e685b50a 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", From 222abe7e76f4332c1456b04fde7f4363bbae5eea Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 19 Dec 2022 13:59:24 -0500 Subject: [PATCH 16/93] don't worry about it --- src/engine/fileOps.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index a140c424..b21d3a0d 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -1167,9 +1167,6 @@ void DivEngine::convertOldFlags(unsigned int oldFlags, DivConfig& newFlags, DivS case 14: if (sys==DIV_SYSTEM_AY8910) newFlags.set("clockSel",14); break; - case 15: - if (sys==DIV_SYSTEM_AY8910) newFlags.set("clockSel",15); - break; } if (sys==DIV_SYSTEM_AY8910) switch ((oldFlags>>4)&3) { case 0: From 920b8d6086156c3d12d46a38ae75eebaa56642df Mon Sep 17 00:00:00 2001 From: Waverider <33787286+liaminventions@users.noreply.github.com> Date: Mon, 19 Dec 2022 14:05:33 -0500 Subject: [PATCH 17/93] hopefully this is my last demo song --- demos/specs2/object.fur | Bin 0 -> 10991 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 demos/specs2/object.fur diff --git a/demos/specs2/object.fur b/demos/specs2/object.fur new file mode 100644 index 0000000000000000000000000000000000000000..6ae76eea097b85491e2d168b24e0e3b7b3e2e509 GIT binary patch literal 10991 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 literal 0 HcmV?d00001 From 71705123cb2858ecc0bbd5dd6eac83ac037c6ad6 Mon Sep 17 00:00:00 2001 From: Waverider <33787286+liaminventions@users.noreply.github.com> Date: Mon, 19 Dec 2022 14:07:31 -0500 Subject: [PATCH 18/93] fix --- demos/specs2/object.fur | Bin 10991 -> 10073 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/demos/specs2/object.fur b/demos/specs2/object.fur index 6ae76eea097b85491e2d168b24e0e3b7b3e2e509..c9a6e8ea4b2bb333dbd12847d24a7eae652612a8 100644 GIT binary patch literal 10073 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 literal 10991 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 From a8071a3c9f9473fb3d48ac27a497b5e2b321587e Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 19 Dec 2022 14:18:04 -0500 Subject: [PATCH 19/93] update mzpokeysnd --- src/engine/platform/sound/pokey/mzpokeysnd.c | 19 ++--- src/engine/platform/sound/pokey/mzpokeysnd.h | 2 + src/engine/platform/sound/pokey/pokey.h | 89 ++++++++++++++++++++ 3 files changed, 97 insertions(+), 13 deletions(-) create mode 100644 src/engine/platform/sound/pokey/pokey.h diff --git a/src/engine/platform/sound/pokey/mzpokeysnd.c b/src/engine/platform/sound/pokey/mzpokeysnd.c index 0e17f0bd..1dee5422 100644 --- a/src/engine/platform/sound/pokey/mzpokeysnd.c +++ b/src/engine/platform/sound/pokey/mzpokeysnd.c @@ -25,17 +25,10 @@ // additional modifications for Furnace by tildearrow #define _USE_MATH_DEFINES -#include "config.h" #include #include -#include "asap_internal.h" -#include "atari.h" #include "mzpokeysnd.h" -#include "pokeysnd.h" -#include "remez.h" -#include "antic.h" -#include "gtia.h" #define CONSOLE_VOL 8 #ifdef NONLINEAR_MIXING @@ -1221,9 +1214,9 @@ found: return size; } -static void mzpokeysnd_process_8(void* sndbuffer, int sndn); -static void mzpokeysnd_process_16(void* sndbuffer, int sndn); -static void Update_pokey_sound_mz(UWORD addr, UBYTE val, UBYTE gain); +void mzpokeysnd_process_8(void* sndbuffer, int sndn); +void mzpokeysnd_process_16(void* sndbuffer, int sndn); +void Update_pokey_sound_mz(UWORD addr, UBYTE val, UBYTE gain); #ifdef VOL_ONLY_SOUND static void Update_vol_only_sound_mz( void ); #endif @@ -1857,7 +1850,7 @@ static void Update_c3stop(PokeyState* ps) /* Outputs: Adjusts local globals - no return value */ /* */ /*****************************************************************************/ -static void Update_pokey_sound_mz(UWORD addr, UBYTE val, UBYTE gain) +void Update_pokey_sound_mz(UWORD addr, UBYTE val, UBYTE gain) { PokeyState* ps = pokey_states; @@ -2162,7 +2155,7 @@ static void Update_pokey_sound_mz(UWORD addr, UBYTE val, UBYTE gain) #define MAX_SAMPLE 152 -static void mzpokeysnd_process_8(void* sndbuffer, int sndn) +void mzpokeysnd_process_8(void* sndbuffer, int sndn) { int i; int nsam = sndn; @@ -2203,7 +2196,7 @@ static void mzpokeysnd_process_8(void* sndbuffer, int sndn) } } -static void mzpokeysnd_process_16(void* sndbuffer, int sndn) +void mzpokeysnd_process_16(void* sndbuffer, int sndn) { int i; int nsam = sndn; diff --git a/src/engine/platform/sound/pokey/mzpokeysnd.h b/src/engine/platform/sound/pokey/mzpokeysnd.h index 11e3019e..908a1dd1 100644 --- a/src/engine/platform/sound/pokey/mzpokeysnd.h +++ b/src/engine/platform/sound/pokey/mzpokeysnd.h @@ -1,6 +1,8 @@ #ifndef MZPOKEYSND_H_ #define MZPOKEYSND_H_ +#include + int MZPOKEYSND_Init(size_t freq17, int playback_freq, int flags, diff --git a/src/engine/platform/sound/pokey/pokey.h b/src/engine/platform/sound/pokey/pokey.h new file mode 100644 index 00000000..777573da --- /dev/null +++ b/src/engine/platform/sound/pokey/pokey.h @@ -0,0 +1,89 @@ +#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 + +/* structures to hold the 9 pokey control bytes */ +extern UBYTE POKEY_AUDF[4 * POKEY_MAXPOKEYS]; /* AUDFx (D200, D202, D204, D206) */ +extern UBYTE POKEY_AUDC[4 * POKEY_MAXPOKEYS]; /* AUDCx (D201, D203, D205, D207) */ +extern UBYTE POKEY_AUDCTL[POKEY_MAXPOKEYS]; /* AUDCTL (D208) */ + +extern int POKEY_DivNIRQ[4], POKEY_DivNMax[4]; +extern int POKEY_Base_mult[POKEY_MAXPOKEYS]; /* selects either 64Khz or 15Khz clock mult */ + +extern UBYTE POKEY_poly9_lookup[POKEY_POLY9_SIZE]; +extern UBYTE POKEY_poly17_lookup[16385]; + +#endif /* POKEY_H_ */ From aa2020154b0cfbc20b86cfd95015184eda9ef3dd Mon Sep 17 00:00:00 2001 From: Waverider <33787286+liaminventions@users.noreply.github.com> Date: Mon, 19 Dec 2022 14:28:05 -0500 Subject: [PATCH 20/93] Add files via upload --- demos/amiga/furnace0.6crk.fur | Bin 0 -> 6076 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 demos/amiga/furnace0.6crk.fur diff --git a/demos/amiga/furnace0.6crk.fur b/demos/amiga/furnace0.6crk.fur new file mode 100644 index 0000000000000000000000000000000000000000..0966f17b08431176e2689ce0bb5ab8f84ce96fc0 GIT binary patch literal 6076 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 literal 0 HcmV?d00001 From 139ac97144f1226f525fcfa61b91f25b227ae3b4 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 19 Dec 2022 16:39:10 -0500 Subject: [PATCH 21/93] more mzpokeysnd changes --- src/engine/platform/sound/pokey/mzpokeysnd.c | 276 ++++++------------- 1 file changed, 80 insertions(+), 196 deletions(-) diff --git a/src/engine/platform/sound/pokey/mzpokeysnd.c b/src/engine/platform/sound/pokey/mzpokeysnd.c index 1dee5422..e5c2a6e0 100644 --- a/src/engine/platform/sound/pokey/mzpokeysnd.c +++ b/src/engine/platform/sound/pokey/mzpokeysnd.c @@ -394,55 +394,14 @@ static void ResetPokeyState(PokeyState* ps) ps->speaker = 0; } - -static double read_resam_all(PokeyState* ps) -{ - int i = ps->qebeg; - qev_t avol,bvol; - double sum; - - if(ps->qebeg == ps->qeend) - { - return ps->ovola * filter_data[0]; /* if no events in the queue */ - } - - avol = ps->ovola; - sum = 0; - - /* Separate two loop cases, for wrap-around and without */ - if(ps->qeend < ps->qebeg) /* With wrap */ - { - while(iqev[i]; - sum += (avol-bvol)*filter_data[ps->curtick - ps->qet[i]]; - avol = bvol; - ++i; - } - i=0; - } - - /* without wrap */ - while(iqeend) - { - bvol = ps->qev[i]; - sum += (avol-bvol)*filter_data[ps->curtick - ps->qet[i]]; - avol = bvol; - ++i; - } - - sum += avol*filter_data[0]; - return sum; -} - #ifdef SYNCHRONIZED_SOUND /* linear interpolation of filter data */ static double interp_filter_data(int pos, double frac) { - if (pos+1 >= filter_size) { - return 0.0; - } - return (frac)*filter_data[pos+1]+(1-frac)*(filter_data[pos]-filter_data[filter_size-1]); + if (pos+1 >= filter_size) { + return 0.0; + } + return (frac)*filter_data[pos+1]+(1-frac)*(filter_data[pos]-filter_data[filter_size-1]); } /* returns the filtered output sample value using an interpolated filter */ @@ -490,15 +449,6 @@ static double interp_read_resam_all(PokeyState* ps, double frac) } #endif /* SYNCHRONIZED_SOUND */ -static void add_change(PokeyState* ps, qev_t a) -{ - ps->qev[ps->qeend] = a; - ps->qet[ps->qeend] = ps->curtick; /*0;*/ - ++ps->qeend; - if(ps->qeend >= filter_size) - ps->qeend = 0; -} - static void bump_qe_subticks(PokeyState* ps, int subticks) { /* Remove too old events from the queue while bumping */ @@ -507,12 +457,12 @@ static void bump_qe_subticks(PokeyState* ps, int subticks) static const int tickoverflowlimit = 1000000000; ps->curtick += subticks; if (ps->curtick > tickoverflowlimit) { - ps->curtick -= tickoverflowlimit/2; - for (i=0; iqet[i] > tickoverflowlimit/2) { - ps->qet[i] -= tickoverflowlimit/2; - } - } + ps->curtick -= tickoverflowlimit/2; + for (i=0; iqet[i] > tickoverflowlimit/2) { + ps->qet[i] -= tickoverflowlimit/2; + } + } } @@ -528,9 +478,9 @@ static void bump_qe_subticks(PokeyState* ps, int subticks) if(ps->qebeg >= filter_size) ps->qebeg = 0; } - else { - return; - } + else { + return; + } ++i; } i=0; @@ -546,9 +496,9 @@ static void bump_qe_subticks(PokeyState* ps, int subticks) if(ps->qebeg >= filter_size) ps->qebeg = 0; } - else { - return; - } + else { + return; + } ++i; } } @@ -571,41 +521,41 @@ static void build_poly4(void) static void build_poly5(void) { - unsigned char c; - unsigned char i; - unsigned char poly5 = 1; + 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; - } + 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; + 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; - } + 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; + 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; - } + for(i = 0; i < 511; i++) { + poly9tbl[i] = (unsigned char) poly9; + c = ((poly9 >> 3) ^ (poly9 >> 8)) & 1; + poly9 = ((poly9 << 1) & 511) + c; + } } static void advance_polies(PokeyState* ps, int tacts) @@ -939,7 +889,6 @@ static void advance_ticks(PokeyState* ps, int ticks) if(outvol_new != ps->outvol_all) { ps->outvol_all = outvol_new; - add_change(ps, outvol_new); } } @@ -1111,7 +1060,6 @@ static void advance_ticks(PokeyState* ps, int ticks) if(outvol_new != ps->outvol_all) { ps->outvol_all = outvol_new; - add_change(ps, outvol_new); } } } @@ -1123,7 +1071,7 @@ static double generate_sample(PokeyState* ps) subticks = (subticks+pokey_frq)%POKEYSND_playback_freq;*/ advance_ticks(ps, pokey_frq/POKEYSND_playback_freq); - return read_resam_all(ps); + return ps->outvol_all; } /****************************************** @@ -1136,8 +1084,8 @@ static int remez_filter_table(double resamp_rate, /* output_rate/input_rate */ int i; static const int orders[] = {600, 800, 1000, 1200}; static const struct { - int stop; /* stopband ripple */ - double weight; /* stopband weight */ + int stop; /* stopband ripple */ + double weight; /* stopband weight */ double twidth[sizeof(orders)/sizeof(orders[0])]; } paramtab[] = { @@ -1163,9 +1111,9 @@ static int remez_filter_table(double resamp_rate, /* output_rate/input_rate */ for (order = 0; order < (int) (sizeof(orders)/sizeof(orders[0])); order++) { if ((*cutoff - paramtab[ripple].twidth[order]) - > passtab[quality] * 0.5 * resamp_rate) - /* transition width OK */ - goto found; + > passtab[quality] * 0.5 * resamp_rate) + /* transition width OK */ + goto found; } } @@ -1217,9 +1165,6 @@ found: void mzpokeysnd_process_8(void* sndbuffer, int sndn); void mzpokeysnd_process_16(void* sndbuffer, int sndn); void Update_pokey_sound_mz(UWORD addr, UBYTE val, UBYTE gain); -#ifdef VOL_ONLY_SOUND -static void Update_vol_only_sound_mz( void ); -#endif /*****************************************************************************/ /* Module: MZPOKEYSND_Init() */ @@ -1258,39 +1203,31 @@ int MZPOKEYSND_Init(size_t freq17, int playback_freq, snd_quality = quality; POKEYSND_Update_ptr = Update_pokey_sound_mz; -#ifdef VOL_ONLY_SOUND - POKEYSND_UpdateVolOnly = Update_vol_only_sound_mz; -#endif - -#ifdef VOL_ONLY_SOUND - POKEYSND_samp_freq=playback_freq; -#endif /* VOL_ONLY_SOUND */ - - POKEYSND_Process_ptr = (flags & POKEYSND_BIT16) ? mzpokeysnd_process_16 : mzpokeysnd_process_8; + POKEYSND_Process_ptr = (flags & POKEYSND_BIT16) ? mzpokeysnd_process_16 : mzpokeysnd_process_8; pokey_frq = (int)(((double)pokey_frq_ideal/POKEYSND_playback_freq) + 0.5) * POKEYSND_playback_freq; - filter_size = remez_filter_table((double)POKEYSND_playback_freq/pokey_frq, - &cutoff, quality); - audible_frq = (int ) (cutoff * pokey_frq); + filter_size = remez_filter_table((double)POKEYSND_playback_freq/pokey_frq, + &cutoff, quality); + audible_frq = (int ) (cutoff * pokey_frq); build_poly4(); build_poly5(); build_poly9(); build_poly17(); - if (clear_regs) - { - ResetPokeyState(pokey_states); - } + if (clear_regs) + { + ResetPokeyState(pokey_states); + } #ifdef SYNCHRONIZED_SOUND - init_syncsound(); + init_syncsound(); #endif volume.s8 = POKEYSND_volume * 0xff / 256.0; volume.s16 = POKEYSND_volume * 0xffff / 256.0; - return 0; /* OK */ + return 0; /* OK */ } @@ -2165,32 +2102,8 @@ void mzpokeysnd_process_8(void* sndbuffer, int sndn) we assume even sndn */ while(nsam >= 1) { -#ifdef VOL_ONLY_SOUND - if( POKEYSND_sampbuf_rptr!=POKEYSND_sampbuf_ptr ) - { int l; - if( POKEYSND_sampbuf_cnt[POKEYSND_sampbuf_rptr]>0 ) - POKEYSND_sampbuf_cnt[POKEYSND_sampbuf_rptr]-=1280; - while( (l=POKEYSND_sampbuf_cnt[POKEYSND_sampbuf_rptr])<=0 ) - { POKEYSND_sampout=POKEYSND_sampbuf_val[POKEYSND_sampbuf_rptr]; - POKEYSND_sampbuf_rptr++; - if( POKEYSND_sampbuf_rptr>=POKEYSND_SAMPBUF_MAX ) - POKEYSND_sampbuf_rptr=0; - if( POKEYSND_sampbuf_rptr!=POKEYSND_sampbuf_ptr ) - { - POKEYSND_sampbuf_cnt[POKEYSND_sampbuf_rptr]+=l; - } - else break; - } - } -#endif - -#ifdef VOL_ONLY_SOUND - buffer[0] = (UBYTE)floor((generate_sample(pokey_states) + POKEYSND_sampout) - * (255.0 / 2 / MAX_SAMPLE / 4 * M_PI * 0.95) + 128 + 0.5 + 0.5 * rand() / RAND_MAX - 0.25); -#else buffer[0] = (UBYTE)floor(generate_sample(pokey_states) * (255.0 / 2 / MAX_SAMPLE / 4 * M_PI * 0.95) + 128 + 0.5 + 0.5 * rand() / RAND_MAX - 0.25); -#endif buffer += 1; nsam -= 1; } @@ -2206,31 +2119,8 @@ void mzpokeysnd_process_16(void* sndbuffer, int sndn) we assume even sndn */ while(nsam >= (int) 1) { -#ifdef VOL_ONLY_SOUND - if( POKEYSND_sampbuf_rptr!=POKEYSND_sampbuf_ptr ) - { int l; - if( POKEYSND_sampbuf_cnt[POKEYSND_sampbuf_rptr]>0 ) - POKEYSND_sampbuf_cnt[POKEYSND_sampbuf_rptr]-=1280; - while( (l=POKEYSND_sampbuf_cnt[POKEYSND_sampbuf_rptr])<=0 ) - { POKEYSND_sampout=POKEYSND_sampbuf_val[POKEYSND_sampbuf_rptr]; - POKEYSND_sampbuf_rptr++; - if( POKEYSND_sampbuf_rptr>=POKEYSND_SAMPBUF_MAX ) - POKEYSND_sampbuf_rptr=0; - if( POKEYSND_sampbuf_rptr!=POKEYSND_sampbuf_ptr ) - { - POKEYSND_sampbuf_cnt[POKEYSND_sampbuf_rptr]+=l; - } - else break; - } - } -#endif -#ifdef VOL_ONLY_SOUND - buffer[0] = (SWORD)floor((generate_sample(pokey_states) + POKEYSND_sampout) - * (65535.0 / 2 / MAX_SAMPLE / 4 * M_PI * 0.95) + 0.5 + 0.5 * rand() / RAND_MAX - 0.25); -#else buffer[0] = (SWORD)floor(generate_sample(pokey_states) * (65535.0 / 2 / MAX_SAMPLE / 4 * M_PI * 0.95) + 0.5 + 0.5 * rand() / RAND_MAX - 0.25); -#endif buffer += 1; nsam -= 1; } @@ -2239,26 +2129,26 @@ void mzpokeysnd_process_16(void* sndbuffer, int sndn) #ifdef SYNCHRONIZED_SOUND static void generate_sync(unsigned int num_ticks) { - double new_samp_pos; - unsigned int ticks; - UBYTE *buffer = POKEYSND_process_buffer + POKEYSND_process_buffer_fill; - UBYTE *buffer_end = POKEYSND_process_buffer + POKEYSND_process_buffer_length; - unsigned int i; + double new_samp_pos; + unsigned int ticks; + UBYTE *buffer = POKEYSND_process_buffer + POKEYSND_process_buffer_fill; + UBYTE *buffer_end = POKEYSND_process_buffer + POKEYSND_process_buffer_length; + unsigned int i; - for (;;) { - double int_part; - new_samp_pos = samp_pos + ticks_per_sample; - new_samp_pos = modf(new_samp_pos, &int_part); - ticks = (unsigned int)int_part; - if (ticks > num_ticks) { - samp_pos -= num_ticks; - break; - } - if (buffer >= buffer_end) - break; + for (;;) { + double int_part; + new_samp_pos = samp_pos + ticks_per_sample; + new_samp_pos = modf(new_samp_pos, &int_part); + ticks = (unsigned int)int_part; + if (ticks > num_ticks) { + samp_pos -= num_ticks; + break; + } + if (buffer >= buffer_end) + break; - samp_pos = new_samp_pos; - num_ticks -= ticks; + samp_pos = new_samp_pos; + num_ticks -= ticks; /* advance pokey to the new position and produce a sample */ advance_ticks(pokey_states, ticks); @@ -2276,19 +2166,13 @@ static void generate_sync(unsigned int num_ticks) * (volume.s8 / 2 / MAX_SAMPLE / 4 * M_PI * 0.95) + 128 + 0.5 + 0.5 * rand() / RAND_MAX - 0.25 ); - } + } - POKEYSND_process_buffer_fill = buffer - POKEYSND_process_buffer; - if (num_ticks > 0) { - /* remaining ticks */ - advance_ticks(pokey_states, num_ticks); - } + POKEYSND_process_buffer_fill = buffer - POKEYSND_process_buffer; + if (num_ticks > 0) { + /* remaining ticks */ + advance_ticks(pokey_states, num_ticks); + } } #endif /* SYNCHRONIZED_SOUND */ - -#ifdef VOL_ONLY_SOUND -static void Update_vol_only_sound_mz( void ) -{ -} -#endif From 112d773544c0b2c56681cb18e41a8294eec02e17 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 19 Dec 2022 16:46:19 -0500 Subject: [PATCH 22/93] and more mzpokeysnd changes --- src/engine/platform/sound/pokey/mzpokeysnd.c | 218 ------------------- 1 file changed, 218 deletions(-) diff --git a/src/engine/platform/sound/pokey/mzpokeysnd.c b/src/engine/platform/sound/pokey/mzpokeysnd.c index e5c2a6e0..2a629b00 100644 --- a/src/engine/platform/sound/pokey/mzpokeysnd.c +++ b/src/engine/platform/sound/pokey/mzpokeysnd.c @@ -84,11 +84,6 @@ typedef double qev_t; typedef unsigned char qev_t; #endif -#ifdef SYNCHRONIZED_SOUND -static double ticks_per_sample; -static double samp_pos; -#endif /* SYNCHRONIZED_SOUND */ - /* State variables for single Pokey Chip */ typedef struct stPokeyState { @@ -99,13 +94,6 @@ typedef struct stPokeyState int poly17pos; int poly9pos; - /* Change queue */ - qev_t ovola; - int qet[1322]; /* maximal length of filter */ - qev_t qev[1322]; - int qebeg; - int qeend; - /* Main divider (64khz/15khz) */ int mdivk; /* 28 for 64khz, 114 for 15khz */ @@ -228,11 +216,6 @@ typedef struct stPokeyState int vol3; int outvol_3; - - /* GTIA speaker */ - - int speaker; - } PokeyState; // TODO: make this fully struct-ized @@ -265,11 +248,6 @@ static void ResetPokeyState(PokeyState* ps) ps->poly9pos = 0; ps->poly17pos = 0; - /* Change queue */ - ps->ovola = 0; - ps->qebeg = 0; - ps->qeend = 0; - /* Global Pokey controls */ ps->mdivk = 28; @@ -389,122 +367,8 @@ static void ResetPokeyState(PokeyState* ps) ps->vol3 = 0; ps->outvol_3 = 0; - - /* GTIA speaker */ - ps->speaker = 0; } -#ifdef SYNCHRONIZED_SOUND -/* linear interpolation of filter data */ -static double interp_filter_data(int pos, double frac) -{ - if (pos+1 >= filter_size) { - return 0.0; - } - return (frac)*filter_data[pos+1]+(1-frac)*(filter_data[pos]-filter_data[filter_size-1]); -} - -/* returns the filtered output sample value using an interpolated filter */ -/* frac is the fractional distance of the output sample point between - * input sample values */ -static double interp_read_resam_all(PokeyState* ps, double frac) -{ - int i = ps->qebeg; - qev_t avol,bvol; - double sum; - - if (ps->qebeg == ps->qeend) - { - return ps->ovola * interp_filter_data(0,frac); /* if no events in the queue */ - } - - avol = ps->ovola; - sum = 0; - - /* Separate two loop cases, for wrap-around and without */ - if (ps->qeend < ps->qebeg) /* With wrap */ - { - while (i < filter_size) - { - bvol = ps->qev[i]; - sum += (avol-bvol)*interp_filter_data(ps->curtick - ps->qet[i],frac); - avol = bvol; - ++i; - } - i = 0; - } - - /* without wrap */ - while (i < ps->qeend) - { - bvol = ps->qev[i]; - sum += (avol-bvol)*interp_filter_data(ps->curtick - ps->qet[i],frac); - avol = bvol; - ++i; - } - - sum += avol*interp_filter_data(0,frac); - - return sum; -} -#endif /* SYNCHRONIZED_SOUND */ - -static void bump_qe_subticks(PokeyState* ps, int subticks) -{ - /* Remove too old events from the queue while bumping */ - int i = ps->qebeg; - /* we must avoid curtick overflow in a 32-bit int, will happen in 20 min */ - static const int tickoverflowlimit = 1000000000; - ps->curtick += subticks; - if (ps->curtick > tickoverflowlimit) { - ps->curtick -= tickoverflowlimit/2; - for (i=0; iqet[i] > tickoverflowlimit/2) { - ps->qet[i] -= tickoverflowlimit/2; - } - } - } - - - if(ps->qeend < ps->qebeg) /* Loop with wrap */ - { - while(iqet[i] += subticks;*/ - if(ps->curtick - ps->qet[i] >= filter_size - 1) - { - ps->ovola = ps->qev[i]; - ++ps->qebeg; - if(ps->qebeg >= filter_size) - ps->qebeg = 0; - } - else { - return; - } - ++i; - } - i=0; - } - /* loop without wrap */ - while(iqeend) - { - /*ps->qet[i] += subticks;*/ - if(ps->curtick - ps->qet[i] >= filter_size - 1) - { - ps->ovola = ps->qev[i]; - ++ps->qebeg; - if(ps->qebeg >= filter_size) - ps->qebeg = 0; - } - else { - return; - } - ++i; - } -} - - - static void build_poly4(void) { unsigned char c; @@ -875,16 +739,9 @@ static void advance_ticks(PokeyState* ps, int ticks) { ps->forcero = 0; #ifdef NONLINEAR_MIXING -#ifdef SYNCHRONIZED_SOUND - outvol_new = pokeymix[ps->outvol_0 + ps->outvol_1 + ps->outvol_2 + ps->outvol_3 + ps->speaker]; -#else outvol_new = pokeymix[ps->outvol_0 + ps->outvol_1 + ps->outvol_2 + ps->outvol_3]; -#endif /* SYNCHRONIZED_SOUND */ #else outvol_new = ps->outvol_0 + ps->outvol_1 + ps->outvol_2 + ps->outvol_3; -#ifdef SYNCHRONIZED_SOUND - outvol_new += ps->speaker; -#endif /* SYNCHRONIZED_SOUND */ #endif /* NONLINEAR_MIXING */ if(outvol_new != ps->outvol_all) { @@ -944,7 +801,6 @@ static void advance_ticks(PokeyState* ps, int ticks) #endif advance_polies(ps,ta); - bump_qe_subticks(ps,ta); if(need) { @@ -1046,16 +902,9 @@ static void advance_ticks(PokeyState* ps, int ticks) } #ifdef NONLINEAR_MIXING -#ifdef SYNCHRONIZED_SOUND - outvol_new = pokeymix[ps->outvol_0 + ps->outvol_1 + ps->outvol_2 + ps->outvol_3 + ps->speaker]; -#else outvol_new = pokeymix[ps->outvol_0 + ps->outvol_1 + ps->outvol_2 + ps->outvol_3]; -#endif /* SYNCHRONIZED_SOUND */ #else outvol_new = ps->outvol_0 + ps->outvol_1 + ps->outvol_2 + ps->outvol_3; -#ifdef SYNCHRONIZED_SOUND - outvol_new += ps->speaker; -#endif /* SYNCHRONIZED_SOUND */ #endif /* NONLINEAR_MIXING */ if(outvol_new != ps->outvol_all) { @@ -1180,19 +1029,6 @@ void Update_pokey_sound_mz(UWORD addr, UBYTE val, UBYTE gain); /* */ /*****************************************************************************/ -#ifdef SYNCHRONIZED_SOUND -static void generate_sync(unsigned int num_ticks); - -static void init_syncsound(void) -{ - double samples_per_frame = (double)POKEYSND_playback_freq/(Atari800_tv_mode == Atari800_TV_PAL ? Atari800_FPS_PAL : Atari800_FPS_NTSC); - unsigned int ticks_per_frame = Atari800_tv_mode*114; - ticks_per_sample = (double)ticks_per_frame / samples_per_frame; - samp_pos = 0.0; - POKEYSND_GenerateSync = generate_sync; -} -#endif /* SYNCHRONIZED_SOUND */ - int MZPOKEYSND_Init(size_t freq17, int playback_freq, int flags, int quality , int clear_regs @@ -1221,9 +1057,6 @@ int MZPOKEYSND_Init(size_t freq17, int playback_freq, ResetPokeyState(pokey_states); } -#ifdef SYNCHRONIZED_SOUND - init_syncsound(); -#endif volume.s8 = POKEYSND_volume * 0xff / 256.0; volume.s16 = POKEYSND_volume * 0xffff / 256.0; @@ -2125,54 +1958,3 @@ void mzpokeysnd_process_16(void* sndbuffer, int sndn) nsam -= 1; } } - -#ifdef SYNCHRONIZED_SOUND -static void generate_sync(unsigned int num_ticks) -{ - double new_samp_pos; - unsigned int ticks; - UBYTE *buffer = POKEYSND_process_buffer + POKEYSND_process_buffer_fill; - UBYTE *buffer_end = POKEYSND_process_buffer + POKEYSND_process_buffer_length; - unsigned int i; - - for (;;) { - double int_part; - new_samp_pos = samp_pos + ticks_per_sample; - new_samp_pos = modf(new_samp_pos, &int_part); - ticks = (unsigned int)int_part; - if (ticks > num_ticks) { - samp_pos -= num_ticks; - break; - } - if (buffer >= buffer_end) - break; - - samp_pos = new_samp_pos; - num_ticks -= ticks; - - /* advance pokey to the new position and produce a sample */ - advance_ticks(pokey_states, ticks); - if (POKEYSND_snd_flags & POKEYSND_BIT16) { - *((SWORD *)buffer) = (SWORD)floor( - interp_read_resam_all(pokey_states, samp_pos) - * (volume.s16 / 2 / MAX_SAMPLE / 4 * M_PI * 0.95) - + 0.5 + 0.5 * rand() / RAND_MAX - 0.25 - ); - buffer += 2; - } - else - *buffer++ = (UBYTE)floor( - interp_read_resam_all(pokey_states, samp_pos) - * (volume.s8 / 2 / MAX_SAMPLE / 4 * M_PI * 0.95) - + 128 + 0.5 + 0.5 * rand() / RAND_MAX - 0.25 - ); - } - - POKEYSND_process_buffer_fill = buffer - POKEYSND_process_buffer; - if (num_ticks > 0) { - /* remaining ticks */ - advance_ticks(pokey_states, num_ticks); - } -} -#endif /* SYNCHRONIZED_SOUND */ - From 4ba8c0513e9f8ede5d72e696af528ca654de1b99 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 19 Dec 2022 17:10:45 -0500 Subject: [PATCH 23/93] the final mzpokeysnd change (maybe) it works! now to work on DivPlatformPOKEY... --- CMakeLists.txt | 2 + src/engine/platform/sound/pokey/mzpokeysnd.c | 454 ++++--------------- src/engine/platform/sound/pokey/mzpokeysnd.h | 157 ++++++- src/engine/platform/sound/pokey/pokey.h | 11 - 4 files changed, 236 insertions(+), 388 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cb5a3068..74d3c727 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -424,6 +424,8 @@ 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/qsound.c src/engine/platform/sound/swan.cpp diff --git a/src/engine/platform/sound/pokey/mzpokeysnd.c b/src/engine/platform/sound/pokey/mzpokeysnd.c index 2a629b00..6395f31d 100644 --- a/src/engine/platform/sound/pokey/mzpokeysnd.c +++ b/src/engine/platform/sound/pokey/mzpokeysnd.c @@ -28,6 +28,7 @@ #include #include +#include "pokey.h" #include "mzpokeysnd.h" #define CONSOLE_VOL 8 @@ -52,195 +53,27 @@ static const double pokeymix[61+CONSOLE_VOL] = { /* Nonlinear POKEY mixing array 120.000000,120.0,120.0,120.0,120.0,120.0,120.0,120.0,120.0}; #endif -#define SND_FILTER_SIZE 2048 - -/* Filter */ -static int pokey_frq; /* Hz - for easier resampling */ -static int filter_size; -static double filter_data[SND_FILTER_SIZE]; -static int audible_frq; - -static const int pokey_frq_ideal = 1789790; /* Hz - True */ - -/* Flags and quality */ -static int snd_quality = 0; - /* Poly tables */ static int poly4tbl[15]; static int poly5tbl[31]; static unsigned char poly17tbl[131071]; static int poly9tbl[511]; - -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; - -// TODO: make this fully struct-ized -PokeyState pokey_states[1]; - -static struct { - double s16; - double s8; -} volume; - /* Forward declarations for ResetPokeyState */ -static int readout0_normal(PokeyState* ps); -static void event0_pure(PokeyState* ps, int p5v, int p4v, int p917v); +int readout0_normal(PokeyState* ps); +void event0_pure(PokeyState* ps, int p5v, int p4v, int p917v); -static int readout1_normal(PokeyState* ps); -static void event1_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); -static int readout2_normal(PokeyState* ps); -static void event2_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); -static int readout3_normal(PokeyState* ps); -static void event3_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); -static void ResetPokeyState(PokeyState* ps) +void ResetPokeyState(PokeyState* ps) { /* Poly positions */ ps->poly4pos = 0; @@ -422,7 +255,7 @@ static void build_poly9(void) } } -static void advance_polies(PokeyState* ps, int tacts) +void advance_polies(PokeyState* ps, int tacts) { ps->poly4pos = (tacts + ps->poly4pos) % 15; ps->poly5pos = (tacts + ps->poly5pos) % 31; @@ -436,19 +269,19 @@ static void advance_polies(PokeyState* ps, int tacts) ************************************/ -static int readout0_vo(PokeyState* ps) +int readout0_vo(PokeyState* ps) { return ps->vol0; } -static int readout0_hipass(PokeyState* ps) +int readout0_hipass(PokeyState* ps) { if(ps->c0t2 ^ ps->c0t3) return ps->vol0; else return 0; } -static int readout0_normal(PokeyState* ps) +int readout0_normal(PokeyState* ps) { if(ps->c0t2) return ps->vol0; @@ -461,19 +294,19 @@ static int readout0_normal(PokeyState* ps) ************************************/ -static int readout1_vo(PokeyState* ps) +int readout1_vo(PokeyState* ps) { return ps->vol1; } -static int readout1_hipass(PokeyState* ps) +int readout1_hipass(PokeyState* ps) { if(ps->c1t2 ^ ps->c1t3) return ps->vol1; else return 0; } -static int readout1_normal(PokeyState* ps) +int readout1_normal(PokeyState* ps) { if(ps->c1t2) return ps->vol1; @@ -486,12 +319,12 @@ static int readout1_normal(PokeyState* ps) ************************************/ -static int readout2_vo(PokeyState* ps) +int readout2_vo(PokeyState* ps) { return ps->vol2; } -static int readout2_normal(PokeyState* ps) +int readout2_normal(PokeyState* ps) { if(ps->c2t2) return ps->vol2; @@ -504,12 +337,12 @@ static int readout2_normal(PokeyState* ps) ************************************/ -static int readout3_vo(PokeyState* ps) +int readout3_vo(PokeyState* ps) { return ps->vol3; } -static int readout3_normal(PokeyState* ps) +int readout3_normal(PokeyState* ps) { if(ps->c3t2) return ps->vol3; @@ -523,39 +356,39 @@ static int readout3_normal(PokeyState* ps) ************************************/ -static void event0_pure(PokeyState* ps, int p5v, int p4v, int p917v) +void event0_pure(PokeyState* ps, int p5v, int p4v, int p917v) { ps->c0t2 = !ps->c0t2; ps->c0t1 = p5v; } -static void event0_p5(PokeyState* ps, int p5v, int p4v, int p917v) +void event0_p5(PokeyState* ps, int p5v, int p4v, int p917v) { if(ps->c0t1) ps->c0t2 = !ps->c0t2; ps->c0t1 = p5v; } -static void event0_p4(PokeyState* ps, int p5v, int p4v, int p917v) +void event0_p4(PokeyState* ps, int p5v, int p4v, int p917v) { ps->c0t2 = p4v; ps->c0t1 = p5v; } -static void event0_p917(PokeyState* ps, int p5v, int p4v, int p917v) +void event0_p917(PokeyState* ps, int p5v, int p4v, int p917v) { ps->c0t2 = p917v; ps->c0t1 = p5v; } -static void event0_p4_p5(PokeyState* ps, int p5v, int p4v, int p917v) +void event0_p4_p5(PokeyState* ps, int p5v, int p4v, int p917v) { if(ps->c0t1) ps->c0t2 = p4v; ps->c0t1 = p5v; } -static void event0_p917_p5(PokeyState* ps, int p5v, int p4v, int p917v) +void event0_p917_p5(PokeyState* ps, int p5v, int p4v, int p917v) { if(ps->c0t1) ps->c0t2 = p917v; @@ -568,39 +401,39 @@ static void event0_p917_p5(PokeyState* ps, int p5v, int p4v, int p917v) ************************************/ -static void event1_pure(PokeyState* ps, int p5v, int p4v, int p917v) +void event1_pure(PokeyState* ps, int p5v, int p4v, int p917v) { ps->c1t2 = !ps->c1t2; ps->c1t1 = p5v; } -static void event1_p5(PokeyState* ps, int p5v, int p4v, int p917v) +void event1_p5(PokeyState* ps, int p5v, int p4v, int p917v) { if(ps->c1t1) ps->c1t2 = !ps->c1t2; ps->c1t1 = p5v; } -static void event1_p4(PokeyState* ps, int p5v, int p4v, int p917v) +void event1_p4(PokeyState* ps, int p5v, int p4v, int p917v) { ps->c1t2 = p4v; ps->c1t1 = p5v; } -static void event1_p917(PokeyState* ps, int p5v, int p4v, int p917v) +void event1_p917(PokeyState* ps, int p5v, int p4v, int p917v) { ps->c1t2 = p917v; ps->c1t1 = p5v; } -static void event1_p4_p5(PokeyState* ps, int p5v, int p4v, int p917v) +void event1_p4_p5(PokeyState* ps, int p5v, int p4v, int p917v) { if(ps->c1t1) ps->c1t2 = p4v; ps->c1t1 = p5v; } -static void event1_p917_p5(PokeyState* ps, int p5v, int p4v, int p917v) +void event1_p917_p5(PokeyState* ps, int p5v, int p4v, int p917v) { if(ps->c1t1) ps->c1t2 = p917v; @@ -613,7 +446,7 @@ static void event1_p917_p5(PokeyState* ps, int p5v, int p4v, int p917v) ************************************/ -static void event2_pure(PokeyState* ps, int p5v, int p4v, int p917v) +void event2_pure(PokeyState* ps, int p5v, int p4v, int p917v) { ps->c2t2 = !ps->c2t2; ps->c2t1 = p5v; @@ -621,7 +454,7 @@ static void event2_pure(PokeyState* ps, int p5v, int p4v, int p917v) ps->c0t3 = ps->c0t2; } -static void event2_p5(PokeyState* ps, int p5v, int p4v, int p917v) +void event2_p5(PokeyState* ps, int p5v, int p4v, int p917v) { if(ps->c2t1) ps->c2t2 = !ps->c2t2; @@ -630,7 +463,7 @@ static void event2_p5(PokeyState* ps, int p5v, int p4v, int p917v) ps->c0t3 = ps->c0t2; } -static void event2_p4(PokeyState* ps, int p5v, int p4v, int p917v) +void event2_p4(PokeyState* ps, int p5v, int p4v, int p917v) { ps->c2t2 = p4v; ps->c2t1 = p5v; @@ -638,7 +471,7 @@ static void event2_p4(PokeyState* ps, int p5v, int p4v, int p917v) ps->c0t3 = ps->c0t2; } -static void event2_p917(PokeyState* ps, int p5v, int p4v, int p917v) +void event2_p917(PokeyState* ps, int p5v, int p4v, int p917v) { ps->c2t2 = p917v; ps->c2t1 = p5v; @@ -646,7 +479,7 @@ static void event2_p917(PokeyState* ps, int p5v, int p4v, int p917v) ps->c0t3 = ps->c0t2; } -static void event2_p4_p5(PokeyState* ps, int p5v, int p4v, int p917v) +void event2_p4_p5(PokeyState* ps, int p5v, int p4v, int p917v) { if(ps->c2t1) ps->c2t2 = p4v; @@ -655,7 +488,7 @@ static void event2_p4_p5(PokeyState* ps, int p5v, int p4v, int p917v) ps->c0t3 = ps->c0t2; } -static void event2_p917_p5(PokeyState* ps, int p5v, int p4v, int p917v) +void event2_p917_p5(PokeyState* ps, int p5v, int p4v, int p917v) { if(ps->c2t1) ps->c2t2 = p917v; @@ -670,7 +503,7 @@ static void event2_p917_p5(PokeyState* ps, int p5v, int p4v, int p917v) ************************************/ -static void event3_pure(PokeyState* ps, int p5v, int p4v, int p917v) +void event3_pure(PokeyState* ps, int p5v, int p4v, int p917v) { ps->c3t2 = !ps->c3t2; ps->c3t1 = p5v; @@ -678,7 +511,7 @@ static void event3_pure(PokeyState* ps, int p5v, int p4v, int p917v) ps->c1t3 = ps->c1t2; } -static void event3_p5(PokeyState* ps, int p5v, int p4v, int p917v) +void event3_p5(PokeyState* ps, int p5v, int p4v, int p917v) { if(ps->c3t1) ps->c3t2 = !ps->c3t2; @@ -687,7 +520,7 @@ static void event3_p5(PokeyState* ps, int p5v, int p4v, int p917v) ps->c1t3 = ps->c1t2; } -static void event3_p4(PokeyState* ps, int p5v, int p4v, int p917v) +void event3_p4(PokeyState* ps, int p5v, int p4v, int p917v) { ps->c3t2 = p4v; ps->c3t1 = p5v; @@ -695,7 +528,7 @@ static void event3_p4(PokeyState* ps, int p5v, int p4v, int p917v) ps->c1t3 = ps->c1t2; } -static void event3_p917(PokeyState* ps, int p5v, int p4v, int p917v) +void event3_p917(PokeyState* ps, int p5v, int p4v, int p917v) { ps->c3t2 = p917v; ps->c3t1 = p5v; @@ -703,7 +536,7 @@ static void event3_p917(PokeyState* ps, int p5v, int p4v, int p917v) ps->c1t3 = ps->c1t2; } -static void event3_p4_p5(PokeyState* ps, int p5v, int p4v, int p917v) +void event3_p4_p5(PokeyState* ps, int p5v, int p4v, int p917v) { if(ps->c3t1) ps->c3t2 = p4v; @@ -712,7 +545,7 @@ static void event3_p4_p5(PokeyState* ps, int p5v, int p4v, int p917v) ps->c1t3 = ps->c1t2; } -static void event3_p917_p5(PokeyState* ps, int p5v, int p4v, int p917v) +void event3_p917_p5(PokeyState* ps, int p5v, int p4v, int p917v) { if(ps->c3t1) ps->c3t2 = p917v; @@ -721,7 +554,7 @@ static void event3_p917_p5(PokeyState* ps, int p5v, int p4v, int p917v) ps->c1t3 = ps->c1t2; } -static void advance_ticks(PokeyState* ps, int ticks) +void advance_ticks(PokeyState* ps, int ticks) { int ta,tbe, tbe0, tbe1, tbe2, tbe3; int p5v,p4v,p917v; @@ -914,107 +747,12 @@ static void advance_ticks(PokeyState* ps, int ticks) } } -static double generate_sample(PokeyState* ps) +double generate_sample(PokeyState* ps) { - /*unsigned long ta = (subticks+pokey_frq)/POKEYSND_playback_freq; - subticks = (subticks+pokey_frq)%POKEYSND_playback_freq;*/ - - advance_ticks(ps, pokey_frq/POKEYSND_playback_freq); + advance_ticks(ps, 1); return ps->outvol_all; } -/****************************************** - filter table generator by Krzysztof Nikiel - ******************************************/ - -static int remez_filter_table(double resamp_rate, /* output_rate/input_rate */ - double *cutoff, int quality) -{ - int i; - static const int orders[] = {600, 800, 1000, 1200}; - static const struct { - int stop; /* stopband ripple */ - double weight; /* stopband weight */ - double twidth[sizeof(orders)/sizeof(orders[0])]; - } paramtab[] = - { - {70, 90, {4.9e-3, 3.45e-3, 2.65e-3, 2.2e-3}}, - {55, 25, {3.4e-3, 2.7e-3, 2.05e-3, 1.7e-3}}, - {40, 6.0, {2.6e-3, 1.8e-3, 1.5e-3, 1.2e-3}}, - {-1, 0, {0, 0, 0, 0}} - }; - static const double passtab[] = {0.5, 0.6, 0.7}; - int ripple = 0, order = 0; - int size; - double weights[2], desired[2], bands[4]; - static const int interlevel = 5; - double step = 1.0 / interlevel; - - *cutoff = 0.95 * 0.5 * resamp_rate; - - if (quality >= (int) (sizeof(passtab) / sizeof(passtab[0]))) - quality = (int) (sizeof(passtab) / sizeof(passtab[0])) - 1; - - for (ripple = 0; paramtab[ripple].stop > 0; ripple++) - { - for (order = 0; order < (int) (sizeof(orders)/sizeof(orders[0])); order++) - { - if ((*cutoff - paramtab[ripple].twidth[order]) - > passtab[quality] * 0.5 * resamp_rate) - /* transition width OK */ - goto found; - } - } - - /* not found -- use shortest transition */ - ripple--; - order--; - -found: - - size = orders[order] + 1; - - if (size > SND_FILTER_SIZE) /* static table too short */ - return 0; - - desired[0] = 1; - desired[1] = 0; - - weights[0] = 1; - weights[1] = paramtab[ripple].weight; - - bands[0] = 0; - bands[2] = *cutoff; - bands[1] = bands[2] - paramtab[ripple].twidth[order]; - bands[3] = 0.5; - - bands[1] *= (double)interlevel; - bands[2] *= (double)interlevel; - REMEZ_CreateFilter(filter_data, (size / interlevel) + 1, 2, bands, desired, weights, REMEZ_BANDPASS); - for (i = size - interlevel; i >= 0; i -= interlevel) - { - int s; - double h1 = filter_data[i/interlevel]; - double h2 = filter_data[i/interlevel+1]; - - for (s = 0; s < interlevel; s++) - { - double d = (double)s * step; - filter_data[i+s] = (h1*(1.0 - d) + h2 * d) * step; - } - } - - /* compute reversed cumulative sum table */ - for (i = size - 2; i >= 0; i--) - filter_data[i] += filter_data[i + 1]; - - return size; -} - -void mzpokeysnd_process_8(void* sndbuffer, int sndn); -void mzpokeysnd_process_16(void* sndbuffer, int sndn); -void Update_pokey_sound_mz(UWORD addr, UBYTE val, UBYTE gain); - /*****************************************************************************/ /* Module: MZPOKEYSND_Init() */ /* Purpose: to handle the power-up initialization functions */ @@ -1029,42 +767,18 @@ void Update_pokey_sound_mz(UWORD addr, UBYTE val, UBYTE gain); /* */ /*****************************************************************************/ -int MZPOKEYSND_Init(size_t freq17, int playback_freq, - int flags, int quality - , int clear_regs - ) +int MZPOKEYSND_Init(PokeyState* ps) { - double cutoff; - - snd_quality = quality; - - POKEYSND_Update_ptr = Update_pokey_sound_mz; - POKEYSND_Process_ptr = (flags & POKEYSND_BIT16) ? mzpokeysnd_process_16 : mzpokeysnd_process_8; - - pokey_frq = (int)(((double)pokey_frq_ideal/POKEYSND_playback_freq) + 0.5) - * POKEYSND_playback_freq; - filter_size = remez_filter_table((double)POKEYSND_playback_freq/pokey_frq, - &cutoff, quality); - audible_frq = (int ) (cutoff * pokey_frq); - build_poly4(); build_poly5(); build_poly9(); build_poly17(); - - if (clear_regs) - { - ResetPokeyState(pokey_states); - } - - volume.s8 = POKEYSND_volume * 0xff / 256.0; - volume.s16 = POKEYSND_volume * 0xffff / 256.0; - + ResetPokeyState(ps); return 0; /* OK */ } -static void Update_readout_0(PokeyState* ps) +void Update_readout_0(PokeyState* ps) { if(ps->c0vo) ps->readout_0 = readout0_vo; @@ -1074,7 +788,7 @@ static void Update_readout_0(PokeyState* ps) ps->readout_0 = readout0_normal; } -static void Update_readout_1(PokeyState* ps) +void Update_readout_1(PokeyState* ps) { if(ps->c1vo) ps->readout_1 = readout1_vo; @@ -1084,7 +798,7 @@ static void Update_readout_1(PokeyState* ps) ps->readout_1 = readout1_normal; } -static void Update_readout_2(PokeyState* ps) +void Update_readout_2(PokeyState* ps) { if(ps->c2vo) ps->readout_2 = readout2_vo; @@ -1092,7 +806,7 @@ static void Update_readout_2(PokeyState* ps) ps->readout_2 = readout2_normal; } -static void Update_readout_3(PokeyState* ps) +void Update_readout_3(PokeyState* ps) { if(ps->c3vo) ps->readout_3 = readout3_vo; @@ -1100,7 +814,7 @@ static void Update_readout_3(PokeyState* ps) ps->readout_3 = readout3_normal; } -static void Update_event0(PokeyState* ps) +void Update_event0(PokeyState* ps) { if(ps->c0sw3) { @@ -1128,7 +842,7 @@ static void Update_event0(PokeyState* ps) } } -static void Update_event1(PokeyState* ps) +void Update_event1(PokeyState* ps) { if(ps->c1sw3) { @@ -1156,7 +870,7 @@ static void Update_event1(PokeyState* ps) } } -static void Update_event2(PokeyState* ps) +void Update_event2(PokeyState* ps) { if(ps->c2sw3) { @@ -1184,7 +898,7 @@ static void Update_event2(PokeyState* ps) } } -static void Update_event3(PokeyState* ps) +void Update_event3(PokeyState* ps) { if(ps->c3sw3) { @@ -1212,7 +926,7 @@ static void Update_event3(PokeyState* ps) } } -static void Update_c0divstart(PokeyState* ps) +void Update_c0divstart(PokeyState* ps) { if(ps->c1_f0) { @@ -1236,7 +950,7 @@ static void Update_c0divstart(PokeyState* ps) } } -static void Update_c1divstart(PokeyState* ps) +void Update_c1divstart(PokeyState* ps) { if(ps->c1_f0) { @@ -1249,7 +963,7 @@ static void Update_c1divstart(PokeyState* ps) ps->c1divstart = (ps->c1diva + 1) * ps->mdivk; } -static void Update_c2divstart(PokeyState* ps) +void Update_c2divstart(PokeyState* ps) { if(ps->c3_f2) { @@ -1273,7 +987,7 @@ static void Update_c2divstart(PokeyState* ps) } } -static void Update_c3divstart(PokeyState* ps) +void Update_c3divstart(PokeyState* ps) { if(ps->c3_f2) { @@ -1286,7 +1000,7 @@ static void Update_c3divstart(PokeyState* ps) ps->c3divstart = (ps->c3diva + 1) * ps->mdivk; } -static void Update_audctl(PokeyState* ps, unsigned char val) +void Update_audctl(PokeyState* ps, unsigned char val) { int nc0_hf,nc2_hf,nc1_f0,nc3_f2,nc0sw4,nc1sw4,new_divk; int recalc0=0; @@ -1424,33 +1138,33 @@ static void Update_audctl(PokeyState* ps, unsigned char val) } /* SKCTL for two-tone mode */ -static void Update_skctl(PokeyState* ps, unsigned char val) +void Update_skctl(PokeyState* ps, unsigned char val) { ps->skctl = val; } /* if using nonlinear mixing, don't stop ultrasounds */ #ifdef NONLINEAR_MIXING -static void Update_c0stop(PokeyState* ps) +void Update_c0stop(PokeyState* ps) { ps->outvol_0 = ps->readout_0(ps); } -static void Update_c1stop(PokeyState* ps) +void Update_c1stop(PokeyState* ps) { ps->outvol_1 = ps->readout_1(ps); } -static void Update_c2stop(PokeyState* ps) +void Update_c2stop(PokeyState* ps) { ps->outvol_2 = ps->readout_2(ps); } -static void Update_c3stop(PokeyState* ps) +void Update_c3stop(PokeyState* ps) { ps->outvol_3 = ps->readout_3(ps); } #else -static void Update_c0stop(PokeyState* ps) +void Update_c0stop(PokeyState* ps) { - int lim = pokey_frq/2/audible_frq; + int lim = 1; int hfa = 0; ps->c0stop = 0; @@ -1502,9 +1216,9 @@ static void Update_c0stop(PokeyState* ps) ps->outvol_0 = ps->vol0; } -static void Update_c1stop(PokeyState* ps) +void Update_c1stop(PokeyState* ps) { - int lim = pokey_frq/2/audible_frq; + int lim = 1; int hfa = 0; ps->c1stop = 0; @@ -1527,9 +1241,9 @@ static void Update_c1stop(PokeyState* ps) ps->outvol_1 = ps->vol1; } -static void Update_c2stop(PokeyState* ps) +void Update_c2stop(PokeyState* ps) { - int lim = pokey_frq/2/audible_frq; + int lim = 1; int hfa = 0; ps->c2stop = 0; @@ -1582,9 +1296,9 @@ static void Update_c2stop(PokeyState* ps) ps->outvol_2 = ps->vol2; } -static void Update_c3stop(PokeyState* ps) +void Update_c3stop(PokeyState* ps) { - int lim = pokey_frq/2/audible_frq; + int lim = 1; int hfa = 0; ps->c3stop = 0; @@ -1620,10 +1334,8 @@ static void Update_c3stop(PokeyState* ps) /* Outputs: Adjusts local globals - no return value */ /* */ /*****************************************************************************/ -void Update_pokey_sound_mz(UWORD addr, UBYTE val, UBYTE gain) +void Update_pokey_sound_mz(PokeyState* ps, unsigned short addr, unsigned char val, unsigned char gain) { - PokeyState* ps = pokey_states; - switch(addr & 0x0f) { case POKEY_OFFSET_AUDF1: @@ -1925,34 +1637,34 @@ void Update_pokey_sound_mz(UWORD addr, UBYTE val, UBYTE gain) #define MAX_SAMPLE 152 -void mzpokeysnd_process_8(void* sndbuffer, int sndn) +void mzpokeysnd_process_8(PokeyState* ps, void* sndbuffer, int sndn) { int i; int nsam = sndn; - UBYTE *buffer = (UBYTE *) sndbuffer; + unsigned char *buffer = (unsigned char *) sndbuffer; /* if there are two pokeys, then the signal is stereo we assume even sndn */ while(nsam >= 1) { - buffer[0] = (UBYTE)floor(generate_sample(pokey_states) + buffer[0] = (unsigned char)floor(generate_sample(ps) * (255.0 / 2 / MAX_SAMPLE / 4 * M_PI * 0.95) + 128 + 0.5 + 0.5 * rand() / RAND_MAX - 0.25); buffer += 1; nsam -= 1; } } -void mzpokeysnd_process_16(void* sndbuffer, int sndn) +void mzpokeysnd_process_16(PokeyState* ps, void* sndbuffer, int sndn) { int i; int nsam = sndn; - SWORD *buffer = (SWORD *) sndbuffer; + short *buffer = (short *) sndbuffer; /* if there are two pokeys, then the signal is stereo we assume even sndn */ while(nsam >= (int) 1) { - buffer[0] = (SWORD)floor(generate_sample(pokey_states) + buffer[0] = (short)floor(generate_sample(ps) * (65535.0 / 2 / MAX_SAMPLE / 4 * M_PI * 0.95) + 0.5 + 0.5 * rand() / RAND_MAX - 0.25); buffer += 1; nsam -= 1; diff --git a/src/engine/platform/sound/pokey/mzpokeysnd.h b/src/engine/platform/sound/pokey/mzpokeysnd.h index 908a1dd1..2ebb84b6 100644 --- a/src/engine/platform/sound/pokey/mzpokeysnd.h +++ b/src/engine/platform/sound/pokey/mzpokeysnd.h @@ -3,11 +3,156 @@ #include -int MZPOKEYSND_Init(size_t freq17, - int playback_freq, - int flags, - int quality - , int clear_regs - ); +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_8(PokeyState* ps, void* sndbuffer, int sndn); +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); + +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 index 777573da..22fd108d 100644 --- a/src/engine/platform/sound/pokey/pokey.h +++ b/src/engine/platform/sound/pokey/pokey.h @@ -75,15 +75,4 @@ #define POKEY_CHIP4 12 #define POKEY_SAMPLE 127 -/* structures to hold the 9 pokey control bytes */ -extern UBYTE POKEY_AUDF[4 * POKEY_MAXPOKEYS]; /* AUDFx (D200, D202, D204, D206) */ -extern UBYTE POKEY_AUDC[4 * POKEY_MAXPOKEYS]; /* AUDCx (D201, D203, D205, D207) */ -extern UBYTE POKEY_AUDCTL[POKEY_MAXPOKEYS]; /* AUDCTL (D208) */ - -extern int POKEY_DivNIRQ[4], POKEY_DivNMax[4]; -extern int POKEY_Base_mult[POKEY_MAXPOKEYS]; /* selects either 64Khz or 15Khz clock mult */ - -extern UBYTE POKEY_poly9_lookup[POKEY_POLY9_SIZE]; -extern UBYTE POKEY_poly17_lookup[16385]; - #endif /* POKEY_H_ */ From ba674a0329a1aea3aa78e73390a5a6f4bcad433f Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 19 Dec 2022 19:45:04 -0500 Subject: [PATCH 24/93] start work on POKEY to-do: - add ability to change AUDCTL - bass shape frequency and waveform mapping - easy noise??? --- CMakeLists.txt | 1 + src/engine/dispatchContainer.cpp | 4 + src/engine/platform/pokey.cpp | 363 +++++++++++++++++++ src/engine/platform/pokey.h | 77 ++++ src/engine/platform/sound/pokey/mzpokeysnd.h | 2 + src/gui/guiConst.cpp | 2 + src/gui/insEdit.cpp | 5 +- 7 files changed, 452 insertions(+), 2 deletions(-) create mode 100644 src/engine/platform/pokey.cpp create mode 100644 src/engine/platform/pokey.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 74d3c727..0bd77ebc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -510,6 +510,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/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index a389a89e..b578f988 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" @@ -338,6 +339,9 @@ 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; + break; case DIV_SYSTEM_QSOUND: dispatch=new DivPlatformQSound; break; diff --git a/src/engine/platform/pokey.cpp b/src/engine/platform/pokey.cpp new file mode 100644 index 00000000..c6fb575e --- /dev/null +++ b/src/engine/platform/pokey.cpp @@ -0,0 +1,363 @@ +/** + * 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 +}; + +const char** DivPlatformPOKEY::getRegisterSheet() { + return regCheatSheetPOKEY; +} + +void DivPlatformPOKEY::acquire(short* bufL, short* bufR, size_t start, size_t len) { + for (size_t h=start; hcalcArp(chan[i].note,chan[i].std.arp.val)); + } + chan[i].freqChanged=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+=chan[i].freq>>3; + chan[i].freq>>=4; + if (chan[i].freq&1) chan[i].freq++; + chan[i].freq>>=1; + } + + if (audctl&1) { + chan[i].freq>>=2; + } + + if ((i==0 && audctl&16) || (i==2 && audctl&8)) { + if (chan[i].freq>65535) chan[i].freq=65535; + } else { + if (chan[i].freq>255) chan[i].freq=255; + } + + if (--chan[i].freq<0) chan[i].freq=0; + + // write frequency + if ((i==1 && audctl&16) || (i==3 && audctl&8)) { + // ignore - channel is paired + } else { + rWrite(i<<1,chan[i].freq); + logV("%d: %d",i,chan[i].freq); + } + + 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)|(chan[i].wave<<5); + if ((i==1 && audctl&16) || (i==3 && audctl&8)) { + // mute - channel is paired + val=0; + } + chan[i].ctlChanged=false; + 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].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_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; + } +} + +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() { + 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); + } + + ResetPokeyState(&pokey); + + audctl=0; + audctlChanged=true; +} + +bool DivPlatformPOKEY::keyOffAffectsArp(int ch) { + return true; +} + +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; + rate=chipClock; + for (int i=0; i<4; i++) { + oscBuf[i]->rate=rate/16; + } +} + +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; + for (int i=0; i<4; i++) { + isMuted[i]=false; + oscBuf[i]=new DivDispatchOscBuffer; + } + + MZPOKEYSND_Init(&pokey); + + setFlags(flags); + reset(); + return 6; +} + +void DivPlatformPOKEY::quit() { + for (int i=0; i<4; i++) { + delete oscBuf[i]; + } +} + +DivPlatformPOKEY::~DivPlatformPOKEY() { +} diff --git a/src/engine/platform/pokey.h b/src/engine/platform/pokey.h new file mode 100644 index 00000000..6df1514f --- /dev/null +++ b/src/engine/platform/pokey.h @@ -0,0 +1,77 @@ +/** + * 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" +} + +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; + PokeyState pokey; + 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); + 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); + 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(); + ~DivPlatformPOKEY(); +}; + +#endif diff --git a/src/engine/platform/sound/pokey/mzpokeysnd.h b/src/engine/platform/sound/pokey/mzpokeysnd.h index 2ebb84b6..4f795087 100644 --- a/src/engine/platform/sound/pokey/mzpokeysnd.h +++ b/src/engine/platform/sound/pokey/mzpokeysnd.h @@ -153,6 +153,8 @@ void mzpokeysnd_process_8(PokeyState* ps, void* sndbuffer, int sndn); 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/gui/guiConst.cpp b/src/gui/guiConst.cpp index 40098ec7..c442a22d 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -958,6 +958,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, @@ -1063,6 +1064,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 cd1d60b5..eaf8f77d 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -5000,7 +5000,8 @@ void FurnaceGUI::drawInsEdit() { } if (ins->type==DIV_INS_TIA || ins->type==DIV_INS_AMIGA || ins->type==DIV_INS_SCC || ins->type==DIV_INS_PET || ins->type==DIV_INS_VIC || ins->type==DIV_INS_SEGAPCM || - ins->type==DIV_INS_FM || ins->type==DIV_INS_K007232 || ins->type==DIV_INS_GA20) { + ins->type==DIV_INS_FM || ins->type==DIV_INS_K007232 || ins->type==DIV_INS_GA20 || + ins->type==DIV_INS_POKEY) { dutyMax=0; } if (ins->type==DIV_INS_VBOY) { @@ -5086,7 +5087,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; From 4b4a2273d37d81f1ce252f441c040bf196a9b568 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 20 Dec 2022 00:29:12 -0500 Subject: [PATCH 25/93] YM2151/2414: fix new arp --- src/engine/platform/arcade.cpp | 4 ++-- src/engine/platform/tx81z.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) 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/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; From 541360a446ad69eeee4d1ecfc29d7cabd4d350db Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 20 Dec 2022 00:34:52 -0500 Subject: [PATCH 26/93] POKEY: louder --- src/engine/platform/pokey.cpp | 5 +++++ src/engine/platform/pokey.h | 1 + 2 files changed, 6 insertions(+) diff --git a/src/engine/platform/pokey.cpp b/src/engine/platform/pokey.cpp index c6fb575e..3f0e02a8 100644 --- a/src/engine/platform/pokey.cpp +++ b/src/engine/platform/pokey.cpp @@ -159,6 +159,7 @@ int DivPlatformPOKEY::dispatch(DivCommand c) { 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; @@ -310,6 +311,10 @@ 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); diff --git a/src/engine/platform/pokey.h b/src/engine/platform/pokey.h index 6df1514f..34751677 100644 --- a/src/engine/platform/pokey.h +++ b/src/engine/platform/pokey.h @@ -64,6 +64,7 @@ class DivPlatformPOKEY: public DivDispatch { 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); From f781d63ac28d236871455689818d407d3c8dd809 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 20 Dec 2022 02:39:34 -0500 Subject: [PATCH 27/93] POKEY: bass period mapping and effect to change AUDCTL --- src/engine/platform/pokey.cpp | 106 +++++++++++++++++++++++++++++----- src/engine/sysDef.cpp | 7 ++- 2 files changed, 96 insertions(+), 17 deletions(-) diff --git a/src/engine/platform/pokey.cpp b/src/engine/platform/pokey.cpp index 3f0e02a8..75074895 100644 --- a/src/engine/platform/pokey.cpp +++ b/src/engine/platform/pokey.cpp @@ -38,6 +38,28 @@ const char* regCheatSheetPOKEY[]={ 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; } @@ -100,30 +122,74 @@ void DivPlatformPOKEY::tick(bool sysTick) { 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+=chan[i].freq>>3; - chan[i].freq>>=4; - if (chan[i].freq&1) chan[i].freq++; - chan[i].freq>>=1; + chan[i].freq/=7; + switch (chan[i].wave) { + case 6: + if (audctl&1) { + chan[i].freq/=5; + } else { + chan[i].freq/=15; + } + chan[i].freq>>=1; + break; + case 7: + chan[i].freq/=5; + 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/=15; + break; + case 7: + chan[i].freq<<=1; + chan[i].freq/=5; + break; + } } - if (audctl&1) { + 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==6) { + 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==7) { + 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>255) chan[i].freq=255; + if (chan[i].freq>minFreq8) chan[i].freq=minFreq8; } - if (--chan[i].freq<0) chan[i].freq=0; - // write frequency if ((i==1 && audctl&16) || (i==3 && audctl&8)) { // ignore - channel is paired } else { - rWrite(i<<1,chan[i].freq); - logV("%d: %d",i,chan[i].freq); + 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) { @@ -134,13 +200,17 @@ void DivPlatformPOKEY::tick(bool sysTick) { chan[i].freqChanged=false; } if (chan[i].ctlChanged) { - unsigned char val=((chan[i].active && !isMuted[i])?(chan[i].outVol&15):0)|(chan[i].wave<<5); - if ((i==1 && audctl&16) || (i==3 && audctl&8)) { - // mute - channel is paired - val=0; - } + unsigned char val=((chan[i].active && !isMuted[i])?(chan[i].outVol&15):0)|(waveMap[chan[i].wave&7]<<5); chan[i].ctlChanged=false; - rWrite(1+(i<<1),val); + 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); + } } } } @@ -204,6 +274,10 @@ int DivPlatformPOKEY::dispatch(DivCommand c) { 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; diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index 9192e9c8..1da237df 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -1124,7 +1124,12 @@ void DivEngine::registerSystems() { {"Channel 1", "Channel 2", "Channel 3", "Channel 4"}, {"CH1", "CH2", "CH3", "CH4"}, {DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE}, - {DIV_INS_POKEY, DIV_INS_POKEY, DIV_INS_POKEY, DIV_INS_POKEY} + {DIV_INS_POKEY, DIV_INS_POKEY, DIV_INS_POKEY, DIV_INS_POKEY}, + {}, + { + {0x10, {DIV_CMD_WAVE, "10xx: Set waveform (0 to 7)"}}, + {0x11, {DIV_CMD_STD_NOISE_MODE, "11xx: Set AUDCTL"}}, + } ); sysDefs[DIV_SYSTEM_RF5C68]=new DivSysDef( From 08f42f1ea0d502f73ad5c4ac36a912b4d35289ea Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 20 Dec 2022 02:43:37 -0500 Subject: [PATCH 28/93] POKEY: it's the opposite --- src/engine/platform/pokey.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/engine/platform/pokey.cpp b/src/engine/platform/pokey.cpp index 75074895..06e8b98c 100644 --- a/src/engine/platform/pokey.cpp +++ b/src/engine/platform/pokey.cpp @@ -125,6 +125,10 @@ void DivPlatformPOKEY::tick(bool sysTick) { 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 { @@ -132,10 +136,6 @@ void DivPlatformPOKEY::tick(bool sysTick) { } chan[i].freq>>=1; break; - case 7: - chan[i].freq/=5; - chan[i].freq>>=1; - break; default: chan[i].freq>>=2; break; @@ -144,11 +144,11 @@ void DivPlatformPOKEY::tick(bool sysTick) { switch (chan[i].wave) { case 6: chan[i].freq<<=1; - chan[i].freq/=15; + chan[i].freq/=5; break; case 7: chan[i].freq<<=1; - chan[i].freq/=5; + chan[i].freq/=15; break; } } @@ -161,13 +161,13 @@ void DivPlatformPOKEY::tick(bool sysTick) { // snap buzz periods int minFreq8=255; - if (chan[i].wave==6) { + 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==7) { + } 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 { From c46f3a308587b79eded3f546407af813d4b02cf0 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 20 Dec 2022 02:43:53 -0500 Subject: [PATCH 29/93] GUI: add POKEY presets --- src/gui/presets.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/gui/presets.cpp b/src/gui/presets.cpp index 818318c8..917b74de 100644 --- a/src/gui/presets.cpp +++ b/src/gui/presets.cpp @@ -826,6 +826,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, From 0d10b99cf758b22e959c89689bb780858bc989ce Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 20 Dec 2022 02:59:26 -0500 Subject: [PATCH 30/93] POKEY: add AUDCTL macro --- src/engine/platform/pokey.cpp | 4 ++++ src/gui/insEdit.cpp | 13 +++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/engine/platform/pokey.cpp b/src/engine/platform/pokey.cpp index 06e8b98c..654ceaf4 100644 --- a/src/engine/platform/pokey.cpp +++ b/src/engine/platform/pokey.cpp @@ -92,6 +92,10 @@ void DivPlatformPOKEY::tick(bool sysTick) { } 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; diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index eaf8f77d..b5e1cf71 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 }; @@ -4979,6 +4983,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; @@ -5000,8 +5008,7 @@ void FurnaceGUI::drawInsEdit() { } if (ins->type==DIV_INS_TIA || ins->type==DIV_INS_AMIGA || ins->type==DIV_INS_SCC || ins->type==DIV_INS_PET || ins->type==DIV_INS_VIC || ins->type==DIV_INS_SEGAPCM || - ins->type==DIV_INS_FM || ins->type==DIV_INS_K007232 || ins->type==DIV_INS_GA20 || - ins->type==DIV_INS_POKEY) { + ins->type==DIV_INS_FM || ins->type==DIV_INS_K007232 || ins->type==DIV_INS_GA20) { dutyMax=0; } if (ins->type==DIV_INS_VBOY) { @@ -5221,6 +5228,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) { From 15afb1da84468d2824259125ce086b8ed9483eb4 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 20 Dec 2022 03:19:25 -0500 Subject: [PATCH 31/93] add POKEY documentation --- papers/doc/7-systems/pokey.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 papers/doc/7-systems/pokey.md 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. From be41a2fe5de6fa223ac6bedf108c5e6b9451a732 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 20 Dec 2022 03:52:18 -0500 Subject: [PATCH 32/93] POKEY: fix forceIns --- TODO.md | 1 - src/engine/platform/pokey.cpp | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/TODO.md b/TODO.md index 1c6b99e8..e53000d9 100644 --- a/TODO.md +++ b/TODO.md @@ -1,6 +1,5 @@ # to-do for 0.6pre2 -- POKEY - PokĂ©mon Mini - register layout - confirm emulation diff --git a/src/engine/platform/pokey.cpp b/src/engine/platform/pokey.cpp index 654ceaf4..23cb9add 100644 --- a/src/engine/platform/pokey.cpp +++ b/src/engine/platform/pokey.cpp @@ -346,6 +346,7 @@ void DivPlatformPOKEY::forceIns() { chan[i].ctlChanged=true; chan[i].freqChanged=true; } + audctlChanged=true; } void* DivPlatformPOKEY::getChanState(int ch) { From b7302e0d981a9854f5f75c1b7acf831e4ec9caa0 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 20 Dec 2022 14:54:55 -0500 Subject: [PATCH 33/93] POKEY: VGM export --- TODO.md | 3 +-- src/engine/sysDef.cpp | 2 +- src/engine/vgmOps.cpp | 23 +++++++++++++++++++++++ 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/TODO.md b/TODO.md index 1c6b99e8..dafe2873 100644 --- a/TODO.md +++ b/TODO.md @@ -1,9 +1,8 @@ # 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/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index 1da237df..bfade1f0 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -1119,7 +1119,7 @@ void DivEngine::registerSystems() { ); sysDefs[DIV_SYSTEM_POKEY]=new DivSysDef( - "POKEY", NULL, 0x94, 0, 4, false, true, 0, false, 0, + "POKEY", NULL, 0x94, 0, 4, false, true, 0x161, false, 0, "TIA, but better and more flexible.\nused in the Atari 8-bit family of computers (400/800/XL/XE).", {"Channel 1", "Channel 2", "Channel 3", "Channel 4"}, {"CH1", "CH2", "CH3", "CH4"}, diff --git a/src/engine/vgmOps.cpp b/src/engine/vgmOps.cpp index 75953a19..f7c891c2 100644 --- a/src/engine/vgmOps.cpp +++ b/src/engine/vgmOps.cpp @@ -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); @@ -698,6 +705,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); @@ -1246,6 +1258,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; From 15dc663a153bdd087205a7c06dc8e1728a48cb44 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 20 Dec 2022 15:07:58 -0500 Subject: [PATCH 34/93] POKEY: remove dithering we only use 16-bit output anyway --- src/engine/platform/sound/pokey/mzpokeysnd.c | 19 +------------------ src/engine/platform/sound/pokey/mzpokeysnd.h | 1 - 2 files changed, 1 insertion(+), 19 deletions(-) diff --git a/src/engine/platform/sound/pokey/mzpokeysnd.c b/src/engine/platform/sound/pokey/mzpokeysnd.c index 6395f31d..9594bd5a 100644 --- a/src/engine/platform/sound/pokey/mzpokeysnd.c +++ b/src/engine/platform/sound/pokey/mzpokeysnd.c @@ -1637,23 +1637,6 @@ void Update_pokey_sound_mz(PokeyState* ps, unsigned short addr, unsigned char va #define MAX_SAMPLE 152 -void mzpokeysnd_process_8(PokeyState* ps, void* sndbuffer, int sndn) -{ - int i; - int nsam = sndn; - unsigned char *buffer = (unsigned char *) sndbuffer; - - /* if there are two pokeys, then the signal is stereo - we assume even sndn */ - while(nsam >= 1) - { - buffer[0] = (unsigned char)floor(generate_sample(ps) - * (255.0 / 2 / MAX_SAMPLE / 4 * M_PI * 0.95) + 128 + 0.5 + 0.5 * rand() / RAND_MAX - 0.25); - buffer += 1; - nsam -= 1; - } -} - void mzpokeysnd_process_16(PokeyState* ps, void* sndbuffer, int sndn) { int i; @@ -1665,7 +1648,7 @@ void mzpokeysnd_process_16(PokeyState* ps, void* sndbuffer, int sndn) while(nsam >= (int) 1) { buffer[0] = (short)floor(generate_sample(ps) - * (65535.0 / 2 / MAX_SAMPLE / 4 * M_PI * 0.95) + 0.5 + 0.5 * rand() / RAND_MAX - 0.25); + * (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 index 4f795087..01da7aff 100644 --- a/src/engine/platform/sound/pokey/mzpokeysnd.h +++ b/src/engine/platform/sound/pokey/mzpokeysnd.h @@ -149,7 +149,6 @@ typedef struct stPokeyState int outvol_3; } PokeyState; -void mzpokeysnd_process_8(PokeyState* ps, void* sndbuffer, int sndn); 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); From 210a002ae41804ed5a015920388c4b0d5a0b9c98 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 20 Dec 2022 15:21:03 -0500 Subject: [PATCH 35/93] POKEY: per-chan osc --- src/engine/platform/pokey.cpp | 11 ++++++++++- src/engine/platform/pokey.h | 1 + 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/engine/platform/pokey.cpp b/src/engine/platform/pokey.cpp index 654ceaf4..6a22c0e4 100644 --- a/src/engine/platform/pokey.cpp +++ b/src/engine/platform/pokey.cpp @@ -74,6 +74,14 @@ void DivPlatformPOKEY::acquire(short* bufL, short* bufR, size_t start, size_t le } mzpokeysnd_process_16(&pokey,&bufL[h],1); + + if (++oscBufDelay>=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; + } } } @@ -408,7 +416,7 @@ void DivPlatformPOKEY::setFlags(const DivConfig& flags) { CHECK_CUSTOM_CLOCK; rate=chipClock; for (int i=0; i<4; i++) { - oscBuf[i]->rate=rate/16; + oscBuf[i]->rate=rate/14; } } @@ -424,6 +432,7 @@ int DivPlatformPOKEY::init(DivEngine* p, int channels, int sugRate, const DivCon parent=p; dumpWrites=false; skipRegisterWrites=false; + oscBufDelay=0; for (int i=0; i<4; i++) { isMuted[i]=false; oscBuf[i]=new DivDispatchOscBuffer; diff --git a/src/engine/platform/pokey.h b/src/engine/platform/pokey.h index 34751677..6e17145f 100644 --- a/src/engine/platform/pokey.h +++ b/src/engine/platform/pokey.h @@ -47,6 +47,7 @@ class DivPlatformPOKEY: public DivDispatch { std::queue writes; unsigned char audctl; bool audctlChanged; + unsigned char oscBufDelay; PokeyState pokey; unsigned char regPool[16]; friend void putDispatchChip(void*,int); From 1bd9bca2457b4e9d7d465eee98aff5acfa50f4ab Mon Sep 17 00:00:00 2001 From: Epictyphlosion <34047941+Epictyphlosion@users.noreply.github.com> Date: Tue, 20 Dec 2022 13:23:53 -0800 Subject: [PATCH 36/93] Add two presets, removed some dupes Added a Neo Geo Pocket and Atari 7800 with Ballblazer or Commando preset. Removed Touchdown Fever presets as they were duplicates of the Triple Z80 presets. --- src/gui/presets.cpp | 43 +++++++++++++++++++------------------------ 1 file changed, 19 insertions(+), 24 deletions(-) diff --git a/src/gui/presets.cpp b/src/gui/presets.cpp index 917b74de..920bc716 100644 --- a/src/gui/presets.cpp +++ b/src/gui/presets.cpp @@ -114,6 +114,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 +203,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, "") @@ -2187,30 +2206,6 @@ void FurnaceGUI::initSystemPresets() { CH(DIV_SYSTEM_OPL2_DRUMS, 64, 0, "clockSel=2") } ); - ENTRY( - "SNK Touchdown Fever", { - CH(DIV_SYSTEM_OPL, 64, 0, "clockSel=2"), - CH(DIV_SYSTEM_Y8950, 64, 0, "clockSel=2") - } - ); - ENTRY( - "SNK Touchdown Fever (drums mode on OPL)", { - CH(DIV_SYSTEM_OPL_DRUMS, 64, 0, "clockSel=2"), - CH(DIV_SYSTEM_Y8950, 64, 0, "clockSel=2") - } - ); - ENTRY( - "SNK Touchdown Fever (drums mode on Y8950)", { - CH(DIV_SYSTEM_OPL, 64, 0, "clockSel=2"), - CH(DIV_SYSTEM_Y8950_DRUMS, 64, 0, "clockSel=2") - } - ); - ENTRY( - "SNK Touchdown Fever (drums mode on OPL and Y8950)", { - CH(DIV_SYSTEM_OPL_DRUMS, 64, 0, "clockSel=2"), - CH(DIV_SYSTEM_Y8950_DRUMS, 64, 0, "clockSel=2") - } - ); ENTRY( "Alpha denshi Alpha-68K", { CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=3"), // 3MHz From 36a62b9d6e01b322c95f5bef7be12c206cc0d66b Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 20 Dec 2022 16:33:57 -0500 Subject: [PATCH 37/93] =?UTF-8?q?Pok=C3=A9mon=20Mini:=20register=20layout?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TODO.md | 3 -- src/engine/platform/pokemini.cpp | 81 +++++++++++++++++++++++--------- src/engine/platform/pokemini.h | 4 +- 3 files changed, 63 insertions(+), 25 deletions(-) diff --git a/TODO.md b/TODO.md index dafe2873..0b92cc7e 100644 --- a/TODO.md +++ b/TODO.md @@ -1,8 +1,5 @@ # to-do for 0.6pre2 -- PokĂ©mon Mini - - register layout - - confirm emulation - (maybe) YM2612 CSM (no DualPCM) - port op macro code to all other OPN chips - bug fixes 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); From 2fbcacfbd0a34b79b19125b6dbed219b187fe9cd Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 20 Dec 2022 16:56:44 -0500 Subject: [PATCH 38/93] update format.md change OPN to YM2203 and PC-98 to YM2608 --- papers/format.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/papers/format.md b/papers/format.md index e7bf1de6..72122e0a 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 From 34f6a303a6dedd692e5d2e69161e2a06399752e9 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 20 Dec 2022 17:40:45 -0500 Subject: [PATCH 39/93] prepare for more CSM --- src/engine/platform/fmshared_OPN.h | 4 +- src/engine/platform/genesis.cpp | 8 ++-- src/engine/platform/genesis.h | 2 +- src/engine/platform/genesisext.cpp | 26 ++++++------- src/engine/song.h | 7 +++- src/engine/sysDef.cpp | 60 ++++++++++++++++++++++++++++++ 6 files changed, 87 insertions(+), 20 deletions(-) diff --git a/src/engine/platform/fmshared_OPN.h b/src/engine/platform/fmshared_OPN.h index a8ec6772..769a1814 100644 --- a/src/engine/platform/fmshared_OPN.h +++ b/src/engine/platform/fmshared_OPN.h @@ -147,17 +147,19 @@ class DivPlatformOPN: public DivPlatformFMBase { double fmFreqBase; unsigned int fmDivBase; unsigned int ayDiv; + unsigned char csmChan; 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), extSys(isExtSys) {} }; diff --git a/src/engine/platform/genesis.cpp b/src/engine/platform/genesis.cpp index 288218da..c375e51f 100644 --- a/src/engine/platform/genesis.cpp +++ b/src/engine/platform/genesis.cpp @@ -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 && softPCM) { // CSM chan[c.chan].macroInit(ins); chan[c.chan].insChanged=false; @@ -686,7 +686,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) { @@ -781,7 +781,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) { @@ -850,7 +850,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); diff --git a/src/engine/platform/genesis.h b/src/engine/platform/genesis.h index bd5d9aff..a6b7eec6 100644 --- a/src/engine/platform/genesis.h +++ b/src/engine/platform/genesis.h @@ -128,7 +128,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..6443729f 100644 --- a/src/engine/platform/genesisext.cpp +++ b/src/engine/platform/genesisext.cpp @@ -454,7 +454,7 @@ void DivPlatformGenesisExt::tick(bool sysTick) { } } if (writeSomething) { - if (chan[7].active) { // CSM + if (chan[csmChan].active) { // CSM writeMask^=0xf0; } /*printf( @@ -589,17 +589,17 @@ 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 (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 +608,7 @@ void DivPlatformGenesisExt::tick(bool sysTick) { } if (writeNoteOn) { - if (chan[7].active) { // CSM + if (chan[csmChan].active) { // CSM writeMask^=0xf0; } /*printf( @@ -622,13 +622,13 @@ void DivPlatformGenesisExt::tick(bool sysTick) { } if (extMode && softPCM) { - if (chan[7].keyOn) { + 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; } } } diff --git a/src/engine/song.h b/src/engine/song.h index cd2d59d0..d564f3da 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -119,7 +119,12 @@ enum DivSystem { DIV_SYSTEM_GA20, DIV_SYSTEM_PCM_DAC, DIV_SYSTEM_PONG, - DIV_SYSTEM_DUMMY + DIV_SYSTEM_DUMMY, + DIV_SYSTEM_YM2612_CSM, + DIV_SYSTEM_YM2610_CSM, + DIV_SYSTEM_YM2610B_CSM, + DIV_SYSTEM_OPN_CSM, + DIV_SYSTEM_PC98_CSM }; struct DivSubSong { diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index bfade1f0..359a4648 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -1030,6 +1030,18 @@ void DivEngine::registerSystems() { fmOPNPostEffectHandlerMap ); + sysDefs[DIV_SYSTEM_OPN_CSM]=new DivSysDef( + "Yamaha YM2203 (OPN) CSM", NULL, 0xc3, 0, 10, true, true, 0x151, false, 1U< Date: Tue, 20 Dec 2022 17:41:32 -0500 Subject: [PATCH 40/93] update to-do list --- TODO.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TODO.md b/TODO.md index 0b92cc7e..c03365e4 100644 --- a/TODO.md +++ b/TODO.md @@ -1,5 +1,5 @@ # to-do for 0.6pre2 -- (maybe) YM2612 CSM (no DualPCM) +- YM2612 CSM (no DualPCM) - port op macro code to all other OPN chips - bug fixes From a4bfbfe8d95e0d58936eca24a93aaf4307ccb730 Mon Sep 17 00:00:00 2001 From: Epictyphlosion <34047941+Epictyphlosion@users.noreply.github.com> Date: Tue, 20 Dec 2022 14:48:30 -0800 Subject: [PATCH 41/93] Fixed missing commas + Atari arcade presets --- src/gui/presets.cpp | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/src/gui/presets.cpp b/src/gui/presets.cpp index 920bc716..12f5244f 100644 --- a/src/gui/presets.cpp +++ b/src/gui/presets.cpp @@ -116,11 +116,11 @@ void FurnaceGUI::initSystemPresets() { ); ENTRY( "Neo Geo Pocket", { - CH(DIV_SYSTEM_T6W28, 64, 0, "") + 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" @@ -205,7 +205,7 @@ void FurnaceGUI::initSystemPresets() { ); ENTRY( "Atari 7800 + Ballblazer/Commando", { - CH(DIV_SYSTEM_TIA, 64, 0, "") + CH(DIV_SYSTEM_TIA, 64, 0, ""), CH(DIV_SYSTEM_POKEY, 64, 0, "") } ); @@ -1671,6 +1671,25 @@ void FurnaceGUI::initSystemPresets() { CH(DIV_SYSTEM_MSM6295, 64, 0, "clockSel=1") } ); + ENTRY( + "Atari Marble Madness", { + CH(DIV_SYSTEM_YM2151, 64, 0, ""), // clock should be 3.579 MHz + CH(DIV_SYSTEM_POKEY, 64, 0, "") + } + ); + ENTRY( + "Atari Championship Sprint", { + CH(DIV_SYSTEM_YM2151, 64, 0, ""), // clock should be 3.579 MHz + 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( "Sega Kyugo", { CH(DIV_SYSTEM_AY8910, 64, 0, "clockSel=14"), From 7a92811fd978c75775fb5a16a9b2ed6c5a0d83a6 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 20 Dec 2022 17:49:53 -0500 Subject: [PATCH 42/93] rename some DIV_SYSTEM_ items OPN -> YM2203 PC98 -> YM2608 FRAC -> DUALPCM --- src/engine/dispatchContainer.cpp | 16 +- src/engine/engine.cpp | 2 +- src/engine/fileOps.cpp | 56 +++--- src/engine/song.h | 24 +-- src/engine/sysDef.cpp | 30 +-- src/engine/vgmOps.cpp | 52 +++--- src/gui/debug.cpp | 32 ++-- src/gui/guiConst.cpp | 32 ++-- src/gui/presets.cpp | 304 +++++++++++++++---------------- src/gui/settings.cpp | 6 +- src/gui/sysConf.cpp | 22 +-- 11 files changed, 288 insertions(+), 288 deletions(-) diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index b578f988..d8f335f0 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -191,22 +191,22 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do dispatch=new DivPlatformOPL; ((DivPlatformOPL*)dispatch)->setOPLType(759,false); break; - case DIV_SYSTEM_YM2612: + case DIV_SYSTEM_YM2612_DUALPCM: dispatch=new DivPlatformGenesis; ((DivPlatformGenesis*)dispatch)->setYMFM(eng->getConfInt("ym2612Core",0)); ((DivPlatformGenesis*)dispatch)->setSoftPCM(false); break; - case DIV_SYSTEM_YM2612_EXT: + case DIV_SYSTEM_YM2612_DUALPCM_EXT: dispatch=new DivPlatformGenesisExt; ((DivPlatformGenesisExt*)dispatch)->setYMFM(eng->getConfInt("ym2612Core",0)); ((DivPlatformGenesisExt*)dispatch)->setSoftPCM(false); break; - case DIV_SYSTEM_YM2612_FRAC: + case DIV_SYSTEM_YM2612_DUALPCM_FRAC: 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_FRAC_EXT: dispatch=new DivPlatformGenesisExt; ((DivPlatformGenesisExt*)dispatch)->setYMFM(eng->getConfInt("ym2612Core",0)); ((DivPlatformGenesisExt*)dispatch)->setSoftPCM(true); @@ -269,16 +269,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: diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 6ee8513f..a50f5599 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -2440,7 +2440,7 @@ int DivEngine::getEffectiveSampleRate(int rate) { switch (song.system[0]) { case DIV_SYSTEM_YMU759: return 8000; - case DIV_SYSTEM_YM2612: case DIV_SYSTEM_YM2612_EXT: + case DIV_SYSTEM_YM2612_DUALPCM: case DIV_SYSTEM_YM2612_DUALPCM_EXT: return 1278409/(1280000/rate); case DIV_SYSTEM_PCE: return 1789773/(1789773/rate); diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index b21d3a0d..7bbadb20 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -937,13 +937,13 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { // handle compound systems if (ds.system[0]==DIV_SYSTEM_GENESIS) { ds.systemLen=2; - ds.system[0]=DIV_SYSTEM_YM2612; + ds.system[0]=DIV_SYSTEM_YM2612_DUALPCM; ds.system[1]=DIV_SYSTEM_SMS; ds.systemVol[1]=32; } if (ds.system[0]==DIV_SYSTEM_GENESIS_EXT) { ds.systemLen=2; - ds.system[0]=DIV_SYSTEM_YM2612_EXT; + ds.system[0]=DIV_SYSTEM_YM2612_DUALPCM_EXT; ds.system[1]=DIV_SYSTEM_SMS; ds.systemVol[1]=32; } @@ -1205,10 +1205,10 @@ void DivEngine::convertOldFlags(unsigned int oldFlags, DivConfig& newFlags, DivS break; } 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: + case DIV_SYSTEM_YM2612_DUALPCM_FRAC: + case DIV_SYSTEM_YM2612_DUALPCM_FRAC_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); @@ -1857,14 +1857,14 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { if (++ds.systemLen>DIV_MAX_CHIPS) ds.systemLen=DIV_MAX_CHIPS; if (ds.system[i]==DIV_SYSTEM_GENESIS) { - ds.system[i]=DIV_SYSTEM_YM2612; + ds.system[i]=DIV_SYSTEM_YM2612_DUALPCM; if (i<31) { ds.system[i+1]=DIV_SYSTEM_SMS; ds.systemVol[i+1]=(((ds.systemVol[i]&127)*3)>>3)|(ds.systemVol[i]&128); } } if (ds.system[i]==DIV_SYSTEM_GENESIS_EXT) { - ds.system[i]=DIV_SYSTEM_YM2612_EXT; + ds.system[i]=DIV_SYSTEM_YM2612_DUALPCM_EXT; if (i<31) { ds.system[i+1]=DIV_SYSTEM_SMS; ds.systemVol[i+1]=(((ds.systemVol[i]&127)*3)>>3)|(ds.systemVol[i]&128); @@ -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_YM2612: - case DIV_SYSTEM_YM2612_EXT: - case DIV_SYSTEM_YM2612_FRAC: - case DIV_SYSTEM_YM2612_FRAC_EXT: + case DIV_SYSTEM_YM2203: + case DIV_SYSTEM_YM2203_EXT: + case DIV_SYSTEM_YM2608: + case DIV_SYSTEM_YM2608_EXT: + case DIV_SYSTEM_YM2612_DUALPCM: + case DIV_SYSTEM_YM2612_DUALPCM_EXT: + case DIV_SYSTEM_YM2612_DUALPCM_FRAC: + case DIV_SYSTEM_YM2612_DUALPCM_FRAC_EXT: opnCount++; break; default: @@ -2513,13 +2513,13 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { // ExtCh compat flag if (ds.version<125) { for (int i=0; iwriteC(version); DivSystem sys=DIV_SYSTEM_NULL; - if (song.system[0]==DIV_SYSTEM_YM2612 && song.system[1]==DIV_SYSTEM_SMS) { + if (song.system[0]==DIV_SYSTEM_YM2612_DUALPCM && song.system[1]==DIV_SYSTEM_SMS) { w->writeC(systemToFileDMF(DIV_SYSTEM_GENESIS)); sys=DIV_SYSTEM_GENESIS; - } else if (song.system[0]==DIV_SYSTEM_YM2612_EXT && song.system[1]==DIV_SYSTEM_SMS) { + } else if (song.system[0]==DIV_SYSTEM_YM2612_DUALPCM_EXT && song.system[1]==DIV_SYSTEM_SMS) { w->writeC(systemToFileDMF(DIV_SYSTEM_GENESIS_EXT)); sys=DIV_SYSTEM_GENESIS_EXT; } else if (song.system[0]==DIV_SYSTEM_YM2151 && song.system[1]==DIV_SYSTEM_SEGAPCM_COMPAT) { diff --git a/src/engine/song.h b/src/engine/song.h index d564f3da..17e39acb 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -52,7 +52,7 @@ enum DivSystem { DIV_SYSTEM_AY8910, DIV_SYSTEM_AMIGA, DIV_SYSTEM_YM2151, - DIV_SYSTEM_YM2612, + DIV_SYSTEM_YM2612_DUALPCM, DIV_SYSTEM_TIA, DIV_SYSTEM_SAA1099, DIV_SYSTEM_AY8930, @@ -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, @@ -83,7 +83,7 @@ enum DivSystem { DIV_SYSTEM_VRC7, DIV_SYSTEM_YM2610B, DIV_SYSTEM_SFX_BEEPER, - DIV_SYSTEM_YM2612_EXT, + DIV_SYSTEM_YM2612_DUALPCM_EXT, DIV_SYSTEM_SCC, DIV_SYSTEM_OPL_DRUMS, DIV_SYSTEM_OPL2_DRUMS, @@ -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_FRAC, + DIV_SYSTEM_YM2612_DUALPCM_FRAC_EXT, DIV_SYSTEM_MSM5232, DIV_SYSTEM_T6W28, DIV_SYSTEM_K007232, @@ -120,11 +120,11 @@ enum DivSystem { DIV_SYSTEM_PCM_DAC, DIV_SYSTEM_PONG, DIV_SYSTEM_DUMMY, - DIV_SYSTEM_YM2612_CSM, + DIV_SYSTEM_YM2612_DUALPCM_CSM, DIV_SYSTEM_YM2610_CSM, DIV_SYSTEM_YM2610B_CSM, - DIV_SYSTEM_OPN_CSM, - DIV_SYSTEM_PC98_CSM + DIV_SYSTEM_YM2203_CSM, + DIV_SYSTEM_YM2608_CSM }; struct DivSubSong { @@ -442,7 +442,7 @@ struct DivSong { systemPan[i]=0; } subsong.push_back(new DivSubSong); - system[0]=DIV_SYSTEM_YM2612; + system[0]=DIV_SYSTEM_YM2612_DUALPCM; system[1]=DIV_SYSTEM_SMS; // OPLL default instrument contest winner - piano_guitar_idk by Weeppiko diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index 359a4648..f3e80073 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -128,7 +128,7 @@ String DivEngine::getSongSystemLegacyName(DivSong& ds, bool isMultiSystemAccepta } break; } - } else if (ds.system[0]==DIV_SYSTEM_YM2612) { + } else if (ds.system[0]==DIV_SYSTEM_YM2612_DUALPCM) { switch (ds.systemFlags[0].getInt("clockSel",0)) { case 2: return "FM Towns"; @@ -146,10 +146,10 @@ String DivEngine::getSongSystemLegacyName(DivSong& ds, bool isMultiSystemAccepta } return getSystemName(ds.system[0]); case 2: - if (ds.system[0]==DIV_SYSTEM_YM2612 && ds.system[1]==DIV_SYSTEM_SMS) { + if (ds.system[0]==DIV_SYSTEM_YM2612_DUALPCM && ds.system[1]==DIV_SYSTEM_SMS) { return "Sega Genesis/Mega Drive"; } - if (ds.system[0]==DIV_SYSTEM_YM2612_EXT && ds.system[1]==DIV_SYSTEM_SMS) { + if (ds.system[0]==DIV_SYSTEM_YM2612_DUALPCM_EXT && ds.system[1]==DIV_SYSTEM_SMS) { return "Sega Genesis Extended Channel 3"; } @@ -293,7 +293,7 @@ const char* DivEngine::getSystemNameJ(DivSystem sys) { return ""; case DIV_SYSTEM_YM2151: return ""; - case DIV_SYSTEM_YM2612: + case DIV_SYSTEM_YM2612_DUALPCM: return ""; case DIV_SYSTEM_TIA: return ""; @@ -811,7 +811,7 @@ void DivEngine::registerSystems() { fmOPMPostEffectHandlerMap ); - sysDefs[DIV_SYSTEM_YM2612]=new DivSysDef( + sysDefs[DIV_SYSTEM_YM2612_DUALPCM]=new DivSysDef( "Yamaha YM2612 (OPN2)", NULL, 0x83, 0, 6, true, false, 0x150, 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); @@ -571,10 +571,10 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write return; } 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: + case DIV_SYSTEM_YM2612_DUALPCM_FRAC: + case DIV_SYSTEM_YM2612_DUALPCM_FRAC_EXT: switch (write.addr>>8) { case 0: // port 0 w->writeC(2|baseAddr1); @@ -666,14 +666,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); @@ -1178,10 +1178,10 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p howManyChips++; } 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: + case DIV_SYSTEM_YM2612_DUALPCM_FRAC: + case DIV_SYSTEM_YM2612_DUALPCM_FRAC_EXT: if (!hasOPN2) { hasOPN2=disCont[i].dispatch->chipClock; willExport[i]=true; @@ -1204,8 +1204,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; @@ -1217,8 +1217,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; @@ -1846,10 +1846,10 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p if (!willExport[i]) continue; streamIDs[i]=streamID; 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: + case DIV_SYSTEM_YM2612_DUALPCM_FRAC: + case DIV_SYSTEM_YM2612_DUALPCM_FRAC_EXT: w->writeC(0x90); w->writeC(streamID); w->writeC(0x02); diff --git a/src/gui/debug.cpp b/src/gui/debug.cpp index c008770f..0eab7c45 100644 --- a/src/gui/debug.cpp +++ b/src/gui/debug.cpp @@ -239,10 +239,10 @@ void putDispatchChip(void* data, int type) { ImVec4 colorOn=ImVec4(1.0f,1.0f,0.0f,1.0f); ImVec4 colorOff=ImVec4(0.3f,0.3f,0.3f,1.0f); 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: + case DIV_SYSTEM_YM2612_DUALPCM_FRAC: + case DIV_SYSTEM_YM2612_DUALPCM_FRAC_EXT: { GENESIS_CHIP_DEBUG; break; } @@ -256,8 +256,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; @@ -267,8 +267,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; @@ -540,13 +540,13 @@ void putDispatchChan(void* data, int chanNum, int type) { } break; } - case DIV_SYSTEM_YM2612: - case DIV_SYSTEM_YM2612_FRAC: { + case DIV_SYSTEM_YM2612_DUALPCM: + case DIV_SYSTEM_YM2612_DUALPCM_FRAC: { GENESIS_CHAN_DEBUG; break; } - case DIV_SYSTEM_YM2612_EXT: - case DIV_SYSTEM_YM2612_FRAC_EXT: { + case DIV_SYSTEM_YM2612_DUALPCM_EXT: + case DIV_SYSTEM_YM2612_DUALPCM_FRAC_EXT: { if (chanNum>=2 && chanNum<=5) { DivPlatformOPN::OPNOpChannelStereo* ch=(DivPlatformOPN::OPNOpChannelStereo*)data; ImGui::Text("> YM2612 (per operator)"); @@ -560,11 +560,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 { @@ -572,13 +572,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/guiConst.cpp b/src/gui/guiConst.cpp index c442a22d..97844c27 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -911,10 +911,10 @@ const FurnaceGUIColorDef guiColors[GUI_COLOR_MAX]={ // all chips const int availableSystems[]={ - DIV_SYSTEM_YM2612, - DIV_SYSTEM_YM2612_EXT, - DIV_SYSTEM_YM2612_FRAC, - DIV_SYSTEM_YM2612_FRAC_EXT, + DIV_SYSTEM_YM2612_DUALPCM, + DIV_SYSTEM_YM2612_DUALPCM_EXT, + DIV_SYSTEM_YM2612_DUALPCM_FRAC, + DIV_SYSTEM_YM2612_DUALPCM_FRAC_EXT, DIV_SYSTEM_SMS, DIV_SYSTEM_GB, DIV_SYSTEM_PCE, @@ -939,10 +939,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, @@ -992,10 +992,10 @@ const int availableSystems[]={ // FM const int chipsFM[]={ - DIV_SYSTEM_YM2612, - DIV_SYSTEM_YM2612_EXT, - DIV_SYSTEM_YM2612_FRAC, - DIV_SYSTEM_YM2612_FRAC_EXT, + DIV_SYSTEM_YM2612_DUALPCM, + DIV_SYSTEM_YM2612_DUALPCM_EXT, + DIV_SYSTEM_YM2612_DUALPCM_FRAC, + DIV_SYSTEM_YM2612_DUALPCM_FRAC_EXT, DIV_SYSTEM_YM2151, DIV_SYSTEM_YM2610, DIV_SYSTEM_YM2610_EXT, @@ -1004,10 +1004,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, diff --git a/src/gui/presets.cpp b/src/gui/presets.cpp index 917b74de..9ce238b8 100644 --- a/src/gui/presets.cpp +++ b/src/gui/presets.cpp @@ -45,31 +45,31 @@ void FurnaceGUI::initSystemPresets() { CATEGORY_BEGIN("Game consoles","let's play some chiptune making games!"); ENTRY( "Sega Genesis", { - CH(DIV_SYSTEM_YM2612, 64, 0, ""), + CH(DIV_SYSTEM_YM2612_DUALPCM, 64, 0, ""), CH(DIV_SYSTEM_SMS, 32, 0, "") } ); ENTRY( "Sega Genesis (extended channel 3)", { - CH(DIV_SYSTEM_YM2612_EXT, 64, 0, ""), + CH(DIV_SYSTEM_YM2612_DUALPCM_EXT, 64, 0, ""), CH(DIV_SYSTEM_SMS, 32, 0, "") } ); ENTRY( "Sega Genesis (DualPCM)", { - CH(DIV_SYSTEM_YM2612_FRAC, 64, 0, ""), + CH(DIV_SYSTEM_YM2612_DUALPCM_FRAC, 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_FRAC_EXT, 64, 0, ""), CH(DIV_SYSTEM_SMS, 32, 0, "") } ); ENTRY( "Sega Genesis (with Sega CD)", { - CH(DIV_SYSTEM_YM2612, 64, 0, ""), + CH(DIV_SYSTEM_YM2612_DUALPCM, 64, 0, ""), CH(DIV_SYSTEM_SMS, 32, 0, ""), CH(DIV_SYSTEM_RF5C68, 64, 0, "clockSel=2\n" @@ -79,7 +79,7 @@ void FurnaceGUI::initSystemPresets() { ); ENTRY( "Sega Genesis (extended channel 3 with Sega CD)", { - CH(DIV_SYSTEM_YM2612_EXT, 64, 0, ""), + CH(DIV_SYSTEM_YM2612_DUALPCM_EXT, 64, 0, ""), CH(DIV_SYSTEM_SMS, 32, 0, ""), CH(DIV_SYSTEM_RF5C68, 64, 0, "clockSel=2\n" @@ -421,25 +421,25 @@ void FurnaceGUI::initSystemPresets() { ENTRY( "NEC PC-88 (with PC-8801-11)", { CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1"), - CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=4") // external + 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_OPN_EXT, 64, 0, "clockSel=4") // external + 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_PC98, 64, 0, "clockSel=1") // external + 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_PC98_EXT, 64, 0, "clockSel=1") // external + CH(DIV_SYSTEM_YM2608_EXT, 64, 0, "clockSel=1") // external } ); ENTRY( @@ -451,7 +451,7 @@ void FurnaceGUI::initSystemPresets() { ENTRY( "NEC PC-8801mk2SR (with PC-8801-10)", { CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1"), - CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=4"), // internal + 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") // "" } @@ -459,7 +459,7 @@ void FurnaceGUI::initSystemPresets() { ENTRY( "NEC PC-8801mk2SR (with PC-8801-10; extended channel 3)", { CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1"), - CH(DIV_SYSTEM_OPN_EXT, 64, 0, "clockSel=4"), // internal + 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") // "" } @@ -467,77 +467,77 @@ void FurnaceGUI::initSystemPresets() { ENTRY( "NEC PC-8801mk2SR (with PC-8801-11)", { CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1"), - CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=4"), // internal - CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=4") // external + 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_OPN_EXT, 64, 0, "clockSel=4"), // internal - CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=4") // external + 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_OPN, 64, 0, "clockSel=4"), // internal - CH(DIV_SYSTEM_OPN_EXT, 64, 0, "clockSel=4") // external + 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_OPN_EXT, 64, 0, "clockSel=4"), // internal - CH(DIV_SYSTEM_OPN_EXT, 64, 0, "clockSel=4") // external + 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_OPN, 64, 0, "clockSel=4"), // internal - CH(DIV_SYSTEM_PC98, 64, 0, "clockSel=1") // external + 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_OPN_EXT, 64, 0, "clockSel=4"), // internal - CH(DIV_SYSTEM_PC98, 64, 0, "clockSel=1") // external + 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_OPN, 64, 0, "clockSel=4"), // internal - CH(DIV_SYSTEM_PC98_EXT, 64, 0, "clockSel=1") // external + 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_OPN_EXT, 64, 0, "clockSel=4"), // internal - CH(DIV_SYSTEM_PC98_EXT, 64, 0, "clockSel=1") // external + 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_OPN, 64, 0, "clockSel=4"), // internal + 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_OPN_EXT, 64, 0, "clockSel=4"), // internal + 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_PC98, 64, 0, "clockSel=4"), // internal + 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") // "" } @@ -545,7 +545,7 @@ void FurnaceGUI::initSystemPresets() { ENTRY( "NEC PC-8801FA (with PC-8801-10; extended channel 3)", { CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1"), - CH(DIV_SYSTEM_PC98_EXT, 64, 0, "clockSel=4"), // internal + 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") // "" } @@ -553,144 +553,144 @@ void FurnaceGUI::initSystemPresets() { ENTRY( "NEC PC-8801FA (with PC-8801-11)", { CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1"), - CH(DIV_SYSTEM_PC98, 64, 0, "clockSel=1"), // internal - CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=4") // external + 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_PC98_EXT, 64, 0, "clockSel=1"), // internal - CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=4") // external + 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_PC98, 64, 0, "clockSel=1"), // internal - CH(DIV_SYSTEM_OPN_EXT, 64, 0, "clockSel=4") // external + 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_PC98_EXT, 64, 0, "clockSel=1"), // internal - CH(DIV_SYSTEM_OPN_EXT, 64, 0, "clockSel=4") // external + 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_PC98, 64, 0, "clockSel=1"), // internal - CH(DIV_SYSTEM_PC98, 64, 0, "clockSel=1") // external + 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_PC98_EXT, 64, 0, "clockSel=1"), // internal - CH(DIV_SYSTEM_PC98, 64, 0, "clockSel=1") // external + 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_PC98, 64, 0, "clockSel=1"), // internal - CH(DIV_SYSTEM_PC98_EXT, 64, 0, "clockSel=1") // external + 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_PC98_EXT, 64, 0, "clockSel=1"), // internal - CH(DIV_SYSTEM_PC98_EXT, 64, 0, "clockSel=1") // external + 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_PC98, 64, 0, "clockSel=1"), // internal + 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_PC98_EXT, 64, 0, "clockSel=1"), // internal + 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" @@ -704,7 +704,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" @@ -718,19 +718,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" @@ -742,7 +742,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" @@ -754,7 +754,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" @@ -766,7 +766,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" @@ -789,29 +789,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( @@ -1047,13 +1047,13 @@ void FurnaceGUI::initSystemPresets() { ); ENTRY( "FM Towns", { - CH(DIV_SYSTEM_YM2612, 64, 0, "clockSel=2"), // YM3438 + CH(DIV_SYSTEM_YM2612_DUALPCM, 64, 0, "clockSel=2"), // YM3438 CH(DIV_SYSTEM_RF5C68, 64, 0, "") } ); ENTRY( "FM Towns (extended channel 3)", { - CH(DIV_SYSTEM_YM2612_EXT, 64, 0, "clockSel=2"), // YM3438 + CH(DIV_SYSTEM_YM2612_DUALPCM_EXT, 64, 0, "clockSel=2"), // YM3438 CH(DIV_SYSTEM_RF5C68, 64, 0, "") } ); @@ -1081,22 +1081,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( @@ -1121,22 +1121,22 @@ void FurnaceGUI::initSystemPresets() { ); ENTRY( "Yamaha YM2612 (OPN2)", { - CH(DIV_SYSTEM_YM2612, 64, 0, "ladderEffect=true") + CH(DIV_SYSTEM_YM2612_DUALPCM, 64, 0, "ladderEffect=true") } ); ENTRY( "Yamaha YM2612 (extended channel 3)", { - CH(DIV_SYSTEM_YM2612_EXT, 64, 0, "ladderEffect=true") + CH(DIV_SYSTEM_YM2612_DUALPCM_EXT, 64, 0, "ladderEffect=true") } ); ENTRY( "Yamaha YM2612 (OPN2) with DualPCM", { - CH(DIV_SYSTEM_YM2612_FRAC, 64, 0, "ladderEffect=true") + CH(DIV_SYSTEM_YM2612_DUALPCM_FRAC, 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_FRAC_EXT, 64, 0, "ladderEffect=true") } ); ENTRY( @@ -1156,22 +1156,22 @@ void FurnaceGUI::initSystemPresets() { ); ENTRY( "Yamaha YM3438 (OPN2C)", { - CH(DIV_SYSTEM_YM2612, 64, 0, "") + CH(DIV_SYSTEM_YM2612_DUALPCM, 64, 0, "") } ); ENTRY( "Yamaha YM3438 (extended channel 3)", { - CH(DIV_SYSTEM_YM2612_EXT, 64, 0, "") + CH(DIV_SYSTEM_YM2612_DUALPCM_EXT, 64, 0, "") } ); ENTRY( "Yamaha YM3438 (OPN2C) with DualPCM", { - CH(DIV_SYSTEM_YM2612_FRAC, 64, 0, "") + CH(DIV_SYSTEM_YM2612_DUALPCM_FRAC, 64, 0, "") } ); ENTRY( "Yamaha YM3438 (extended channel 3) with DualPCM", { - CH(DIV_SYSTEM_YM2612_FRAC_EXT, 64, 0, "") + CH(DIV_SYSTEM_YM2612_DUALPCM_FRAC_EXT, 64, 0, "") } ); ENTRY( @@ -1692,13 +1692,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 } ); @@ -1719,57 +1719,57 @@ void FurnaceGUI::initSystemPresets() { ); ENTRY( "Sega System 18", { - CH(DIV_SYSTEM_YM2612, 64, 0, "clockSel=2"), // discrete 8MHz YM3438 - CH(DIV_SYSTEM_YM2612, 64, 0, "clockSel=2"), // ^^ + CH(DIV_SYSTEM_YM2612_DUALPCM, 64, 0, "clockSel=2"), // discrete 8MHz YM3438 + CH(DIV_SYSTEM_YM2612_DUALPCM, 64, 0, "clockSel=2"), // ^^ CH(DIV_SYSTEM_RF5C68, 64, 0, "clockSel=1") // 10MHz } ); ENTRY( "Sega System 18 (extended channel 3 on first OPN2C)", { - CH(DIV_SYSTEM_YM2612_EXT, 64, 0, "clockSel=2"), // discrete 8MHz YM3438 - CH(DIV_SYSTEM_YM2612, 64, 0, "clockSel=2"), // ^^ + CH(DIV_SYSTEM_YM2612_DUALPCM_EXT, 64, 0, "clockSel=2"), // discrete 8MHz YM3438 + CH(DIV_SYSTEM_YM2612_DUALPCM, 64, 0, "clockSel=2"), // ^^ CH(DIV_SYSTEM_RF5C68, 64, 0, "clockSel=1") // 10MHz } ); ENTRY( "Sega System 18 (extended channel 3 on second OPN2C)", { - CH(DIV_SYSTEM_YM2612, 64, 0, "clockSel=2"), // discrete 8MHz YM3438 - CH(DIV_SYSTEM_YM2612_EXT, 64, 0, "clockSel=2"), // ^^ + CH(DIV_SYSTEM_YM2612_DUALPCM, 64, 0, "clockSel=2"), // discrete 8MHz YM3438 + CH(DIV_SYSTEM_YM2612_DUALPCM_EXT, 64, 0, "clockSel=2"), // ^^ CH(DIV_SYSTEM_RF5C68, 64, 0, "clockSel=1") // 10MHz } ); ENTRY( "Sega System 18 (extended channel 3 on both OPN2Cs)", { - CH(DIV_SYSTEM_YM2612_EXT, 64, 0, "clockSel=2"), // discrete 8MHz YM3438 - CH(DIV_SYSTEM_YM2612_EXT, 64, 0, "clockSel=2"), // ^^ + CH(DIV_SYSTEM_YM2612_DUALPCM_EXT, 64, 0, "clockSel=2"), // discrete 8MHz YM3438 + CH(DIV_SYSTEM_YM2612_DUALPCM_EXT, 64, 0, "clockSel=2"), // ^^ CH(DIV_SYSTEM_RF5C68, 64, 0, "clockSel=1") // 10MHz } ); ENTRY( "Sega System 32", { - CH(DIV_SYSTEM_YM2612, 64, 0, "clockSel=4"), // discrete 8.05MHz YM3438 - CH(DIV_SYSTEM_YM2612, 64, 0, "clockSel=4"), // ^^ + CH(DIV_SYSTEM_YM2612_DUALPCM, 64, 0, "clockSel=4"), // discrete 8.05MHz YM3438 + CH(DIV_SYSTEM_YM2612_DUALPCM, 64, 0, "clockSel=4"), // ^^ CH(DIV_SYSTEM_RF5C68, 64, 0, "clockSel=2") // 12.5MHz } ); ENTRY( "Sega System 32 (extended channel 3 on first OPN2C)", { - CH(DIV_SYSTEM_YM2612_EXT, 64, 0, "clockSel=4"), // discrete 8.05MHz YM3438 - CH(DIV_SYSTEM_YM2612, 64, 0, "clockSel=4"), // ^^ + CH(DIV_SYSTEM_YM2612_DUALPCM_EXT, 64, 0, "clockSel=4"), // discrete 8.05MHz YM3438 + CH(DIV_SYSTEM_YM2612_DUALPCM, 64, 0, "clockSel=4"), // ^^ CH(DIV_SYSTEM_RF5C68, 64, 0, "clockSel=2") // 12.5MHz } ); ENTRY( "Sega System 32 (extended channel 3 on second OPN2C)", { - CH(DIV_SYSTEM_YM2612, 64, 0, "clockSel=4"), // discrete 8.05MHz YM3438 - CH(DIV_SYSTEM_YM2612_EXT, 64, 0, "clockSel=4"), // ^^ + CH(DIV_SYSTEM_YM2612_DUALPCM, 64, 0, "clockSel=4"), // discrete 8.05MHz YM3438 + CH(DIV_SYSTEM_YM2612_DUALPCM_EXT, 64, 0, "clockSel=4"), // ^^ CH(DIV_SYSTEM_RF5C68, 64, 0, "clockSel=2") // 12.5MHz } ); ENTRY( "Sega System 32 (extended channel 3 on both OPN2Cs)", { - CH(DIV_SYSTEM_YM2612_EXT, 64, 0, "clockSel=4"), // discrete 8.05MHz YM3438 - CH(DIV_SYSTEM_YM2612_EXT, 64, 0, "clockSel=4"), // ^^ + CH(DIV_SYSTEM_YM2612_DUALPCM_EXT, 64, 0, "clockSel=4"), // discrete 8.05MHz YM3438 + CH(DIV_SYSTEM_YM2612_DUALPCM_EXT, 64, 0, "clockSel=4"), // ^^ CH(DIV_SYSTEM_RF5C68, 64, 0, "clockSel=2") // 12.5MHz } ); @@ -1788,26 +1788,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( @@ -1842,7 +1842,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" @@ -1855,7 +1855,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" @@ -1868,21 +1868,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" @@ -1891,7 +1891,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" @@ -1923,29 +1923,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 } ); @@ -1986,13 +1986,13 @@ void FurnaceGUI::initSystemPresets() { ); ENTRY( "Sunsoft Arcade", { - CH(DIV_SYSTEM_YM2612, 64, 0, "clockSel=2"), // discrete YM3438 8MHz + CH(DIV_SYSTEM_YM2612_DUALPCM, 64, 0, "clockSel=2"), // discrete YM3438 8MHz CH(DIV_SYSTEM_MSM6295, 64, 0, "clockSel=1") // 1.056MHz } ); ENTRY( "Sunsoft Arcade (extended channel 3)", { - CH(DIV_SYSTEM_YM2612_EXT, 64, 0, "clockSel=2"), // discrete YM3438 8MHz + CH(DIV_SYSTEM_YM2612_DUALPCM_EXT, 64, 0, "clockSel=2"), // discrete YM3438 8MHz CH(DIV_SYSTEM_MSM6295, 64, 0, "clockSel=1") // 1.056MHz } ); @@ -2022,66 +2022,66 @@ void FurnaceGUI::initSystemPresets() { ); 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 } @@ -2089,7 +2089,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 @@ -2098,7 +2098,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 @@ -2213,7 +2213,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" @@ -2223,7 +2223,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" @@ -2233,7 +2233,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" @@ -2243,7 +2243,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" @@ -2331,13 +2331,13 @@ void FurnaceGUI::initSystemPresets() { ENTRY( "Seta 1 + FM addon", { CH(DIV_SYSTEM_X1_010, 64, 0, ""), - CH(DIV_SYSTEM_YM2612, 64, 0, "clockSel=2") // Discrete YM3438 + CH(DIV_SYSTEM_YM2612_DUALPCM, 64, 0, "clockSel=2") // Discrete YM3438 } ); ENTRY( "Seta 1 + FM addon (extended channel 3)", { CH(DIV_SYSTEM_X1_010, 64, 0, ""), - CH(DIV_SYSTEM_YM2612_EXT, 64, 0, "clockSel=2") // Discrete YM3438 + CH(DIV_SYSTEM_YM2612_DUALPCM_EXT, 64, 0, "clockSel=2") // Discrete YM3438 } ); ENTRY( @@ -2432,13 +2432,13 @@ void FurnaceGUI::initSystemPresets() { CATEGORY_BEGIN("DefleMask-compatible","these configurations are compatible with DefleMask.\nselect this if you need to save as .dmf or work with that program."); ENTRY( "Sega Genesis", { - CH(DIV_SYSTEM_YM2612, 64, 0, ""), + CH(DIV_SYSTEM_YM2612_DUALPCM, 64, 0, ""), CH(DIV_SYSTEM_SMS, 32, 0, "") } ); ENTRY( "Sega Genesis (extended channel 3)", { - CH(DIV_SYSTEM_YM2612_EXT, 64, 0, ""), + CH(DIV_SYSTEM_YM2612_DUALPCM_EXT, 64, 0, ""), CH(DIV_SYSTEM_SMS, 32, 0, "") } ); diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index d40e4dac..7bed7025 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -333,7 +333,7 @@ void FurnaceGUI::drawSettings() { ImGui::SameLine(); if (ImGui::Button("Reset to defaults")) { settings.initialSys.clear(); - settings.initialSys.set("id0",e->systemToFileFur(DIV_SYSTEM_YM2612)); + settings.initialSys.set("id0",e->systemToFileFur(DIV_SYSTEM_YM2612_DUALPCM)); settings.initialSys.set("vol0",64); settings.initialSys.set("pan0",0); settings.initialSys.set("flags0",""); @@ -429,7 +429,7 @@ void FurnaceGUI::drawSettings() { } if (sysCount<32) if (ImGui::Button(ICON_FA_PLUS "##InitSysAdd")) { - settings.initialSys.set(fmt::sprintf("id%d",sysCount),(int)e->systemToFileFur(DIV_SYSTEM_YM2612)); + settings.initialSys.set(fmt::sprintf("id%d",sysCount),(int)e->systemToFileFur(DIV_SYSTEM_YM2612_DUALPCM)); settings.initialSys.set(fmt::sprintf("vol%d",sysCount),64); settings.initialSys.set(fmt::sprintf("pan%d",sysCount),0); settings.initialSys.set(fmt::sprintf("flags%d",sysCount),""); @@ -2566,7 +2566,7 @@ void FurnaceGUI::syncSettings() { settings.initialSys.loadFromBase64(initialSys2.c_str()); if (settings.initialSys.getInt("id0",0)==0) { settings.initialSys.clear(); - settings.initialSys.set("id0",e->systemToFileFur(DIV_SYSTEM_YM2612)); + settings.initialSys.set("id0",e->systemToFileFur(DIV_SYSTEM_YM2612_DUALPCM)); settings.initialSys.set("vol0",64); settings.initialSys.set("pan0",0); settings.initialSys.set("flags0",""); diff --git a/src/gui/sysConf.cpp b/src/gui/sysConf.cpp index b8e95a71..5337cb44 100644 --- a/src/gui/sysConf.cpp +++ b/src/gui/sysConf.cpp @@ -28,10 +28,10 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo bool supportsCustomRate=true; 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: + case DIV_SYSTEM_YM2612_DUALPCM_FRAC: + case DIV_SYSTEM_YM2612_DUALPCM_FRAC_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_DUALPCM_EXT || type==DIV_SYSTEM_YM2612_DUALPCM_FRAC_EXT) { if (ImGui::Checkbox("Disable ExtCh FM macros (compatibility)",&noExtMacros)) { altered=true; } @@ -834,8 +834,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); @@ -879,7 +879,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; } @@ -894,8 +894,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); @@ -923,7 +923,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; } From fe042b84cd80408d348949f367a34d5d9eb08d6e Mon Sep 17 00:00:00 2001 From: Epictyphlosion <34047941+Epictyphlosion@users.noreply.github.com> Date: Tue, 20 Dec 2022 14:50:49 -0800 Subject: [PATCH 43/93] Wait that's the default clock speed derp --- src/gui/presets.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gui/presets.cpp b/src/gui/presets.cpp index 12f5244f..a63f7e56 100644 --- a/src/gui/presets.cpp +++ b/src/gui/presets.cpp @@ -1673,13 +1673,13 @@ void FurnaceGUI::initSystemPresets() { ); ENTRY( "Atari Marble Madness", { - CH(DIV_SYSTEM_YM2151, 64, 0, ""), // clock should be 3.579 MHz + CH(DIV_SYSTEM_YM2151, 64, 0, ""), CH(DIV_SYSTEM_POKEY, 64, 0, "") } ); ENTRY( "Atari Championship Sprint", { - CH(DIV_SYSTEM_YM2151, 64, 0, ""), // clock should be 3.579 MHz + CH(DIV_SYSTEM_YM2151, 64, 0, ""), CH(DIV_SYSTEM_POKEY, 64, 0, ""), CH(DIV_SYSTEM_POKEY, 64, 0, "") } From 909c553be3eab6d7dc8e0c129374c61eeecee090 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 20 Dec 2022 17:55:05 -0500 Subject: [PATCH 44/93] whoops! --- src/engine/dispatchContainer.cpp | 8 ++-- src/engine/engine.cpp | 2 +- src/engine/fileOps.cpp | 28 ++++++------ src/engine/song.h | 12 ++--- src/engine/sysDef.cpp | 18 ++++---- src/engine/vgmOps.cpp | 16 +++---- src/gui/debug.cpp | 14 +++--- src/gui/guiConst.cpp | 8 ++-- src/gui/presets.cpp | 76 ++++++++++++++++---------------- src/gui/settings.cpp | 6 +-- src/gui/sysConf.cpp | 8 ++-- 11 files changed, 98 insertions(+), 98 deletions(-) diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index d8f335f0..710a2de4 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -191,22 +191,22 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do dispatch=new DivPlatformOPL; ((DivPlatformOPL*)dispatch)->setOPLType(759,false); break; - case DIV_SYSTEM_YM2612_DUALPCM: + case DIV_SYSTEM_YM2612: dispatch=new DivPlatformGenesis; ((DivPlatformGenesis*)dispatch)->setYMFM(eng->getConfInt("ym2612Core",0)); ((DivPlatformGenesis*)dispatch)->setSoftPCM(false); break; - case DIV_SYSTEM_YM2612_DUALPCM_EXT: + case DIV_SYSTEM_YM2612_EXT: dispatch=new DivPlatformGenesisExt; ((DivPlatformGenesisExt*)dispatch)->setYMFM(eng->getConfInt("ym2612Core",0)); ((DivPlatformGenesisExt*)dispatch)->setSoftPCM(false); break; - case DIV_SYSTEM_YM2612_DUALPCM_FRAC: + case DIV_SYSTEM_YM2612_DUALPCM: dispatch=new DivPlatformGenesis; ((DivPlatformGenesis*)dispatch)->setYMFM(eng->getConfInt("ym2612Core",0)); ((DivPlatformGenesis*)dispatch)->setSoftPCM(true); break; - case DIV_SYSTEM_YM2612_DUALPCM_FRAC_EXT: + case DIV_SYSTEM_YM2612_DUALPCM_EXT: dispatch=new DivPlatformGenesisExt; ((DivPlatformGenesisExt*)dispatch)->setYMFM(eng->getConfInt("ym2612Core",0)); ((DivPlatformGenesisExt*)dispatch)->setSoftPCM(true); diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index a50f5599..6ee8513f 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -2440,7 +2440,7 @@ int DivEngine::getEffectiveSampleRate(int rate) { switch (song.system[0]) { case DIV_SYSTEM_YMU759: return 8000; - case DIV_SYSTEM_YM2612_DUALPCM: case DIV_SYSTEM_YM2612_DUALPCM_EXT: + case DIV_SYSTEM_YM2612: case DIV_SYSTEM_YM2612_EXT: return 1278409/(1280000/rate); case DIV_SYSTEM_PCE: return 1789773/(1789773/rate); diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 7bbadb20..d56836bf 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -937,13 +937,13 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { // handle compound systems if (ds.system[0]==DIV_SYSTEM_GENESIS) { ds.systemLen=2; - ds.system[0]=DIV_SYSTEM_YM2612_DUALPCM; + ds.system[0]=DIV_SYSTEM_YM2612; ds.system[1]=DIV_SYSTEM_SMS; ds.systemVol[1]=32; } if (ds.system[0]==DIV_SYSTEM_GENESIS_EXT) { ds.systemLen=2; - ds.system[0]=DIV_SYSTEM_YM2612_DUALPCM_EXT; + ds.system[0]=DIV_SYSTEM_YM2612_EXT; ds.system[1]=DIV_SYSTEM_SMS; ds.systemVol[1]=32; } @@ -1205,10 +1205,10 @@ void DivEngine::convertOldFlags(unsigned int oldFlags, DivConfig& newFlags, DivS break; } break; + case DIV_SYSTEM_YM2612: + case DIV_SYSTEM_YM2612_EXT: case DIV_SYSTEM_YM2612_DUALPCM: case DIV_SYSTEM_YM2612_DUALPCM_EXT: - case DIV_SYSTEM_YM2612_DUALPCM_FRAC: - case DIV_SYSTEM_YM2612_DUALPCM_FRAC_EXT: switch (oldFlags&0x7fffffff) { case 0: newFlags.set("clockSel",0); @@ -1857,14 +1857,14 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { if (++ds.systemLen>DIV_MAX_CHIPS) ds.systemLen=DIV_MAX_CHIPS; if (ds.system[i]==DIV_SYSTEM_GENESIS) { - ds.system[i]=DIV_SYSTEM_YM2612_DUALPCM; + ds.system[i]=DIV_SYSTEM_YM2612; if (i<31) { ds.system[i+1]=DIV_SYSTEM_SMS; ds.systemVol[i+1]=(((ds.systemVol[i]&127)*3)>>3)|(ds.systemVol[i]&128); } } if (ds.system[i]==DIV_SYSTEM_GENESIS_EXT) { - ds.system[i]=DIV_SYSTEM_YM2612_DUALPCM_EXT; + ds.system[i]=DIV_SYSTEM_YM2612_EXT; if (i<31) { ds.system[i+1]=DIV_SYSTEM_SMS; ds.systemVol[i+1]=(((ds.systemVol[i]&127)*3)>>3)|(ds.systemVol[i]&128); @@ -2488,10 +2488,10 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { 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_DUALPCM: case DIV_SYSTEM_YM2612_DUALPCM_EXT: - case DIV_SYSTEM_YM2612_DUALPCM_FRAC: - case DIV_SYSTEM_YM2612_DUALPCM_FRAC_EXT: opnCount++; break; default: @@ -2513,8 +2513,8 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { // ExtCh compat flag if (ds.version<125) { for (int i=0; iwriteC(version); DivSystem sys=DIV_SYSTEM_NULL; - if (song.system[0]==DIV_SYSTEM_YM2612_DUALPCM && song.system[1]==DIV_SYSTEM_SMS) { + if (song.system[0]==DIV_SYSTEM_YM2612 && song.system[1]==DIV_SYSTEM_SMS) { w->writeC(systemToFileDMF(DIV_SYSTEM_GENESIS)); sys=DIV_SYSTEM_GENESIS; - } else if (song.system[0]==DIV_SYSTEM_YM2612_DUALPCM_EXT && song.system[1]==DIV_SYSTEM_SMS) { + } else if (song.system[0]==DIV_SYSTEM_YM2612_EXT && song.system[1]==DIV_SYSTEM_SMS) { w->writeC(systemToFileDMF(DIV_SYSTEM_GENESIS_EXT)); sys=DIV_SYSTEM_GENESIS_EXT; } else if (song.system[0]==DIV_SYSTEM_YM2151 && song.system[1]==DIV_SYSTEM_SEGAPCM_COMPAT) { diff --git a/src/engine/song.h b/src/engine/song.h index 17e39acb..d7c28736 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -52,7 +52,7 @@ enum DivSystem { DIV_SYSTEM_AY8910, DIV_SYSTEM_AMIGA, DIV_SYSTEM_YM2151, - DIV_SYSTEM_YM2612_DUALPCM, + DIV_SYSTEM_YM2612, DIV_SYSTEM_TIA, DIV_SYSTEM_SAA1099, DIV_SYSTEM_AY8930, @@ -83,7 +83,7 @@ enum DivSystem { DIV_SYSTEM_VRC7, DIV_SYSTEM_YM2610B, DIV_SYSTEM_SFX_BEEPER, - DIV_SYSTEM_YM2612_DUALPCM_EXT, + DIV_SYSTEM_YM2612_EXT, DIV_SYSTEM_SCC, DIV_SYSTEM_OPL_DRUMS, DIV_SYSTEM_OPL2_DRUMS, @@ -111,8 +111,8 @@ enum DivSystem { DIV_SYSTEM_NAMCO, DIV_SYSTEM_NAMCO_15XX, DIV_SYSTEM_NAMCO_CUS30, - DIV_SYSTEM_YM2612_DUALPCM_FRAC, - DIV_SYSTEM_YM2612_DUALPCM_FRAC_EXT, + DIV_SYSTEM_YM2612_DUALPCM, + DIV_SYSTEM_YM2612_DUALPCM_EXT, DIV_SYSTEM_MSM5232, DIV_SYSTEM_T6W28, DIV_SYSTEM_K007232, @@ -120,7 +120,7 @@ enum DivSystem { DIV_SYSTEM_PCM_DAC, DIV_SYSTEM_PONG, DIV_SYSTEM_DUMMY, - DIV_SYSTEM_YM2612_DUALPCM_CSM, + DIV_SYSTEM_YM2612_CSM, DIV_SYSTEM_YM2610_CSM, DIV_SYSTEM_YM2610B_CSM, DIV_SYSTEM_YM2203_CSM, @@ -442,7 +442,7 @@ struct DivSong { systemPan[i]=0; } subsong.push_back(new DivSubSong); - system[0]=DIV_SYSTEM_YM2612_DUALPCM; + system[0]=DIV_SYSTEM_YM2612; system[1]=DIV_SYSTEM_SMS; // OPLL default instrument contest winner - piano_guitar_idk by Weeppiko diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index f3e80073..da899ae8 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -128,7 +128,7 @@ String DivEngine::getSongSystemLegacyName(DivSong& ds, bool isMultiSystemAccepta } break; } - } else if (ds.system[0]==DIV_SYSTEM_YM2612_DUALPCM) { + } else if (ds.system[0]==DIV_SYSTEM_YM2612) { switch (ds.systemFlags[0].getInt("clockSel",0)) { case 2: return "FM Towns"; @@ -146,10 +146,10 @@ String DivEngine::getSongSystemLegacyName(DivSong& ds, bool isMultiSystemAccepta } return getSystemName(ds.system[0]); case 2: - if (ds.system[0]==DIV_SYSTEM_YM2612_DUALPCM && ds.system[1]==DIV_SYSTEM_SMS) { + if (ds.system[0]==DIV_SYSTEM_YM2612 && ds.system[1]==DIV_SYSTEM_SMS) { return "Sega Genesis/Mega Drive"; } - if (ds.system[0]==DIV_SYSTEM_YM2612_DUALPCM_EXT && ds.system[1]==DIV_SYSTEM_SMS) { + if (ds.system[0]==DIV_SYSTEM_YM2612_EXT && ds.system[1]==DIV_SYSTEM_SMS) { return "Sega Genesis Extended Channel 3"; } @@ -293,7 +293,7 @@ const char* DivEngine::getSystemNameJ(DivSystem sys) { return ""; case DIV_SYSTEM_YM2151: return ""; - case DIV_SYSTEM_YM2612_DUALPCM: + case DIV_SYSTEM_YM2612: return ""; case DIV_SYSTEM_TIA: return ""; @@ -811,7 +811,7 @@ void DivEngine::registerSystems() { fmOPMPostEffectHandlerMap ); - sysDefs[DIV_SYSTEM_YM2612_DUALPCM]=new DivSysDef( + sysDefs[DIV_SYSTEM_YM2612]=new DivSysDef( "Yamaha YM2612 (OPN2)", NULL, 0x83, 0, 6, true, false, 0x150, false, 1U<writeC(2|baseAddr1); w->writeC(0x80+i); @@ -571,10 +571,10 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write return; } switch (sys) { + case DIV_SYSTEM_YM2612: + case DIV_SYSTEM_YM2612_EXT: case DIV_SYSTEM_YM2612_DUALPCM: case DIV_SYSTEM_YM2612_DUALPCM_EXT: - case DIV_SYSTEM_YM2612_DUALPCM_FRAC: - case DIV_SYSTEM_YM2612_DUALPCM_FRAC_EXT: switch (write.addr>>8) { case 0: // port 0 w->writeC(2|baseAddr1); @@ -1178,10 +1178,10 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p howManyChips++; } break; + case DIV_SYSTEM_YM2612: + case DIV_SYSTEM_YM2612_EXT: case DIV_SYSTEM_YM2612_DUALPCM: case DIV_SYSTEM_YM2612_DUALPCM_EXT: - case DIV_SYSTEM_YM2612_DUALPCM_FRAC: - case DIV_SYSTEM_YM2612_DUALPCM_FRAC_EXT: if (!hasOPN2) { hasOPN2=disCont[i].dispatch->chipClock; willExport[i]=true; @@ -1846,10 +1846,10 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p if (!willExport[i]) continue; streamIDs[i]=streamID; switch (song.system[i]) { + case DIV_SYSTEM_YM2612: + case DIV_SYSTEM_YM2612_EXT: case DIV_SYSTEM_YM2612_DUALPCM: case DIV_SYSTEM_YM2612_DUALPCM_EXT: - case DIV_SYSTEM_YM2612_DUALPCM_FRAC: - case DIV_SYSTEM_YM2612_DUALPCM_FRAC_EXT: w->writeC(0x90); w->writeC(streamID); w->writeC(0x02); diff --git a/src/gui/debug.cpp b/src/gui/debug.cpp index 0eab7c45..3a9ff950 100644 --- a/src/gui/debug.cpp +++ b/src/gui/debug.cpp @@ -239,10 +239,10 @@ void putDispatchChip(void* data, int type) { ImVec4 colorOn=ImVec4(1.0f,1.0f,0.0f,1.0f); ImVec4 colorOff=ImVec4(0.3f,0.3f,0.3f,1.0f); switch (type) { + case DIV_SYSTEM_YM2612: + case DIV_SYSTEM_YM2612_EXT: case DIV_SYSTEM_YM2612_DUALPCM: - case DIV_SYSTEM_YM2612_DUALPCM_EXT: - case DIV_SYSTEM_YM2612_DUALPCM_FRAC: - case DIV_SYSTEM_YM2612_DUALPCM_FRAC_EXT: { + case DIV_SYSTEM_YM2612_DUALPCM_EXT: { GENESIS_CHIP_DEBUG; break; } @@ -540,13 +540,13 @@ void putDispatchChan(void* data, int chanNum, int type) { } break; } - case DIV_SYSTEM_YM2612_DUALPCM: - case DIV_SYSTEM_YM2612_DUALPCM_FRAC: { + case DIV_SYSTEM_YM2612: + case DIV_SYSTEM_YM2612_DUALPCM: { GENESIS_CHAN_DEBUG; break; } - case DIV_SYSTEM_YM2612_DUALPCM_EXT: - case DIV_SYSTEM_YM2612_DUALPCM_FRAC_EXT: { + case DIV_SYSTEM_YM2612_EXT: + case DIV_SYSTEM_YM2612_DUALPCM_EXT: { if (chanNum>=2 && chanNum<=5) { DivPlatformOPN::OPNOpChannelStereo* ch=(DivPlatformOPN::OPNOpChannelStereo*)data; ImGui::Text("> YM2612 (per operator)"); diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index 97844c27..cb6b24dc 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -911,10 +911,10 @@ const FurnaceGUIColorDef guiColors[GUI_COLOR_MAX]={ // all chips const int availableSystems[]={ + DIV_SYSTEM_YM2612, + DIV_SYSTEM_YM2612_EXT, DIV_SYSTEM_YM2612_DUALPCM, DIV_SYSTEM_YM2612_DUALPCM_EXT, - DIV_SYSTEM_YM2612_DUALPCM_FRAC, - DIV_SYSTEM_YM2612_DUALPCM_FRAC_EXT, DIV_SYSTEM_SMS, DIV_SYSTEM_GB, DIV_SYSTEM_PCE, @@ -992,10 +992,10 @@ const int availableSystems[]={ // FM const int chipsFM[]={ + DIV_SYSTEM_YM2612, + DIV_SYSTEM_YM2612_EXT, DIV_SYSTEM_YM2612_DUALPCM, DIV_SYSTEM_YM2612_DUALPCM_EXT, - DIV_SYSTEM_YM2612_DUALPCM_FRAC, - DIV_SYSTEM_YM2612_DUALPCM_FRAC_EXT, DIV_SYSTEM_YM2151, DIV_SYSTEM_YM2610, DIV_SYSTEM_YM2610_EXT, diff --git a/src/gui/presets.cpp b/src/gui/presets.cpp index 9ce238b8..639f3eb7 100644 --- a/src/gui/presets.cpp +++ b/src/gui/presets.cpp @@ -45,31 +45,31 @@ void FurnaceGUI::initSystemPresets() { CATEGORY_BEGIN("Game consoles","let's play some chiptune making games!"); ENTRY( "Sega Genesis", { - CH(DIV_SYSTEM_YM2612_DUALPCM, 64, 0, ""), + CH(DIV_SYSTEM_YM2612, 64, 0, ""), CH(DIV_SYSTEM_SMS, 32, 0, "") } ); ENTRY( "Sega Genesis (extended channel 3)", { - CH(DIV_SYSTEM_YM2612_DUALPCM_EXT, 64, 0, ""), + CH(DIV_SYSTEM_YM2612_EXT, 64, 0, ""), CH(DIV_SYSTEM_SMS, 32, 0, "") } ); ENTRY( "Sega Genesis (DualPCM)", { - CH(DIV_SYSTEM_YM2612_DUALPCM_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_DUALPCM_FRAC_EXT, 64, 0, ""), + CH(DIV_SYSTEM_YM2612_DUALPCM_EXT, 64, 0, ""), CH(DIV_SYSTEM_SMS, 32, 0, "") } ); ENTRY( "Sega Genesis (with Sega CD)", { - CH(DIV_SYSTEM_YM2612_DUALPCM, 64, 0, ""), + CH(DIV_SYSTEM_YM2612, 64, 0, ""), CH(DIV_SYSTEM_SMS, 32, 0, ""), CH(DIV_SYSTEM_RF5C68, 64, 0, "clockSel=2\n" @@ -79,7 +79,7 @@ void FurnaceGUI::initSystemPresets() { ); ENTRY( "Sega Genesis (extended channel 3 with Sega CD)", { - CH(DIV_SYSTEM_YM2612_DUALPCM_EXT, 64, 0, ""), + CH(DIV_SYSTEM_YM2612_EXT, 64, 0, ""), CH(DIV_SYSTEM_SMS, 32, 0, ""), CH(DIV_SYSTEM_RF5C68, 64, 0, "clockSel=2\n" @@ -1047,13 +1047,13 @@ void FurnaceGUI::initSystemPresets() { ); ENTRY( "FM Towns", { - CH(DIV_SYSTEM_YM2612_DUALPCM, 64, 0, "clockSel=2"), // YM3438 + CH(DIV_SYSTEM_YM2612, 64, 0, "clockSel=2"), // YM3438 CH(DIV_SYSTEM_RF5C68, 64, 0, "") } ); ENTRY( "FM Towns (extended channel 3)", { - CH(DIV_SYSTEM_YM2612_DUALPCM_EXT, 64, 0, "clockSel=2"), // YM3438 + CH(DIV_SYSTEM_YM2612_EXT, 64, 0, "clockSel=2"), // YM3438 CH(DIV_SYSTEM_RF5C68, 64, 0, "") } ); @@ -1121,22 +1121,22 @@ void FurnaceGUI::initSystemPresets() { ); ENTRY( "Yamaha YM2612 (OPN2)", { - CH(DIV_SYSTEM_YM2612_DUALPCM, 64, 0, "ladderEffect=true") + CH(DIV_SYSTEM_YM2612, 64, 0, "ladderEffect=true") } ); ENTRY( "Yamaha YM2612 (extended channel 3)", { - CH(DIV_SYSTEM_YM2612_DUALPCM_EXT, 64, 0, "ladderEffect=true") + CH(DIV_SYSTEM_YM2612_EXT, 64, 0, "ladderEffect=true") } ); ENTRY( "Yamaha YM2612 (OPN2) with DualPCM", { - CH(DIV_SYSTEM_YM2612_DUALPCM_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_DUALPCM_FRAC_EXT, 64, 0, "ladderEffect=true") + CH(DIV_SYSTEM_YM2612_DUALPCM_EXT, 64, 0, "ladderEffect=true") } ); ENTRY( @@ -1156,22 +1156,22 @@ void FurnaceGUI::initSystemPresets() { ); ENTRY( "Yamaha YM3438 (OPN2C)", { - CH(DIV_SYSTEM_YM2612_DUALPCM, 64, 0, "") + CH(DIV_SYSTEM_YM2612, 64, 0, "") } ); ENTRY( "Yamaha YM3438 (extended channel 3)", { - CH(DIV_SYSTEM_YM2612_DUALPCM_EXT, 64, 0, "") + CH(DIV_SYSTEM_YM2612_EXT, 64, 0, "") } ); ENTRY( "Yamaha YM3438 (OPN2C) with DualPCM", { - CH(DIV_SYSTEM_YM2612_DUALPCM_FRAC, 64, 0, "") + CH(DIV_SYSTEM_YM2612_DUALPCM, 64, 0, "") } ); ENTRY( "Yamaha YM3438 (extended channel 3) with DualPCM", { - CH(DIV_SYSTEM_YM2612_DUALPCM_FRAC_EXT, 64, 0, "") + CH(DIV_SYSTEM_YM2612_DUALPCM_EXT, 64, 0, "") } ); ENTRY( @@ -1719,57 +1719,57 @@ void FurnaceGUI::initSystemPresets() { ); ENTRY( "Sega System 18", { - CH(DIV_SYSTEM_YM2612_DUALPCM, 64, 0, "clockSel=2"), // discrete 8MHz YM3438 - CH(DIV_SYSTEM_YM2612_DUALPCM, 64, 0, "clockSel=2"), // ^^ + CH(DIV_SYSTEM_YM2612, 64, 0, "clockSel=2"), // discrete 8MHz YM3438 + CH(DIV_SYSTEM_YM2612, 64, 0, "clockSel=2"), // ^^ CH(DIV_SYSTEM_RF5C68, 64, 0, "clockSel=1") // 10MHz } ); ENTRY( "Sega System 18 (extended channel 3 on first OPN2C)", { - CH(DIV_SYSTEM_YM2612_DUALPCM_EXT, 64, 0, "clockSel=2"), // discrete 8MHz YM3438 - CH(DIV_SYSTEM_YM2612_DUALPCM, 64, 0, "clockSel=2"), // ^^ + CH(DIV_SYSTEM_YM2612_EXT, 64, 0, "clockSel=2"), // discrete 8MHz YM3438 + CH(DIV_SYSTEM_YM2612, 64, 0, "clockSel=2"), // ^^ CH(DIV_SYSTEM_RF5C68, 64, 0, "clockSel=1") // 10MHz } ); ENTRY( "Sega System 18 (extended channel 3 on second OPN2C)", { - CH(DIV_SYSTEM_YM2612_DUALPCM, 64, 0, "clockSel=2"), // discrete 8MHz YM3438 - CH(DIV_SYSTEM_YM2612_DUALPCM_EXT, 64, 0, "clockSel=2"), // ^^ + CH(DIV_SYSTEM_YM2612, 64, 0, "clockSel=2"), // discrete 8MHz YM3438 + CH(DIV_SYSTEM_YM2612_EXT, 64, 0, "clockSel=2"), // ^^ CH(DIV_SYSTEM_RF5C68, 64, 0, "clockSel=1") // 10MHz } ); ENTRY( "Sega System 18 (extended channel 3 on both OPN2Cs)", { - CH(DIV_SYSTEM_YM2612_DUALPCM_EXT, 64, 0, "clockSel=2"), // discrete 8MHz YM3438 - CH(DIV_SYSTEM_YM2612_DUALPCM_EXT, 64, 0, "clockSel=2"), // ^^ + CH(DIV_SYSTEM_YM2612_EXT, 64, 0, "clockSel=2"), // discrete 8MHz YM3438 + CH(DIV_SYSTEM_YM2612_EXT, 64, 0, "clockSel=2"), // ^^ CH(DIV_SYSTEM_RF5C68, 64, 0, "clockSel=1") // 10MHz } ); ENTRY( "Sega System 32", { - CH(DIV_SYSTEM_YM2612_DUALPCM, 64, 0, "clockSel=4"), // discrete 8.05MHz YM3438 - CH(DIV_SYSTEM_YM2612_DUALPCM, 64, 0, "clockSel=4"), // ^^ + CH(DIV_SYSTEM_YM2612, 64, 0, "clockSel=4"), // discrete 8.05MHz YM3438 + CH(DIV_SYSTEM_YM2612, 64, 0, "clockSel=4"), // ^^ CH(DIV_SYSTEM_RF5C68, 64, 0, "clockSel=2") // 12.5MHz } ); ENTRY( "Sega System 32 (extended channel 3 on first OPN2C)", { - CH(DIV_SYSTEM_YM2612_DUALPCM_EXT, 64, 0, "clockSel=4"), // discrete 8.05MHz YM3438 - CH(DIV_SYSTEM_YM2612_DUALPCM, 64, 0, "clockSel=4"), // ^^ + CH(DIV_SYSTEM_YM2612_EXT, 64, 0, "clockSel=4"), // discrete 8.05MHz YM3438 + CH(DIV_SYSTEM_YM2612, 64, 0, "clockSel=4"), // ^^ CH(DIV_SYSTEM_RF5C68, 64, 0, "clockSel=2") // 12.5MHz } ); ENTRY( "Sega System 32 (extended channel 3 on second OPN2C)", { - CH(DIV_SYSTEM_YM2612_DUALPCM, 64, 0, "clockSel=4"), // discrete 8.05MHz YM3438 - CH(DIV_SYSTEM_YM2612_DUALPCM_EXT, 64, 0, "clockSel=4"), // ^^ + CH(DIV_SYSTEM_YM2612, 64, 0, "clockSel=4"), // discrete 8.05MHz YM3438 + CH(DIV_SYSTEM_YM2612_EXT, 64, 0, "clockSel=4"), // ^^ CH(DIV_SYSTEM_RF5C68, 64, 0, "clockSel=2") // 12.5MHz } ); ENTRY( "Sega System 32 (extended channel 3 on both OPN2Cs)", { - CH(DIV_SYSTEM_YM2612_DUALPCM_EXT, 64, 0, "clockSel=4"), // discrete 8.05MHz YM3438 - CH(DIV_SYSTEM_YM2612_DUALPCM_EXT, 64, 0, "clockSel=4"), // ^^ + CH(DIV_SYSTEM_YM2612_EXT, 64, 0, "clockSel=4"), // discrete 8.05MHz YM3438 + CH(DIV_SYSTEM_YM2612_EXT, 64, 0, "clockSel=4"), // ^^ CH(DIV_SYSTEM_RF5C68, 64, 0, "clockSel=2") // 12.5MHz } ); @@ -1986,13 +1986,13 @@ void FurnaceGUI::initSystemPresets() { ); ENTRY( "Sunsoft Arcade", { - CH(DIV_SYSTEM_YM2612_DUALPCM, 64, 0, "clockSel=2"), // discrete YM3438 8MHz + CH(DIV_SYSTEM_YM2612, 64, 0, "clockSel=2"), // discrete YM3438 8MHz CH(DIV_SYSTEM_MSM6295, 64, 0, "clockSel=1") // 1.056MHz } ); ENTRY( "Sunsoft Arcade (extended channel 3)", { - CH(DIV_SYSTEM_YM2612_DUALPCM_EXT, 64, 0, "clockSel=2"), // discrete YM3438 8MHz + CH(DIV_SYSTEM_YM2612_EXT, 64, 0, "clockSel=2"), // discrete YM3438 8MHz CH(DIV_SYSTEM_MSM6295, 64, 0, "clockSel=1") // 1.056MHz } ); @@ -2331,13 +2331,13 @@ void FurnaceGUI::initSystemPresets() { ENTRY( "Seta 1 + FM addon", { CH(DIV_SYSTEM_X1_010, 64, 0, ""), - CH(DIV_SYSTEM_YM2612_DUALPCM, 64, 0, "clockSel=2") // Discrete YM3438 + CH(DIV_SYSTEM_YM2612, 64, 0, "clockSel=2") // Discrete YM3438 } ); ENTRY( "Seta 1 + FM addon (extended channel 3)", { CH(DIV_SYSTEM_X1_010, 64, 0, ""), - CH(DIV_SYSTEM_YM2612_DUALPCM_EXT, 64, 0, "clockSel=2") // Discrete YM3438 + CH(DIV_SYSTEM_YM2612_EXT, 64, 0, "clockSel=2") // Discrete YM3438 } ); ENTRY( @@ -2432,13 +2432,13 @@ void FurnaceGUI::initSystemPresets() { CATEGORY_BEGIN("DefleMask-compatible","these configurations are compatible with DefleMask.\nselect this if you need to save as .dmf or work with that program."); ENTRY( "Sega Genesis", { - CH(DIV_SYSTEM_YM2612_DUALPCM, 64, 0, ""), + CH(DIV_SYSTEM_YM2612, 64, 0, ""), CH(DIV_SYSTEM_SMS, 32, 0, "") } ); ENTRY( "Sega Genesis (extended channel 3)", { - CH(DIV_SYSTEM_YM2612_DUALPCM_EXT, 64, 0, ""), + CH(DIV_SYSTEM_YM2612_EXT, 64, 0, ""), CH(DIV_SYSTEM_SMS, 32, 0, "") } ); diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 7bed7025..d40e4dac 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -333,7 +333,7 @@ void FurnaceGUI::drawSettings() { ImGui::SameLine(); if (ImGui::Button("Reset to defaults")) { settings.initialSys.clear(); - settings.initialSys.set("id0",e->systemToFileFur(DIV_SYSTEM_YM2612_DUALPCM)); + settings.initialSys.set("id0",e->systemToFileFur(DIV_SYSTEM_YM2612)); settings.initialSys.set("vol0",64); settings.initialSys.set("pan0",0); settings.initialSys.set("flags0",""); @@ -429,7 +429,7 @@ void FurnaceGUI::drawSettings() { } if (sysCount<32) if (ImGui::Button(ICON_FA_PLUS "##InitSysAdd")) { - settings.initialSys.set(fmt::sprintf("id%d",sysCount),(int)e->systemToFileFur(DIV_SYSTEM_YM2612_DUALPCM)); + settings.initialSys.set(fmt::sprintf("id%d",sysCount),(int)e->systemToFileFur(DIV_SYSTEM_YM2612)); settings.initialSys.set(fmt::sprintf("vol%d",sysCount),64); settings.initialSys.set(fmt::sprintf("pan%d",sysCount),0); settings.initialSys.set(fmt::sprintf("flags%d",sysCount),""); @@ -2566,7 +2566,7 @@ void FurnaceGUI::syncSettings() { settings.initialSys.loadFromBase64(initialSys2.c_str()); if (settings.initialSys.getInt("id0",0)==0) { settings.initialSys.clear(); - settings.initialSys.set("id0",e->systemToFileFur(DIV_SYSTEM_YM2612_DUALPCM)); + settings.initialSys.set("id0",e->systemToFileFur(DIV_SYSTEM_YM2612)); settings.initialSys.set("vol0",64); settings.initialSys.set("pan0",0); settings.initialSys.set("flags0",""); diff --git a/src/gui/sysConf.cpp b/src/gui/sysConf.cpp index 5337cb44..2fab65c5 100644 --- a/src/gui/sysConf.cpp +++ b/src/gui/sysConf.cpp @@ -28,10 +28,10 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo bool supportsCustomRate=true; switch (type) { + case DIV_SYSTEM_YM2612: + case DIV_SYSTEM_YM2612_EXT: case DIV_SYSTEM_YM2612_DUALPCM: - case DIV_SYSTEM_YM2612_DUALPCM_EXT: - case DIV_SYSTEM_YM2612_DUALPCM_FRAC: - case DIV_SYSTEM_YM2612_DUALPCM_FRAC_EXT: { + 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_DUALPCM_EXT || type==DIV_SYSTEM_YM2612_DUALPCM_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; } From 8f972daf00d3dc1cd14dc667ec8a230fa854aaf6 Mon Sep 17 00:00:00 2001 From: Epictyphlosion <34047941+Epictyphlosion@users.noreply.github.com> Date: Tue, 20 Dec 2022 15:04:06 -0800 Subject: [PATCH 45/93] One more for good measure --- src/gui/presets.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/gui/presets.cpp b/src/gui/presets.cpp index a63f7e56..81416055 100644 --- a/src/gui/presets.cpp +++ b/src/gui/presets.cpp @@ -1690,6 +1690,14 @@ void FurnaceGUI::initSystemPresets() { CH(DIV_SYSTEM_POKEY, 64, 0, "") } ); + ENTRY( + "Atari I, Robot", { + CH(DIV_SYSTEM_POKEY, 64, 0, ""), // clock speed should be 1.512 MHz + CH(DIV_SYSTEM_POKEY, 64, 0, ""), // same here + CH(DIV_SYSTEM_POKEY, 64, 0, ""), // same here + CH(DIV_SYSTEM_POKEY, 64, 0, "") // same here... + } + ); ENTRY( "Sega Kyugo", { CH(DIV_SYSTEM_AY8910, 64, 0, "clockSel=14"), From 62b40464f179a35d13e0386e08b743e83cde862c Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 20 Dec 2022 18:55:09 -0500 Subject: [PATCH 46/93] update readme --- README.md | 296 +----------------------------------------------------- 1 file changed, 3 insertions(+), 293 deletions(-) diff --git a/README.md b/README.md index e5573b4b..509c7b44 100644 --- a/README.md +++ b/README.md @@ -1,297 +1,7 @@ # Furnace (chiptune tracker) -![screenshot](papers/screenshot2.png) +# HELP -the biggest multi-system chiptune tracker ever made! +Furnace has a serious crash bug! download the latest artifact (either from the Actions tab or [nightly.link](https://nightly.link/tildearrow/furnace/workflows/build/master)) or compile it and help me diagnose and track the origin! -[downloads](#downloads) | [discussion/help](#quick-references) | [developer info](#developer-info) | [unofficial packages](#unofficial-packages) | [FAQ](#frequently-asked-questions) - ---- -## downloads - -check out the [Releases](https://github.com/tildearrow/furnace/releases) page. available for Windows, macOS and Linux (AppImage). - -[see here](https://nightly.link/tildearrow/furnace/workflows/build/master) for unstable developer builds. - -## features - -- over 50 sound chips - and counting: - - Yamaha FM chips: - - YM2151 (OPM) - - YM2203 (OPN) - - YM2413 (OPLL) - - YM2414 (OPZ) used in Yamaha TX81Z - - YM2608 (OPNA) used in PC-98 - - YM2610 (OPNB) used in Neo Geo - - YM2610B (OPNB2) - - YM2612 (OPN2) used in Sega Genesis and FM Towns - - YM3526 (OPL) used in C64 Sound Expander - - YM3812 (OPL2) - - YMF262 (OPL3) with full 4-op support! - - Y8950 (OPL with ADPCM) - - square wave chips: - - AY-3-8910/YM2149(F) used in several computers and game consoles - - Commodore VIC used in the VIC-20 - - Microchip AY8930 - - TI SN76489 used in Sega Master System and BBC Micro - - PC Speaker - - Philips SAA1099 used in SAM CoupĂ© - - sample chips: - - Amiga - - SegaPCM - all 16 channels - - Capcom QSound - - Yamaha YMZ280B (PCMD8) - - Ricoh RF5C68 used in Sega CD and FM Towns - - OKI MSM6258 and MSM6295 - - wavetable chips: - - HuC6280 used in PC Engine - - Konami Bubble System WSG - - Konami SCC/SCC+ - - Namco arcade chips (WSG/C15/C30) - - WonderSwan - - Seta/Allumer X1-010 - - NES (Ricoh 2A03/2A07), with additional expansion sound support: - - Konami VRC6 - - Konami VRC7 - - MMC5 - - Famicom Disk System - - Sunsoft 5B - - Namco 163 - - Family Noraebang (OPLL) - - SID (6581/8580) used in Commodore 64 - - Mikey used in Atari Lynx - - ZX Spectrum beeper (SFX-like engine) - - Commodore PET - - TIA used in Atari 2600 - - Game Boy - - modern/fantasy: - - Commander X16 VERA - - tildearrow Sound Unit -- mix and match sound chips! - - over 200 ready to use presets from computers, game consoles and arcade boards... - - ...or create your own - up to 32 of them or a total of 128 channels! -- DefleMask compatibility - - loads .dmf modules from all versions (beta 1 to 1.1.3) - - saves .dmf modules - both modern and legacy - - Furnace doubles as a module downgrader - - loads/saves .dmp instruments and .dmw wavetables as well - - clean-room design (guesswork and ABX tests only, no decompilation involved) - - bug/quirk implementation for increased playback accuracy through compatibility flags -- VGM export -- modular layout that you may adapt to your needs -- audio file export - entire song, per chip or per channel -- quality emulation cores (Nuked, MAME, SameBoy, Mednafen PCE, NSFplay, puNES, reSID, Stella, SAASound, vgsound_emu and ymfm) -- wavetable synthesizer - - available on wavetable chips - - create complex sounds with ease - provide up to two wavetables, select and effect and let go! -- MIDI input support -- additional features: - - FM macros! - - negative octaves - - arbitrary pitch samples - - sample loop points - - SSG envelopes and ADPCM-B in Neo Geo - - full duty/cutoff range in C64 - - ability to change tempo mid-song - - multiple sub-songs in a module - - per-channel oscilloscope with waveform centering - - built-in sample editor - - chip mixing settings - - built-in visualizer in pattern view -- open-source under GPLv2 or later. - ---- -# quick references - - - **discussion**: see the [Discussions](https://github.com/tildearrow/furnace/discussions) section, or (preferably) the [official Discord server](https://discord.gg/EfrwT2wq7z). - - **help**: check out the [documentation](papers/doc/README.md). it's mostly incomplete, but has details on effects. - -## unofficial packages - -[![Packaging status](https://repology.org/badge/tiny-repos/furnace.svg)](https://repology.org/project/furnace/versions) - -some people have provided packages for Unix/Unix-like distributions. here's a list. - - **Arch Linux**: [furnace-git is in the AUR.](https://aur.archlinux.org/packages/furnace-git) thank you Essem! - - **FreeBSD**: [a package in ports](https://www.freshports.org/audio/furnace/) is available courtesy of ehaupt. - - **Nix**: [package](https://search.nixos.org/packages?channel=unstable&show=furnace&from=0&size=50&sort=relevance&type=packages&query=furnace) thanks to OPNA2608. - - **openSUSE**: [a package](https://software.opensuse.org/package/furnace) is available, courtesy of fpesari. - ---- -# developer info - -[![Build furnace](https://github.com/tildearrow/furnace/actions/workflows/build.yml/badge.svg)](https://github.com/tildearrow/furnace/actions/workflows/build.yml) - -if you can't download these artifacts (because GitHub requires you to be logged in), [go here](https://nightly.link/tildearrow/furnace/workflows/build/master) instead. - -**NOTE: do not download the project's source as a .zip or .tar.gz as these do not include the project's submodules which are necessary to proceed with building. please instead use Git as shown below.** - -## dependencies - -- CMake -- JACK (optional, macOS/Linux only) - -if building under Windows or macOS, no additional dependencies are required. -otherwise, you may also need the following: - -- libpulse -- libx11 -- libasound -- libGL - -some Linux distributions (e.g. Ubuntu or openSUSE) will require you to install the `-dev` versions of these. - -## getting the source - -type the following on a terminal/console: (make sure Git is installed) - -``` -git clone --recursive https://github.com/tildearrow/furnace.git -cd furnace -``` - -(the `--recursive` parameter ensures submodules are fetched as well) - -## compilation - -your typical CMake project. - -### Windows using MSVC - -as of now tildearrow uses MinGW for Windows builds, but thanks to OPNA2608 this works again! - -from the developer tools command prompt: - -``` -mkdir build -cd build -cmake .. -msbuild ALL_BUILD.vcxproj -``` - -### macOS and Linux - -``` -mkdir build -cd build -cmake .. -make -``` -Alternatively, build scripts are provided in the `scripts/` folder in the root of the repository. - -### CMake options - -To add an option from the command-line: `-D=` -Example: `cmake -DBUILD_GUI=OFF -DWARNINGS_ARE_ERRORS=ON ..` - -Available options: - -| Name | Default | Description | -| :--: | :-----: | ----------- | -| `BUILD_GUI` | `ON` | Build the tracker (disable to build only a headless player) | -| `USE_RTMIDI` | `ON` | Build with MIDI support using RtMidi | -| `USE_SDL2` | `ON` | Build with SDL2 (required to build with GUI) | -| `USE_SNDFILE` | `ON` | Build with libsndfile (required in order to work with audio files) | -| `USE_BACKWARD` | `ON` | Use backward-cpp to print a backtrace on crash/abort | -| `WITH_JACK` | `ON` if system-installed JACK detected, otherwise `OFF` | Whether to build with JACK support. Auto-detects if JACK is available | -| `SYSTEM_FFTW` | `OFF` | Use a system-installed version of FFTW instead of the vendored one | -| `SYSTEM_FMT` | `OFF` | Use a system-installed version of fmt instead of the vendored one | -| `SYSTEM_LIBSNDFILE` | `OFF` | Use a system-installed version of libsndfile instead of the vendored one | -| `SYSTEM_RTMIDI` | `OFF` | Use a system-installed version of RtMidi instead of the vendored one | -| `SYSTEM_ZLIB` | `OFF` | Use a system-installed version of zlib instead of the vendored one | -| `SYSTEM_SDL2` | `OFF` | Use a system-installed version of SDL2 instead of the vendored one | -| `WARNINGS_ARE_ERRORS` | `OFF` (but consider enabling this & reporting any errors that arise from it!) | Whether warnings in furnace's C++ code should be treated as errors | -| `WITH_DEMOS` | `ON` | Install demo songs on `make install` | -| `WITH_INSTRUMENTS` | `ON` | Install demo instruments on `make install` | - -## console usage - -``` -./furnace -``` - -this opens the program. - -``` -./furnace -console -``` - -this will play a compatible file. - -``` -./furnace -console -view commands -``` - -this will play a compatible file and enable the commands view. - -**note that these commands only actually work in Linux environments. on other command lines, such as Windows' Command Prompt, or MacOS Terminal, it may not work correctly.** - ---- -# frequently asked questions - -> woah! 50 sound chips?! I can't believe it! - -yup, it's real. - -> where's the manual? - -see [papers/](papers/doc/README.md). it's kind of incomplete, but at least the sound chips section is there. - -> it doesn't open under macOS! - -this is due to Apple's application signing policy. a workaround is to right click on the Furnace app icon and select Open. - -**as of Monterey, this workaround no longer works (especially on ARM).** yeah, Apple has decided to be strict on the matter. -if you happen to be on that version, use this workaround instead (on a Terminal): - -``` -xattr -d com.apple.quarantine /path/to/Furnace.app -``` - -(replace /path/to/ with the path where Furnace.app is located) - -you may need to log out and/or reboot after doing this. - -> how do I use C64 absolute filter/duty? - -on Instrument Editor in the C64 tab there are two options to toggle these. -also provided are two effects: - -- `3xxx`: set fine duty. -- `4xxx`: set fine cutoff. `xxx` range is 000-7ff. -additionally, you can change the cutoff and/or duty as a macro inside an instrument by clicking the `absolute cutoff macro` and/or `absolute duty macro` checkbox at the bottom of the instrument. (for the filter, you also need to click the checkbox that says `volume macro is cutoff macro`.) - -> how do I use PCM on a PCM-capable chip? - -two possibilities: -- the recommended way is by creating the "Sample" type instrument and assigning a sample to it. -- otherwise you may employ the DefleMask-compatible method, using `17xx` effect. - -> my .dmf song sounds very odd at a certain point - -file a bug report. use the Issues page. it's probably another playback inaccuracy. - -> my .dmf song sounds correct, but it doesn't in DefleMask - -file a bug report **here**. it still is a playback inaccuracy. - -> my song sounds terrible after saving as .dmf! - -the DefleMask format has several limitations. save in Furnace song format instead (.fur). - -> how do I solo channels? - -right click on the channel name. - ---- -# footnotes - -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. - - -despite the fact this program works with the .dmf file format, it is NOT affiliated with Delek or DefleMask in any way, nor it is a replacement for the original program. +see the [bug report](https://github.com/tildearrow/furnace/issues/793) for more info. From 5f81ae48d8013f4f71174837ae860fda68b08bb9 Mon Sep 17 00:00:00 2001 From: Epictyphlosion <34047941+Epictyphlosion@users.noreply.github.com> Date: Tue, 20 Dec 2022 20:52:51 -0800 Subject: [PATCH 47/93] As you wish --- src/gui/presets.cpp | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/gui/presets.cpp b/src/gui/presets.cpp index 81416055..b9a4a210 100644 --- a/src/gui/presets.cpp +++ b/src/gui/presets.cpp @@ -2233,6 +2233,30 @@ void FurnaceGUI::initSystemPresets() { CH(DIV_SYSTEM_OPL2_DRUMS, 64, 0, "clockSel=2") } ); + ENTRY( + "SNK Touchdown Fever", { + CH(DIV_SYSTEM_OPL, 64, 0, "clockSel=2"), + CH(DIV_SYSTEM_Y8950, 64, 0, "clockSel=2") + } + ); + ENTRY( + "SNK Touchdown Fever (drums mode on OPL)", { + CH(DIV_SYSTEM_OPL_DRUMS, 64, 0, "clockSel=2"), + CH(DIV_SYSTEM_Y8950, 64, 0, "clockSel=2") + } + ); + ENTRY( + "SNK Touchdown Fever (drums mode on Y8950)", { + CH(DIV_SYSTEM_OPL, 64, 0, "clockSel=2"), + CH(DIV_SYSTEM_Y8950_DRUMS, 64, 0, "clockSel=2") + } + ); + ENTRY( + "SNK Touchdown Fever (drums mode on OPL and Y8950)", { + CH(DIV_SYSTEM_OPL_DRUMS, 64, 0, "clockSel=2"), + CH(DIV_SYSTEM_Y8950_DRUMS, 64, 0, "clockSel=2") + } + ); ENTRY( "Alpha denshi Alpha-68K", { CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=3"), // 3MHz From b208d2f32bbc800873cc90fe25197aec5430e500 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 21 Dec 2022 01:20:56 -0500 Subject: [PATCH 48/93] fix Furnace not exiting after error during startup --- src/main.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/main.cpp b/src/main.cpp index 019ba75d..79502ad6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -426,17 +426,20 @@ int main(int argc, char** argv) { 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) { @@ -446,6 +449,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]; @@ -453,23 +457,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!"); @@ -483,6 +491,7 @@ int main(int argc, char** argv) { } else { e.benchmarkPlayback(); } + finishLogFile(); return 0; } if (outName!="" || vgmOutName!="" || cmdOutName!="") { @@ -523,6 +532,7 @@ int main(int argc, char** argv) { e.saveAudio(outName.c_str(),loops,outMode); e.waitAudioFile(); } + finishLogFile(); return 0; } @@ -540,6 +550,7 @@ int main(int argc, char** argv) { cli.loop(); cli.finish(); e.quit(); + finishLogFile(); return 0; } else { #ifdef HAVE_SDL2 @@ -549,6 +560,7 @@ int main(int argc, char** argv) { if (ev.type==SDL_QUIT) break; } e.quit(); + finishLogFile(); return 0; #else while (true) { @@ -566,6 +578,7 @@ int main(int argc, char** argv) { g.bindEngine(&e); if (!g.init()) { reportError(g.getLastError()); + finishLogFile(); return 1; } From 5ee41c5f5e36d6e1ee2043e6496fef50fb4131db Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 21 Dec 2022 13:39:43 -0500 Subject: [PATCH 49/93] GUI: fix possible issue when changing sample depth --- src/gui/sampleEdit.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/gui/sampleEdit.cpp b/src/gui/sampleEdit.cpp index 2f80a04f..43fdac72 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; } From 41a21ebb915d233d69aabb7260412c9567d95a49 Mon Sep 17 00:00:00 2001 From: Epictyphlosion <34047941+Epictyphlosion@users.noreply.github.com> Date: Wed, 21 Dec 2022 12:42:47 -0800 Subject: [PATCH 50/93] A few more for better measure Some MSM5232 arcades --- src/gui/presets.cpp | 105 ++++++++++++++++++++++++++++++++------------ 1 file changed, 78 insertions(+), 27 deletions(-) diff --git a/src/gui/presets.cpp b/src/gui/presets.cpp index b9a4a210..59e26132 100644 --- a/src/gui/presets.cpp +++ b/src/gui/presets.cpp @@ -1671,33 +1671,6 @@ void FurnaceGUI::initSystemPresets() { CH(DIV_SYSTEM_MSM6295, 64, 0, "clockSel=1") } ); - 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, ""), // clock speed should be 1.512 MHz - CH(DIV_SYSTEM_POKEY, 64, 0, ""), // same here - CH(DIV_SYSTEM_POKEY, 64, 0, ""), // same here - CH(DIV_SYSTEM_POKEY, 64, 0, "") // same here... - } - ); ENTRY( "Sega Kyugo", { CH(DIV_SYSTEM_AY8910, 64, 0, "clockSel=14"), @@ -2066,6 +2039,33 @@ 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, ""), // clock speed should be 1.512 MHz + CH(DIV_SYSTEM_POKEY, 64, 0, ""), // same here + CH(DIV_SYSTEM_POKEY, 64, 0, ""), // same here + CH(DIV_SYSTEM_POKEY, 64, 0, "") // same here... + } + ); ENTRY( "Data East Karnov", { CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=5"), // 1.5MHz @@ -2297,6 +2297,20 @@ void FurnaceGUI::initSystemPresets() { ) // software controlled 8 bit DAC } ); + ENTRY( + "Alpha denshi Equites", { + CH(DIV_SYSTEM_MSM5232, 64, 0, ""), // // clock speed should be 6.144 MHz + CH(DIV_SYSTEM_AY8910, 64, 0, "clockSel=14"), + CH(DIV_SYSTEM_DAC, 64, 0, + "rate=11025\n" + "outDepth=5\n" + ), + CH(DIV_SYSTEM_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, "") @@ -2369,6 +2383,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_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_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, "") From 2ccc525293eb5f33600da09f6dfebdcd508b97f8 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 21 Dec 2022 16:26:06 -0500 Subject: [PATCH 51/93] K007232: ??? issue #797 --- src/engine/platform/k007232.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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; From dc4ad09903abb062885023fe780dd00cad101386 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 21 Dec 2022 16:33:32 -0500 Subject: [PATCH 52/93] MSM6295: 127!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --- src/engine/platform/msm6295.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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]) { From 50e6409348d000b95a4416f5c8a24de9380da1de Mon Sep 17 00:00:00 2001 From: Kagamiin <102362203+Kagamiin@users.noreply.github.com> Date: Wed, 21 Dec 2022 21:45:14 +0000 Subject: [PATCH 53/93] Adding demos/sms/thunderblade-type-ii.fur (#794) * Adding demos/sms/thunderblade-type-ii.fur * Adding my name to about.cpp --- demos/sms/thunderblade-type-ii.fur | Bin 0 -> 6983 bytes src/gui/about.cpp | 1 + 2 files changed, 1 insertion(+) create mode 100644 demos/sms/thunderblade-type-ii.fur diff --git a/demos/sms/thunderblade-type-ii.fur b/demos/sms/thunderblade-type-ii.fur new file mode 100644 index 0000000000000000000000000000000000000000..d2f922fa99be3ed607c4fab798d7bf254f543fe2 GIT binary patch literal 6983 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≫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 literal 0 HcmV?d00001 diff --git a/src/gui/about.cpp b/src/gui/about.cpp index e685b50a..0d14465d 100644 --- a/src/gui/about.cpp +++ b/src/gui/about.cpp @@ -79,6 +79,7 @@ const char* aboutLine[]={ "iyatemu", "JayBOB18", "Jimmy-DS", + "Kagamiin~", "kleeder", "jaezu", "Laggy", From e9652af5a9075960370a12627f30f190df0c9b33 Mon Sep 17 00:00:00 2001 From: freq-mod <32672779+freq-mod@users.noreply.github.com> Date: Wed, 21 Dec 2022 22:06:34 +0000 Subject: [PATCH 54/93] add RF5C164 demo song (#784) * add RF5C164 demo song * fix note cuts * 8 channels * move to directory * aaaaaaaa --- demos/megacd/rf5wapianoroll.fur | Bin 0 -> 19979 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 demos/megacd/rf5wapianoroll.fur diff --git a/demos/megacd/rf5wapianoroll.fur b/demos/megacd/rf5wapianoroll.fur new file mode 100644 index 0000000000000000000000000000000000000000..4b2919f3f72d2e88331315d2c6e9987d21d36ce8 GIT binary patch literal 19979 zcmV)8K*qm#obA0^kfceL9_0T2agWG*X5FhgtE#(a_NLt(Axt1!L#;srGSFyjcL9SZ zR7UO*VDnZDM&D(|zn<~8i%6Gr>*R~TFRBKwPKln?3OVw3n8HP%$KM()=U#QbxfAKZ1*TB_!`nq>q z?CTFd`0@LjfAGWK`TY+zfAYbPezf_AAN>Bmv-x{J`QXQ!|Hcn~_($(=zW1ZQ{?i|C z{`xO({^1Wk_{kr=|NHO%uKMBc{*7PW{LSzDxy|4C$xr^w=3oC~c>DYR?ytc9ZhrG$ z`Svete(Tr1^)2||55KJvH-*+Tnbz;YR&WU{cBaNey^?7zqg{*e{x-` z|N5?0|HDJA{?RXJ^?&}VRv-O2t#`t5&PtM`9T ztH1dpt^V#$wED09eXaiff2h^}@^`fQKmJp#KK#$L+W8Z$vj0Y_rT~W< zb&^)9wbTAlt5(uPtN0%}_d4a|{?@<1yNmz7;syl!6Zn7b^_xHZ{s%w6^ZNEb`{&)C zeCJ<%?_YWc{`vhM{O}Kc`;Y(N{onZY*7tw(!T0~hZ@vGc-}qMR`#=4|KlwKa-+iy9zK6D={_1<*Pz|NljK(?5C;#_9{)hkFfBtv><`4eO z@BQU}?%lQJHmt)K{7=gKmX}sLb#3}%ZAQlf z1@r5{$n*{l##yCVtEm3r&@`HL)!o@XJ{p2y)LQj=rPi!hlQbI*E6a@pKC-sBwA`#E zrhC#IW~SOS2S>eOQcEVi?HAiS2giePrqXmcf~l+3{&7#WR@;qQYiafRy<5wRZ8hp0 zc1@DjC&zn7s@bZUqy6r|;r`Je&4w!Nos3k~^o|ZsvTA*Csk3_X&ck1NaOdug#dI>t z()M!Ibocw!sv39q4-cL_eYvxL2v2@|JW3ZDlm2+58jbeS(&Ea-&3m_R-&kL)^!5)< zOtU^XIXccN$#^n2*?;xw#nYF2NBzm+!62!(;h-{D?BdGe`mO7??mf8w_QTs7)$Z=` zFl{8mZcimTZ6?E${oS2skGA&r4|ZN`9SqHAJUrYz8jRB$Df=p8=&?8%G0{jH~4d&ls0Gdw<-z_C^8ozCLM^*ax~@k`&>NR6(x zU?pZW%HZ(yWIXD<_~_G@ub#hr`sDf6QI=$r(O@)Il{9U4+G{sAZol)(zj&t&v+FcX zN5{Bv^+eZds<-uQYwN|<_RD7gQ)#Vg4Wgb_+sn(V%hw+K+MoUA-5YD|*3!~aCmE-$ zX0zT{SgM(mo#)S6$Vcs$W+Jk-s0Rn@O;zVnR-H`d!}eW_EM^hb%B z7{Dg!c<1S}$B&xhx((0w?!#NlwffRxQ;!rJqf(Xj zLaR33d$qmw;`x)$KK$s(&e3St&1ygl6W!^+BQ_Q{9{lRBzI%IR<@$1~T?Z;04aTP0 zT3V%a8t@4j{S#BpYV64xH^MUE6&4_Ph7*+`F~5zS3-U(ouh; zYt7ZQc4Z8nc(VQU;}1Xm=;u$i4-O7a`o~A3q~6F5Pe#eooxArR+`G4V``X&d+F~uq zdV{fQ05gn-qpZL4^zmmu|9^k>=*g3v!=vM)?c=1@sPvDzgJkLEy?b{aJiLAF#`SCK zi;cSJ^-XPIsh)vljE)ZXpM3Q5fBNy`Po5rij}CVCM=99U;P`OZSlPI9Z}aW@w{PFO zbMyMfYSRoSwdK`DtyzP}o=uKkJbCo#ryoDsK04lewR1AmtpaF9wpUgquQuyox-B`x1N3W$WO`^BrHNxfYeo}Bb1>Eeys_ujsL z|LwPKZod8C)`FSnYOUFBH_~doUd>LrM=w79=%Y`bZSQP7ef07ut5>qo@qTZZEnMHY ziR-*^=bg9jt^r+5fJW*yWop1a<6&?232f)5&$oAcIWuPCIyM4NFo+)xFk z*YAJxod<7SYbU1Cs8lK`5MQ-wOf}no{^;XJFL$?}e){;?*6{?G?YKKQ=}u}Zciy^v z>*l?OZ~x*iZC+b!G!|e}n(&CtTCLu!o8uRco^64hK7aP~>CPxglF4Y&KRO<@*4MA! zzP@q${=2{Y?#)#=z-B##J*lSERM%RS!Or7H&!0cve)-wQA3feZ8IFOWyF0r_!%BN; zV`Fjo*8R8N-n_ZoZZ*;*g)j+P!6fY#aQ3q&PhRY7Kl}7&AHCe)1AfUyM|;4=mBrQN z#pU%|55M`&z1!Co7g`nct_Cn(rP->d{he1Yp6wjH`t;*Z9=~|G(>Fjey}hl&3|wuo zwX|{P;kyqu?_OJ5244b;Prwmt3++aA2pqU~wD;_@M~@#pdU*&&uYn+UPE@V4&}y$- zyZ81xZ*Shcb8`^_N^=2tElum4<;6;OXYc4}_tmTCpMLh_(ewS@BvGTIo&ABXH=C`6 zr5g_)-UoNOcVh|ut<$Pu$ZxJ}v`5|E;r`+N&dVo{z)YX*b~BarcejuF<7zFbw3e^k zxp(j3yAN-#!UMu%)sn#goMHiZ>iF>BV0Y{Jnp7!OB=1_)lRLNnqjZsJv`WX^2vuEzknbL-f#k+)z4C$G!|B_-G2Cs-`u=?W8=o1 z>x*yPoF~&JvcyuIy&i%)p(>EOLrb_-nn)2_Pu+z);l$@ z>{_F}*lM(@lOYgPcJSiyr=LB2wR>;~n1288cr>15u+rxG-TUB9ci!6Eytm!~yR0@E zt#*5%tsuVElHt+TqfZ|_ez~`^dw2pTb9gdDE*O}`+RfW{ZeF`{fAik$^#$-;(1~!6 z9S|y6rLG1iTc3UM=oz^E-r?cl0s6yuaC8h-vvO@?ef`FryRgpHHk?cbLASF|%SM?> zD#_^Z<>SYXpFDlGy}y5WxVwAQ9}atmhbNQ9;>zmU#?4#z-o6Ju(P^ZaYA$w~RbbGh zR!hc*TQ8nGe){z3_Tj<)-tPYKU@|;DIOwTrYjtgT_2#|%_wNF4FV~f>w-*-MK>MoE zLO$GsX#M2rHXOsj-u_X~sQwWgkb$_ow7h!z)X)04k046n~mk`H*ep*vk9EISkvHr;I_%cRO<~8Z{wq_m(Mjz1rec@7?(^p_cTf7gE~q~+oclOe(&_?`%p!=>DsXR7X)d(umEryt?8m{Yr$}#IfSnv4z)1{D0!wPH+}eC_ z@5V~I+3BexYTa8 z014A2X>)10ZTj%e-Vv;IYZvC}b$htfqd|swOWIn!^Y(+=H^9;s7Zy5g5aOvZ^~L4o zrWy46y%Qj|SKGV$Xeomp%y>WXg<9bNvp4>zmhr4VOAikWUFjQNVSoW;}#<9_+k& zxxEWMf84YD2ue5`p*O8wzj^1*jkUFvg?0(I_wX+NBeM$gYmF?eB6VWwzzuZ&h2X}3k$2T)`l8q zpw$3YuR;Dg?wy<*ZohoFbJB-1*u(kYqYhznbgjK|^Ue(jq)V&os|z*Q;-p#wM*<^- z`GBVPUjfA&_qxaEy)a!53DEe{#}=2OOpCb=c0;#g%KYsSEW~gM5d{S`c_4>?a^(ceZwq!B~!V_l^fh z$zAY_6X4M`w3g*H;QO^sElJQHYK>MC9I}GA{T*0lzYk9b(WT$*!;>B#9zu?`vIeVM zUfs9}a=Qi!K?C8}8?c{{h+CPr15xZ`e-DTo76`BI;od<{Hy2k{78jN`Zrt2hY&C13 zMv@A`z~vjws_E}-@1MY4ppAjM_Xnfl@!lTH2fVh}hEu$8eQgrQK>N!bASQ6zMjh#G5m-2blkfG=XOAJCoq!6RK(yaJfP55ouLCx= ze(l=oQm2mF0(pkgKo+19CW9WrARvM82J0SWUId#7eH1UHAXhRr_!dogBsvbVE;(g!}Q)SHOgxON@lQUz97tLRKO7BI>HZx8UK zdtl*kfMb;nj`#QAM1T)*N#I1VqbqGVwtBOMm4QYFHmnYQHA2*2aE!Y+F;*7Af(Jv8 z<&9>0X&v;zN*fNXg>QyMw2*FKW=MYrkP2Z898M zf$4x3Gl1!=53v^fd;qD_vdz%3fMt2H#CnWiYp2SZGxh#tQ{`09Yu# zc?25@uF)NUJ@gQ_gs|1sB@9Z5$}-r{MVuR#jOzq1>UMEM;ZcD%21pn~1+l%;Sz1|L zS?aVf>7F2CEr7WuSV>F982^spjj*A35M7KNN5|k55R5voR7iQ-EmQ?#kR=e=Dmtx! z*-bE39G`$$!$F(?fer^Ky+#RA3O?QP(jtnAG16!o>@!V4Nx?dS|6rxCOb8F)ko_TU z6y#d)u}wIH<;BJ2rFLCUMp;sCBiUCi4uVVG5f=EYWqK=g#QcItYJ zY8$Mqf~h3L?!+Jt9?u|w_D8_J@RU7NB~?f{JB!OJ=!CE>stR0D!`YCbQUhuUSOVOy z2d*%J%|RjChY7(8+Z}ZL)x{dxHI^g&|J6|jbure$DfG~4Fu0tYz=4i3*nmc}1%hU^ zgMNhD511;JGa=CfJqpvo(*b{ig(3SQ3-l2OZ)h)XfYW2q1aP=a1UB03xT@~U5$kKMzj0Qu14Y86l9s#@$G6i-Nk|r>%dS_*QZKczw0-Lg1~MRdMQo>i-mtcnC~GE zTgMt@ASG8oAYig%c@+E!_#Bha!~hUr;771pNF|17Fef0^AxD{1Kq)UT18+i9veOyh z8OSOjhH3>AjC(mA!Jz@Gb}?MQdO=!d_$MibdVajuL2y>A=oG+Ik*cj=fogUf&cemcTT#$gOF1Kr`HE4J! z#5Ei6g3t?zjN2NckhYO~2jCoFcNye>E31nTu3=MZkb-2mJ7|B9-#Md9l*~(twpEmPV5*?xWMe0a`maM4i`$c;9Lvu7ZaR@}kjf zHf!jIDO&dg9}{R4J#jFwO7oy+AQUzs8X~R=)@?GQA>V4&t@8p~V32_q9LNw|aX7N! zw{K8`G@HnrAbr3qa7U4#@K5V$h9{rFhk<*552CSxn%M`H0Q-&<3>(-)F=7KK9;K}! zfl8Qo9M&))oMz+RA;{7`#85D>CLS07(R)m4xdH{E$4%@?f%(zCMp$z{h9UUIN{P3L1PLV;Y`3S{mYPXDqGT1I11dx*^&rJdp;^ zg2;=49ei;FsRzQ~r1ANzGkF~R8~za-K0tBn^<;qY9@GYC6ouP>#tkQpz6)kkffH>z zRf1auJ2NFCay+?pJMGCbr9%l7{e!w(PkX|0=+lTbHcv;e1zdzD+YHw#e4~Is58*ckhW87D+6UHv zfxU)i&7LXxc3kx5T}u_P+eG&ft`wp@hw)2r$E6FncyVQ39&xY z#^^FZtAzIh&gO;{Ff{x#>;c=Lft-(911<>*0#gQW0ulxDusB>MR@U*O(BBjqD{%AJ z3$H##R0TpAYcgosU@(YtIbOr_n)L@~PLna=CP=uJw^3|Qz^2dwFgU@H;(Jk-pkXJr z3p++R+_SO)2(f`CiU&W&YCt8iI6EIYEanwy>iW~*6&QapU`)Vfnl`=wE<>9_dV|LU zqqV98i6XWAagZ8Abc(*s6VSGrm@eteezFyNHJhR1UfE@0u^i*6^$NgQo3Z48&T6nP z3~w=3v(<28mE&zo1z_Ef9bjtTvtm=>&Va#Sbb*7yZnZUsI-b0x2}^e9TUKZEZGt&; zJBssJn8ksoLX4^730hWyBTRAG8Okquy3Cd%lKHaNYKaOEtV1yhk?mPjttMI zVy_)w>lOF23p}H0P0T&)(cx^^icjqcqCa8MhW&3x9^e&2^b4D%Ro!V-QcJ(a+Ba55 zEB4K3wivxE#{u;ZfoYJ~hR6;z>voBI8c1W%q2ZJ4Lm^H(ptj67vII3!_L%E6%f8mM z5C`mJ6OzQ81yV7dASwn(o-ot4_EEPu+?vc%J4|J-WTd!Nc8gItK%jybrcK8^Yib^c zlxIT%?w?&g#B6_Q0sg?v4eIh3v*58MZnS^gQcDgP3z2!SET`=bgMHf1z$2_y(F@Un;rSp5?jgTdt%9@~4`aLCz-rxJIYH?RORC_uVHh4` zaKbDN&8T9j6SbjLSlT_a1uCQy7i-`ju2uu-T@ND@W@Jc#+So1gDgpCJyZPwRIGx*p z5gz<87RM9I(6%~>x)V>VVx70{KFSgZN+Ww1n05n?f)8T7plY{4+ih?sShbZhojI9c zo!&n01mz5zAgbHMem*_~S{kkZvl0kv73a!$9%yVf@MD?}ga~rOk&0zOD+bXA35PUc zukJWpf_Vk3)3&H=408d~MG8C#)V4NB)B;zvf*&JVALW!KqpBr8cM?d4wkVKTvqfi4 z?BcO%)_0K*Ri;bHt1iGell1iKaoVQCXd%kR&>qf+G8Hc+RozKA)2cSCLpw6o)?93y z(>4~`l_H({AO_oj#D&EKBm#S6{xT9ivpt#7(B_0#!s6iPqPfF)-nJ{XCxO<7)DC$C zR*vma*x;AilR}(IEDxYDq_j6ymV1-Iov~?YVh|u@79qB0H)JltHNc zv9MjSbZ2cXbsUqXKHJmQis6qEn>pD>9A@s)8R0UwP*|oV0xR8}2CrkJLRo@V19Htp zu&N6@mS&u-J3G=g-lR5Js96zhaRaOmC`((&nc0MB6md0s_ErJe2xlX)6Kr5INC)WA zi3?cvL~Xw1&c|vBXYAIh?0h|RCiFrVu&g?AY~;<|rO#M_Ygk(4BZQP<>~Ix-5-y3d zL|3iS3HtW0i}p^G4C$Sz%Q+nVIxlhCjDgqgrwsr$^ulZC--#)_#n~JN*Xd3|*}U6Y znq^ui*0FgoG1d*y=f`e;-HBGbL*qJ8qSS2}#b)Ua2XD1Iqg-xee556Zwx)m$9Vb$- zq||de>dp*tE~j>Z)~Q*{gc5i1$U8Pqz;c4e*+;Rt(ZoGr;><1aL`pa`M_XbWz)4J; zGO|oDc27vGlKAV4)?!>pb!{|TZnG(QqFrueRkT%2v-HA13bLEj3vlb>8arjf>dzx@)ar;bVk*<9XD{9S<{}$_Zy9qm3Mi z6?y|Qnbg@7ig_0c6YshnM@MM4&nioM^mb>w%iBpQ1f>2X@H`t9hudK1bGGf*iL+Up zPJHOFID3RxqpMnlXv?H<_B3B+94yNehYsuV_BNsO4elgdQP>~7w#BO+&F7Yck2e_;&M}rZ84Gl0I&nCwB@kz~Wg-a5FW$2r?}CupUO zPxU+@TDP}3yT`e8PF$qI6ZAOkJjBX*aDkDtP@)Izo^R|4G2m<*sX8yTbTJ$_obh|= zEXMCBpP<7zw|8_gD&XwFd*`kA6U=P>V%3IqL*;PR(d}9I4fQw+^cL!H^+u!Nb(d`) zxCinkYRh36p9G$RwM0)ds5Cv!nuyI>9d0!CJL*o*rb||Ay9rT{j_k|5&jZ~;z4JP` z<=DasaLhR!k0OpDLwph*=jlG+thxvjDz-EP8t3$h){MjAjk~2{JTSu9avwP_<2)a> zTFsg#V25)nwHre?pB!tCb9{(3k%T53)6TdW%y>lNCUrW-eP_aoAX`aJ6fbmQ{7D#r zvoVgpG2n3xm(J3J#(k*A*}}9-SI9RpP&Hfu)u!EkLPK9Tu+&PnEpb?NZ#13mQnu4o zz~PYw9GJW%D6E7woT<5m4xM^2uC75JWZB7<$*pTT!f((*O@z~g8~JiW;;QQomsUN_ z5%-9kiA{z##_AbYqI5K4aWx;(M&6m6&G;?0wC!N2rx_bTn@-JWhZ~d4;7QnKpmlTY zAJP#p=1bOhttzpW?0HN%pBUNP(zTjW$!jQ{P|4O@8A;+$z!A>*kKi@Szv2kTV^6pO z=NvZiWoj!?h%ERBc|yibsAgqxE-gjEuX?sfg|phoplOG#M$m3U9Zq?6;v=*x^`#Ma zGGoGdAtrGCD16=7Jt1b%%5j>N7Cwn0oYqXEHW_hFKKAn&eqJBaDAGGEjV}hT;g%cWZo(K#<)_TozDpNoshV2mL?YJH^vKI$1;I^-RAh* z$1OAqPT?9C-be`NO5g!k@f{c=ab6%=To!N{nGm~#W%Hm%U6$bkrf(x;%=gg)W>!a2 zQdq0`2lR~uCw?;~*5HG-_NATb;_SyO0_~BwP9Ffm&R7ZIDyUw`lEzXzA>!mhomu&0 zvgXuG)fHt4r$k_K@{pX~Mq*p`todxq&_W657n(_@pLm?ldnL}r3B}G#IA7zOc$XBi zYa(#0Nt`W^^JZS1rU8c=77=B6dQzF_uRs0T|}9EaPx0W0I?ou2fk7 zhK-@P;q|Dv+85!O*qo~JR#vMAzHpy0!X>5qivcnVVvVN*K-!%p!GOw*BIb6yMwGSv%9^+_d$SHV2uf?d^ z5mcTg)3#(6aIR~{;*V6zWa7&Kg!7`x`08cVwV-`|LN!3I^4KXfJ1?n=0!~XDmAKjI z*$W+x^M%CkT`f_4hn(Ccsf>Bl zmvMAK^%|YT-YcAfY0c{{(MAXphFpuC!YP?dqJDCjO=r>t;zkTyYVBf)3E11>qvfnq=G{6DMI2Ozy4>Njj6m zoap57Z^I-(LYhpLSv4Q42x;;JSzTlM*Wx_?hB(KOB070~LSNv?426H1Fvcb2xQS1` z#EyC(;T+O|++8nMW)1pAhX~_$#$EZ)J}S9;x3+HLvt9}V9>+&m@e_J*>I+JgyprVR zypfN-;xCyb?(&rH*m9N~<9xTH;x$m>`OM=KlT#)XjuA(1Z+@lhw1f*2vLw#o{6!a^ zXX=C+N)vwtIw%}(G868|*}E{Qy_KUW70>C)Af6_~zg2e^#$5=dmYJvm3dOlh_O{lk|=>apkJ0ftQV(W2ZK0=-_^KF^b*E1YF1x=fA zQHgQ(7L>ch>VCtb5u?2 zv{fqAc%39l!h}Arv6p>FF1mXy?%>^JncRegb9J$f?z^flg+N&I!QfIqq=}o;UrvNBFI>(TRHjbcPaH->VWt@3SkjW5zaGq>PpKl z3k}xoaCGOxm(>~PfR#{sg}1qEOD979a{Zrg^ou9-DZJYp!{p@W^Ie1@j!S0#x{YIU zl0aF&IiSaJ-^36uu%VYF?7kez7-vigj^&J|Jq$s@(Hh=6q)eM}{=-OJc$^kE&DAct zzvNR*8Pj+^ulT3q6zys#visbO=F`C|0!|fi90>!CO9|lyhT_mv!1?n`2*=%yfa3|1 zTy#kjUMS&2g)d}HNkZiXc6u)*CQC z3o2?4xa7yjPT{`Qs<`pS-SjZwS#hkJRUSv#fW(o{WmI=zXPRUr`vNWmD)JqQeC&i{ zfztvfF5z(+FTK!rOZoALXR4feQTdgf(3MXnbTUNQ0!Q*f;&g;lIh<2cqKaCE;{`-~ zNTEvLj48uWaU_q+TerB$M!1x51Z3awI4hn=Z{x2(2#r3v*Ae0t0+kqD>B;s{vrG1K@qEiW{4ahrlgjN`%y z;ZzyUr@~^PR@Q188JPVX4#&&{ex2SmXg(d>BEr#Lu}4!O&(p8NdH3}PNGBR9_KNTc z-DpjM9}4yjWh;l%jB{#{zA)i52(ho*75Xf`TZGysmz!?+8Ub#@^al!GcK5Y^>9RZcVksmXTU?meOPBZ8v zHDA>)#RV1(cIG`QT=A#kbO}zKiql1$Dz4M=pO*=g{^S;_Lo<}pA)+oy(-JDD37if% zetDc{#57GMkYG8S;ZVo(ML1QCGdUbHsb^0WaI7~NmxOgP8zu#u)Ke6;HH{=NoEwp; zz-2K`0P{HcfI_O|aNMyXj$xcC;v@wp!3~11dK}3FW^^ukq>+`fNu7e@I+A9fQnFUU zhML1ATz|^_2fy@OR1rrOER8hL+*8ltd_*=*dy2CcI*2OjB{=p*xzN=2Kd0aVVa2%A<4maF zD}U@0iKBpeCY;IR$SGJRM8S*9tbijgWOyC@N zrMOIzIWIKe5{`8_Vv8=Eg$pc<)6;O7Txdp{9&%>?cJa?JVbE_ZiF~&!Vc{=vvCu2S zrCP+fNSdg1rtn9pN}-Cjq6Hc)SC1JDRe8mP4U;Yj0%33@Iff53Wy1fz!^WCJ%5@n z(ig0Rob*hByfDVwO(;x6I1hA`$2jG2RM7AqY@&s7jZGyY{4!2^oY17~zmdG~FUHXy z<#ExK#0bYu830tmj;}XI2~|GEWt>xsOIGbh3&~iR98!Ibb~pH zqxmYSz&UTK#yFzks7hMEg(w>1M3s~Ur3#zJIHon1QH3kFqN}6C<|XtKF8=+F_DMd6 zZ(K~nr8;~l%gfLXqLm2i&f~l!i{>L5HQ`~C)c@9pBAz#a*fNIYaEft>7730|;YlQ9 zh53~B_qIqT1Y9C;xw4y^ummUatq2#+-{GoL@-4$%DiyvLhgd`2tEpeIuR*<|*BXR09oFlfX_>Oy6X&H`n zR~ph0FDxa_1#S-2t_v)QjqbmSt>J9T=b~O3<#6;(2_gfodW4<>^*tmZRw`-qgj^LU z;CRixt5V6~h{bX;fh>rlFAJ}bjh5pK<9sufWl3SaN$8gddEuL|)D@N4FXOmh!Z{*0 zZ|Sm@z!6a33{$tQ3$xObVCzXMRQZ)92be^Z8Txj`ja?ED*oq0G%uZq)Z^%D zKxAs}@wu7GI7OwOv;d64eYhk@794cK8E}%hgTA1vcUs~!hx?+uFl;ltGm3&q6h#X- zCAz5`?zIxWGYuz=o2WvO-Iy%aPnvPGGjwNxU?#&0zx_l zc|mUt>48y7;)*gs0$F_6w=i!rPL?f2$xC=9;VLEJo|2YQ97j>E3J6>%K!xh6!*LWX z$OPH($SA_FnHFV2BrkF}#haR|Lgez8G8{!Z;-@Iw$7PnNx*E%bsG2~V$H1Hl4%Gz0 zRdYDry{w26;XX7Q1w$!OCFMWnGE2gDXA=_m_=YIni|5W&6LL7Y3vqL^h@-@bc$9X5 zqJ6ZXF5HWROL^n)IAPyKnZSfWs)Wiq;_&m~-s=l-2Z|5*R9qmaY6(sWM-*Y3*F-o? zl8ueV;rOb(#Cf8r$})?NC-fIBb&ONd-KkvoC7EEv7W+Tu{J>F!D#bM-EgnM3QAgD?uOsFF>72)1T3`sPMW9y!R6M2lhtVLA>6@`0; zBg>a%OA=6iok}Ln{+i%49BWznU!OGljbSF?DqDU+nzaz_Io}RtOQA2EiIw71j7ua= z^RD`^9o_K~1f@w|M7?{Z1dhX>5;#S4k?W>99Q~xakvK!;N6`de-RT}M@FABf6$O9B zaXZy7G>yxoMmmiFjTOi1MIyAoUw&pcr{F3aI4X%3eKe5pW`NhNN|gduEC!+8l1 zj5G@ONst#3ry`t)_etTK)b4GOT&JeGXdXo0z?K9h)w3|}f=m#&P|hdf5l$gw0vT=@ zjpW#}F>;Ojd@| zq1-tY7kMv5J}N(om`gzJrk)#D!WI;MLq3uR5-v*8b2$FeiNv`x%wZ17o_q{F5Fil>Ncn0R6!>A<|g5U#4hiqMu`&( z8dZWbug6KRI0Z+2oU}{@N8y+4FCw-g&S&?APK{+kNPwiEk;azM4RUf?rAl!+;6jov zaZx@(xI$O7G~BZ+5lureVN!NAJ0h9_j&#ZEaD0g3wwA;BGATW$=%%W0FF$I-hN=ML z47Ih5r`1ANlya*GN8MCr47pHlp+;p=KOuKFDZ=Htn*rxph2l0ng`ZK2%L4hN*;#ca zaABoECsg9*f%17`lvhY#2-_m61tSo+2pHo+zU56$$Y&NReiNj1$6;GVjvo{U9WwRq z<8miE`Kh?7|EfdKA5pv)0xE2t{EBi8r%%J_5}b_EqTCq_LTi@!{396hISu!5Xs6gn zwa`HYoG5$*<;BcEdM1aXKF4cunrDe{rW6|K z6XuHvBNMf`aE{R>sFGWnT-TX`BOv4GoJ(+~9OsysWkMOwlDWDPrWpw!ht3@n5%<=cPZ)k{E;hRzc6sG1rH764Y zWl_pX&h#%)Bp z?V4(Z`z7R(VnQJ+;_hD_XM(n@&{vM=G0Sj#j65M1^L;oB%{WeEB#tL6$I;ggeRq@6 zN-<$oX7_%l>F$H*HmU&R+YqH)NghOXrTMTeaP(d&Ee)3u3*nx=b&?cu-n!#%BX3?H zHX;P66O6yC72}9#L|aropi-q_+Xn^iZbofURlsS9Gn)Q7C{alY;a+wPLxLv=N_Htj z{LSHv5Bnw%jSx_T(*ft}tInkqH*(~JY~5_j({XY_j(N&gfC7#@CTis5#j{A7eRq>I zpo$tfLOkm-oGQUZT}l;B!-e}oS5z!iR3C+z23B2$~wMBJ=%Y|RMvlQVfr{h8*<6SXoizX3H30Ow+`BRA( znnXVh5oChDrWtUebVV{D{KSLzAJN^6aB=hA+lHVjK{Qg>9;-ifRET=PL*(Srv%V zAH{OEh$HK+6mht;3ElS~!>})%{~@;!MnfV~sLz<-74df|OK>W}`KEu#-DQygar~3G ze3D+kiR)pUZF!lH@||aKE3^zpqTBOH%E_-Nld?9Sgr6Mc!&hY8;_fnON@BL?7eFPB zZe0nPK#5FTevENp!a{d*DlSaORe@4mPA2dZM&X`gm#^?;u~;`^rJ|ZpN*F{>irA|l z6Y||nDNBOMF!vR|^jR!h1{}Et;X<91biTJpAxVRoX1Q;HB#a9(VLFazEe$yG;8+7o zocK+NG$xvY2KJ^Xs|hhq37m;=A#CT$or*053AO(2~7(q~{s#)->QenKJFOT%{jC5d>#O6YFp!}giGnIj&!>O=U zge!C@Re%wWA5igXLvb$dZW@6*wY!ODNI25LqQ0skoDrH-e0lQ>xTw3y;obzC_)(u2 zr|HX^`LOK^S>bx8qBPGhG`c+%<49hF^e#7H6z=H(;_hYvR|xlXv`L=WF~ADAIBZK?k|V_6P-Wp>&KDRl!g08lKLtg9n8+kT;@C{(WlJv;jCfXj z5qq+^$#BnEIyIM~1SqGv%fh|Dl?frn<-+r#Rb+tUC(7{<9;3 zlG#>-Q+b?wGO-?2-jsvR{ z-Qq%#c7L}ry^4ho6XU25s)N)x1sCZAuL1HhA?{Mf6Ov5GNS4?=D#Mj@H`!HW9#e{w zIw6N6MvN~9`lKtm^jwY$@m@y{7|DcC(~fXnP5Olv!#x`#6C1^C!m%!qzj0T@sR*ZO z1yr_-eyc9PXufztp%bR!qRI|?h2Ol~Z`=j`)hbLFeG=&e(WT@w&f$2%0sKubOO2eCacIv=J9t(lQlX=xzp`A9pwN>bW%Bi|%Hc(+OnV zC1KlF+yWowaLE*$udgP74-5LDENlm26M{w;a9O@9Dv2%NyrIN6!(l}dP2An&a3AAj zxMv>;Mk8=hcay>|f0i$MoVmN12!fLB)Y7IyaNw{8;x(x+@QtV73{g$)7L;r{1YB8n zQ*D|q=(4{m*TwVxyZBkk$jk=pG6GGUo6n{xuk-l(lk-LLj#Hk{#^zJg_vSN3Wy@I~8>x!Iebn6)`dQ#~8P0uqGsYFPtlLN*Wr~-QxIiOCH^VqO6}y{3UlbouL>a~f zf)dhF;BwJPg!_QwtqHg&C0lu1Qdt`wx3VmC+$giqroyElwZ3o()QOtZpi`w!rQvRDYU#q3oke&$p5&AR4;)!W4d?9|biCBBJnpzTjtxG!@dh|==hJtYVcT5&ztGe;ZL zWEIDPlpyV9WB<~JwqbUp`(}av(x7Nxh0k0tcVFRAju*1mx^E0pMxVUuY;xpGyV_{% zFLLNh!uGT83VVvDA(6)vZy*&=f_J{b2_HX0vvt>P8OgxWq`y&=?G)!IbJ153m6zk% zeU#C|BOX7zA^1%GZ6}+A17hqGBU)G%FEeI5fsfdMKGtb&YF%c!gHOpdaJ8ZT*_k@o znL1jrLtx+072%_Z@XIzRhp?;Np-q+5oGRv>bJetG3n?{%oF&M`u|^#D>-y@M@OOsV zVIRVVDt0s0ACe7Jv+P7vM&>z0_*Oj1tYL;JhE|yTW+^UjwmWj%zQHoPcyP@3*c`y7 zuBUnHQt-6^6sF9_ZYuL1e<#nb=e0>*%f|M#>lYHrEPrX*QP9?81>upZ2zck)PM*%$ zUBHLXf(!Si^zixhTYjw9LAPv!WFCK85ngo`?@4MOXpD||eTMA+)aQ}#eYqZ`RNH)J z^CbhAjxOH}ck*rUwGiAp=m#&+pK%=}lQ`I++q*;+EWSzNffJs)^mpyV`vHub;`Gk^ zM6#cYounO>p`SVu^B<9b173w=Ux*DEu+z4?jHR;U|ZhcX!0ZyZxCl|LeBm??t2Uc3xLPIN$Xl zBjp2=&t0Ci>#6P5ja1+R?!FpHJIXS8Pv^Uiy*qPqlyN|r5@kRCTyEs$)Xx1Cp z#iE5X>>l^WZz5<9ONt(?4?k>jqgS;3p7lPh^Lv61|84MVkj$c=pFm#KFgv) zoaoNTKYx@Z)LFG7jrbpNCOucZlNk%0Gw?Ou7?GCH3c9<-Dys8(=bc~STaTE@k|{+6 zymgixhC*B~s2%0;^_S&~HW$lDz>82kJb4}Oj;_oV^E>9*g{#6UF~{U3grFj@n~^Z+ z+I(E)6ZF$p53E2rkJ}&UG30D`s%0(f)ilkIcpc}8Jne!`wV^<@EXd`T(rhcu2TQ*D z_8wMjbtv9zIP!TPX5VT4I6s#+5`Bxd-a{$8mI_i-FAxjdCUIZxm2aqe8HWZZs^skk7o636682+dip7* z1x`UAW8V|;m|;6=L|Mh)H&mSeX^IhOll`)U(PektZNOVVOi>X&$MX{pW1{5)QI|SR zD2OfdW*^7};!F$Ga=mE{EJFy&&GV|vjjM|3tEQ)SXJ#$8rm+JQlV%^z;~FbI&#+8N zaJw*RsDNF&xh-jICUF9$lXyk5hiksL^T>%Tw=K7*T0zGRqX>xXJj4neF2j#{rK2fH zUwp;<+9IgNJtPOp)4E7H?y6`U0Q zw+z&1FeD!=CBP4w2d!6*r?jbx`%lW3YiewRZ7fo+`M9NNOz0w>WtB5a)3$8RAMl?c zw<0g8lGiM2%9o=MtzKedV@&gO-^R~33ZfjX(@VOUn{KwfTtuy<1RM1@3urdhEXK}$ zrB1&*X>eWi;1F{5+XO3o7WpMPax5(}RkhvvLr1?3n|zS!#<_4q@=Y^UumyC%2&7Vw zV+f+nf%^LJTPZCw*{ywIsYy3};)@X`v{2G=PRz4-6=bN%O|c{nRg#Am>)xnPSr1?C8N>W8={iKgH;DPT@=Q#xwAyi3lo5B38U3 z*;)6vRfAkaDBDjAVZAVK8+NI_pSO^Y<%~l|n3x6`wxLo+Q0^rC?4fTXx&b?fXnnHS z9K90B4Vp{QChsXNNhAz==YV37Hr7OJXS&cxb=gv2K0tPBoer^(?0gX1b^r+$49Xo| zL_AQ{3!@Dk4-l4%OqIo31v+ek64hNFPaVQfAf*7EpN7y;afD(2_rYN7(i~YaS0v<0 zfA~ICAV`gI(bY|yTdMo9k3P`}5Jq;RUiK=H$*@bT_^xkN$;0-B0N>}UnCgBu&%y&b zlKe@mNCHq+71&wlw4GA*!@Tna{!ALyt%bGtOr>#^`9nv5e*%Oo_5Qls4vvOeqS?5) zrJB&$7t@PF*X2MU=GtEggzMcl)_sf;K3BSkBEdpPvA~%35y-ON}G2w|wz5$aYl(Wy=*F^Yc1G=zjnGxmbh z9aGe~X-(VxM6lmld)78hr*unN9G#4gOBV_5U;#JHSIZ{B7}`YsK-&KSKj!!T? zv*s?kh84;%y+9aP()lXTK`M4Rp20Zpv6@tega1KE7)&3>Q*AK)i{yckdm{pZG82G8 zXdFUMyiKabNW=`Gye3qebxxeunnjow7-^kOn_Jo~&Qsf~BLb3`>a%CbbA_+*TxrHa zBo_c7*_NI0p|&I}7Zp0C(-TaYDw9OhGoK?F$*Bo&Fk5xAgAf~O8{RM!B zBDr@qV>NxN!-WzD^W8Xf(>L_B#9*)>&ExKzjFw1;$t~r0Wcr}u$f}|e22=EV>EIq0 zE=WaAz}5?BYl#N{ZF#z`3Uahw7>v9bOnSxKb~D7eRRg4jPyqm`p-EHpXYw_H9F)V` zylsh*l#HlU6*Cqo{Q&_!0(3-79JmuTRu%JkOZfR1bTLX%Yzl~Sr^ja4{5bZAfqL*4>JM+Cf^ z^J$>Q0|@1WbTB)@XE?It6m&!l#E#TpJ6u7Bu%D6!Vf7RZ9yX-T17=fqKjTFn4~kJq zwhC4zv!b?yn&>{e{wz=<6LSFeS5r{i zFJFNXthCvZ7gv&j*{d!vW1ldA6v$`@AU%$&e5SD$R7P4NX{cLEg3&(lEKQ@74W+n0 z*9f_imepQB<_*Bv8OC8ig#(gIC+~)?oSdecjsxz(&H(sUf)eJIn#IyWedx*4eM3mB zCdg&JPree>5_M^6)h>XXC2RJFhyn5yE0S5wcJbTsR?3*{IiI&$;Lle%YdoZjWLN6t zYy_i}bzv6CP+=Qnf+khp%8Fpm!~e9ax(XdxiAp65SWVVFPTyu6d0yp=1Z?`K@k;V* z!?+3~sY$*dL`o$c0}GNpiYLCOGsUo*HmkG4R<3?BT#Q_G;Z#A?m>SSo5IQgmz;uz| ghyCCIE%xkh`kAt9IioabUo9F7a?RSW!`u`92a`~4ZvX%Q literal 0 HcmV?d00001 From 44b90b6a1079a2ff57fd0973f4bca3a3e8551ab0 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 21 Dec 2022 17:07:12 -0500 Subject: [PATCH 55/93] move --- demos/{megacd => misc}/rf5wapianoroll.fur | Bin 1 file changed, 0 insertions(+), 0 deletions(-) rename demos/{megacd => misc}/rf5wapianoroll.fur (100%) diff --git a/demos/megacd/rf5wapianoroll.fur b/demos/misc/rf5wapianoroll.fur similarity index 100% rename from demos/megacd/rf5wapianoroll.fur rename to demos/misc/rf5wapianoroll.fur From 4cf97bba5ea15757af8095864182d62e52735f9e Mon Sep 17 00:00:00 2001 From: Epictyphlosion <34047941+Epictyphlosion@users.noreply.github.com> Date: Wed, 21 Dec 2022 14:37:36 -0800 Subject: [PATCH 56/93] Fixed typos --- src/gui/presets.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/gui/presets.cpp b/src/gui/presets.cpp index 59e26132..428f467a 100644 --- a/src/gui/presets.cpp +++ b/src/gui/presets.cpp @@ -2301,11 +2301,11 @@ void FurnaceGUI::initSystemPresets() { "Alpha denshi Equites", { CH(DIV_SYSTEM_MSM5232, 64, 0, ""), // // clock speed should be 6.144 MHz CH(DIV_SYSTEM_AY8910, 64, 0, "clockSel=14"), - CH(DIV_SYSTEM_DAC, 64, 0, + CH(DIV_SYSTEM_PCM_DAC, 64, 0, "rate=11025\n" "outDepth=5\n" ), - CH(DIV_SYSTEM_DAC, 64, 0, + CH(DIV_SYSTEM_PCM_DAC, 64, 0, "rate=11025\n" "outDepth=5\n" ) // don't know what the actual sample rate is @@ -2397,7 +2397,7 @@ void FurnaceGUI::initSystemPresets() { "clockSel=3\n" "chipType=1\n" ), - CH(DIV_SYSTEM_DAC, 64, 0, + CH(DIV_SYSTEM_PCM_DAC, 64, 0, "rate=11025\n" "outDepth=7\n" ) // don't know what the actual sample rate is @@ -2414,7 +2414,7 @@ void FurnaceGUI::initSystemPresets() { "clockSel=3\n" "chipType=1\n" ), - CH(DIV_SYSTEM_DAC, 64, 0, + CH(DIV_SYSTEM_PCM_DAC, 64, 0, "rate=11025\n" "outDepth=7\n" ) // don't know what the actual sample rate is From 2a65f24b3ff6575dc9ce56ec2d5fd48543a83f37 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 21 Dec 2022 17:55:38 -0500 Subject: [PATCH 57/93] port ExtCh op macro code to OPN family, part 1 --- src/engine/platform/ym2203ext.cpp | 44 +++++++++++++++++++++++++---- src/engine/platform/ym2608ext.cpp | 45 +++++++++++++++++++++++++----- src/engine/platform/ym2610bext.cpp | 45 +++++++++++++++++++++++++----- src/engine/platform/ym2610ext.cpp | 45 +++++++++++++++++++++++++----- 4 files changed, 152 insertions(+), 27 deletions(-) diff --git a/src/engine/platform/ym2203ext.cpp b/src/engine/platform/ym2203ext.cpp index add360b0..f4ad294f 100644 --- a/src/engine/platform/ym2203ext.cpp +++ b/src/engine/platform/ym2203ext.cpp @@ -41,15 +41,32 @@ 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.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,13 +79,15 @@ 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)); + 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; @@ -80,15 +99,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; } diff --git a/src/engine/platform/ym2608ext.cpp b/src/engine/platform/ym2608ext.cpp index 179a632a..f08bdfad 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; } diff --git a/src/engine/platform/ym2610bext.cpp b/src/engine/platform/ym2610bext.cpp index adde670a..a68747f3 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; } diff --git a/src/engine/platform/ym2610ext.cpp b/src/engine/platform/ym2610ext.cpp index a86df585..4bc15389 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; } From 0eb2449c57db46322647cb7293a764341f385560 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 21 Dec 2022 18:02:00 -0500 Subject: [PATCH 58/93] port ExtCh op macro code to OPN family, part 2 --- src/engine/platform/fmshared_OPN.h | 2 ++ src/engine/platform/genesisext.cpp | 1 - src/engine/platform/ym2203ext.cpp | 3 --- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/engine/platform/fmshared_OPN.h b/src/engine/platform/fmshared_OPN.h index 769a1814..6ab448e6 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; diff --git a/src/engine/platform/genesisext.cpp b/src/engine/platform/genesisext.cpp index 6443729f..8e16071a 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) { diff --git a/src/engine/platform/ym2203ext.cpp b/src/engine/platform/ym2203ext.cpp index f4ad294f..b8c40f68 100644 --- a/src/engine/platform/ym2203ext.cpp +++ b/src/engine/platform/ym2203ext.cpp @@ -45,8 +45,6 @@ int DivPlatformYM2203Ext::dispatch(DivCommand c) { 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]; } @@ -80,7 +78,6 @@ int DivPlatformYM2203Ext::dispatch(DivCommand c) { } if (opChan[ch].insChanged) { // TODO how does this work? 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; From 005aab057fb95cee5d08bf66e02076c61bc081cb Mon Sep 17 00:00:00 2001 From: Epictyphlosion <34047941+Epictyphlosion@users.noreply.github.com> Date: Wed, 21 Dec 2022 16:12:15 -0800 Subject: [PATCH 59/93] Wait, you can set custom clock speeds? --- src/gui/presets.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/gui/presets.cpp b/src/gui/presets.cpp index 428f467a..d8cf21af 100644 --- a/src/gui/presets.cpp +++ b/src/gui/presets.cpp @@ -2060,10 +2060,10 @@ void FurnaceGUI::initSystemPresets() { ); ENTRY( "Atari I, Robot", { - CH(DIV_SYSTEM_POKEY, 64, 0, ""), // clock speed should be 1.512 MHz - CH(DIV_SYSTEM_POKEY, 64, 0, ""), // same here - CH(DIV_SYSTEM_POKEY, 64, 0, ""), // same here - CH(DIV_SYSTEM_POKEY, 64, 0, "") // same here... + 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( @@ -2299,7 +2299,7 @@ void FurnaceGUI::initSystemPresets() { ); ENTRY( "Alpha denshi Equites", { - CH(DIV_SYSTEM_MSM5232, 64, 0, ""), // // clock speed should be 6.144 MHz + 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" From 2c36706d229ebb4073e5c56029ea67b3d9badf9d Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 21 Dec 2022 21:50:49 -0500 Subject: [PATCH 60/93] port ExtCh op macro code to OPN family, part 3 --- src/engine/platform/fmshared_OPN.h | 2 ++ src/engine/platform/genesis.h | 2 -- src/engine/platform/genesisext.cpp | 4 ++-- src/engine/platform/ym2203ext.cpp | 26 +++++++++++++++----------- src/engine/platform/ym2608ext.cpp | 27 +++++++++++++++------------ src/engine/platform/ym2610bext.cpp | 27 +++++++++++++++------------ src/engine/platform/ym2610ext.cpp | 27 +++++++++++++++------------ 7 files changed, 64 insertions(+), 51 deletions(-) diff --git a/src/engine/platform/fmshared_OPN.h b/src/engine/platform/fmshared_OPN.h index 6ab448e6..610e6160 100644 --- a/src/engine/platform/fmshared_OPN.h +++ b/src/engine/platform/fmshared_OPN.h @@ -150,6 +150,7 @@ class DivPlatformOPN: public DivPlatformFMBase { unsigned int fmDivBase; unsigned int ayDiv; unsigned char csmChan; + unsigned char lfoValue; bool extSys; DivConfig ayFlags; @@ -162,6 +163,7 @@ class DivPlatformOPN: public DivPlatformFMBase { fmDivBase(d), ayDiv(a), csmChan(cc), + lfoValue(0), extSys(isExtSys) {} }; diff --git a/src/engine/platform/genesis.h b/src/engine/platform/genesis.h index a6b7eec6..1b017922 100644 --- a/src/engine/platform/genesis.h +++ b/src/engine/platform/genesis.h @@ -81,8 +81,6 @@ class DivPlatformGenesis: public DivPlatformOPN { ymfm::ym2612* fm_ymfm; ymfm::ym2612::output_data out_ymfm; DivYM2612Interface iface; - - unsigned char lfoValue; int softPCMTimer; diff --git a/src/engine/platform/genesisext.cpp b/src/engine/platform/genesisext.cpp index 8e16071a..c2040eff 100644 --- a/src/engine/platform/genesisext.cpp +++ b/src/engine/platform/genesisext.cpp @@ -219,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; diff --git a/src/engine/platform/ym2203ext.cpp b/src/engine/platform/ym2203ext.cpp index b8c40f68..72c4e749 100644 --- a/src/engine/platform/ym2203ext.cpp +++ b/src/engine/platform/ym2203ext.cpp @@ -173,8 +173,9 @@ 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)); + case DIV_CMD_FM_LFO: { // ??? + lfoValue=(c.value&7)|((c.value>>4)<<3); + rWrite(0x22,lfoValue); break; } case DIV_CMD_FM_FB: { @@ -182,20 +183,23 @@ int DivPlatformYM2203Ext::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; } diff --git a/src/engine/platform/ym2608ext.cpp b/src/engine/platform/ym2608ext.cpp index f08bdfad..d95582d1 100644 --- a/src/engine/platform/ym2608ext.cpp +++ b/src/engine/platform/ym2608ext.cpp @@ -140,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: { @@ -193,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: { @@ -201,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; } diff --git a/src/engine/platform/ym2610bext.cpp b/src/engine/platform/ym2610bext.cpp index a68747f3..5cfffcf2 100644 --- a/src/engine/platform/ym2610bext.cpp +++ b/src/engine/platform/ym2610bext.cpp @@ -136,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: { @@ -189,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: { @@ -197,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; } diff --git a/src/engine/platform/ym2610ext.cpp b/src/engine/platform/ym2610ext.cpp index 4bc15389..52705500 100644 --- a/src/engine/platform/ym2610ext.cpp +++ b/src/engine/platform/ym2610ext.cpp @@ -136,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: { @@ -189,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: { @@ -197,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; } From d47881bf99c34a6ccd336cd13b449b868895e51b Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 22 Dec 2022 03:57:14 -0500 Subject: [PATCH 61/93] new SNES demo song by BlueElectric05 --- demos/snes/Breezy.fur | Bin 0 -> 190888 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 demos/snes/Breezy.fur diff --git a/demos/snes/Breezy.fur b/demos/snes/Breezy.fur new file mode 100644 index 0000000000000000000000000000000000000000..20c5121a6a78653ec707d828b1e53b9c6fd7ece9 GIT binary patch literal 190888 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

C(eiverMV&W@0 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 literal 0 HcmV?d00001 From eab679e9d3e28699db06f156cde7d524e7ae176e Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 22 Dec 2022 13:10:08 -0500 Subject: [PATCH 62/93] GUI: improve system detection algorithm (maybe) --- src/engine/config.cpp | 13 +++++++++++++ src/engine/config.h | 6 ++++++ src/gui/gui.cpp | 34 +++++++++++++++++++++++++++++----- src/gui/gui.h | 1 + src/gui/presets.cpp | 6 +++--- 5 files changed, 52 insertions(+), 8 deletions(-) diff --git a/src/engine/config.cpp b/src/engine/config.cpp index ce7b1b4a..0c7ce8ec 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 { + 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/gui/gui.cpp b/src/gui/gui.cpp index 76af88fc..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)); @@ -5057,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"); @@ -5071,6 +5094,7 @@ bool FurnaceGUI::loop() { w->finish(); } } + logD("backup saved."); backupTimer=30.0; return true; }); diff --git a/src/gui/gui.h b/src/gui/gui.h index 805ce67c..51ce9c08 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); }; diff --git a/src/gui/presets.cpp b/src/gui/presets.cpp index aa9648da..061173d3 100644 --- a/src/gui/presets.cpp +++ b/src/gui/presets.cpp @@ -330,7 +330,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( @@ -2611,9 +2611,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, From e74d7f19227edf60a573150518434bebb2f1929a Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 22 Dec 2022 15:40:10 -0500 Subject: [PATCH 63/93] well I need to discard it? I am not going to use that variable, you picky MSVC --- src/engine/config.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/config.cpp b/src/engine/config.cpp index 0c7ce8ec..86c7ddc0 100644 --- a/src/engine/config.cpp +++ b/src/engine/config.cpp @@ -177,7 +177,7 @@ String DivConfig::getString(String key, String fallback) const { bool DivConfig::has(String key) { try { - conf.at(key); + String test=conf.at(key); } catch (std::out_of_range& e) { return false; } From 2373884b5eaecaad00c34751953b7be8a2f18357 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 22 Dec 2022 15:47:05 -0500 Subject: [PATCH 64/93] crash test 1 --- src/log.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/log.cpp b/src/log.cpp index 32023791..889e1cb5 100644 --- a/src/log.cpp +++ b/src/log.cpp @@ -163,6 +163,9 @@ void _logFileThread() { } bool startLogFile(const char* path) { + logFileAvail=false; + return false; + /* if (logFileAvail) return true; // rotate log file if possible @@ -181,6 +184,7 @@ bool startLogFile(const char* path) { logFileThread=new std::thread(_logFileThread); return true; + */ } bool finishLogFile() { @@ -196,4 +200,4 @@ bool finishLogFile() { fclose(logFile); return true; -} \ No newline at end of file +} From 3a94a7acde0357fd6a74d4992a65dcb43ed89fb8 Mon Sep 17 00:00:00 2001 From: Waldemar Pawlaszek Date: Thu, 22 Dec 2022 21:53:29 +0100 Subject: [PATCH 65/93] Implementation of POKEY core based on ASAP (http://asap.sourceforge.net) --- CMakeLists.txt | 1 + src/engine/platform/pokey.cpp | 8 + src/engine/platform/pokey.h | 4 + src/engine/platform/sound/pokey/Pokey.cpp | 652 ++++++++++++++++++++++ src/engine/platform/sound/pokey/Pokey.hpp | 35 ++ 5 files changed, 700 insertions(+) create mode 100644 src/engine/platform/sound/pokey/Pokey.cpp create mode 100644 src/engine/platform/sound/pokey/Pokey.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 0bd77ebc..0b6bd139 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -425,6 +425,7 @@ 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/Pokey.cpp src/engine/platform/sound/qsound.c diff --git a/src/engine/platform/pokey.cpp b/src/engine/platform/pokey.cpp index 4d22a196..4a695587 100644 --- a/src/engine/platform/pokey.cpp +++ b/src/engine/platform/pokey.cpp @@ -68,12 +68,15 @@ void DivPlatformPOKEY::acquire(short* bufL, short* bufR, size_t start, size_t le for (size_t h=start; hwrite( w.addr, w.val ); Update_pokey_sound_mz(&pokey,w.addr,w.val,0); regPool[w.addr&0x0f]=w.val; writes.pop(); } mzpokeysnd_process_16(&pokey,&bufL[h],1); + mPokey2->sampleAudio( &bufL[h], 1 ); + bufL[h] *= 160; if (++oscBufDelay>=14) { oscBufDelay=0; @@ -370,6 +373,7 @@ DivDispatchOscBuffer* DivPlatformPOKEY::getOscBuffer(int ch) { } unsigned char* DivPlatformPOKEY::getRegisterPool() { + return const_cast( mPokey2->getRegisterPool() ); return regPool; } @@ -389,6 +393,7 @@ void DivPlatformPOKEY::reset() { } ResetPokeyState(&pokey); + mPokey2->reset(); audctl=0; audctlChanged=true; @@ -419,6 +424,9 @@ void DivPlatformPOKEY::setFlags(const DivConfig& flags) { for (int i=0; i<4; i++) { oscBuf[i]->rate=rate/14; } + + mPokey2 = std::make_unique( chipClock, chipClock ); + } void DivPlatformPOKEY::poke(unsigned int addr, unsigned short val) { diff --git a/src/engine/platform/pokey.h b/src/engine/platform/pokey.h index 6e17145f..9560e113 100644 --- a/src/engine/platform/pokey.h +++ b/src/engine/platform/pokey.h @@ -27,6 +27,9 @@ extern "C" { #include "sound/pokey/mzpokeysnd.h" } +#include "sound/pokey/Pokey.hpp" + + class DivPlatformPOKEY: public DivDispatch { struct Channel: public SharedChannel { unsigned char wave; @@ -49,6 +52,7 @@ class DivPlatformPOKEY: public DivDispatch { bool audctlChanged; unsigned char oscBufDelay; PokeyState pokey; + std::unique_ptr mPokey2; unsigned char regPool[16]; friend void putDispatchChip(void*,int); friend void putDispatchChan(void*,int,int); diff --git a/src/engine/platform/sound/pokey/Pokey.cpp b/src/engine/platform/sound/pokey/Pokey.cpp new file mode 100644 index 00000000..f9f110d2 --- /dev/null +++ b/src/engine/platform/sound/pokey/Pokey.cpp @@ -0,0 +1,652 @@ +/** + * 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 "Pokey.hpp" +#include +#include +#include +#include +#include + +namespace Test +{ + +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 MuteUser = 4; +static constexpr int MuteSerialInput = 8; + +int32_t clamp( int32_t v, int32_t lo, int32_t hi ) +{ + return v < lo ? lo : ( v > hi ? hi : v ); +} + +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 }, mOut{ 0 }, mDelta{ 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 + { + int newOut; + if ( ( mAudc & 0x40 ) != 0 ) + newOut = 0x5370 >> (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; + } + else + { + muteUltrasound( cycle ); + if ( mDelta > 0 ) + mDelta = volume; + else + mDelta = -volume; + } + } + + 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; + bool mInit; +}; + +} + + +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{}, mPokeyClock{ pokeyClock * 8 }, mTick{}, mNextTick{}, mSampleRate{ sampleRate }, mSamplesRemainder{}, + mReloadCycles1{ 28 }, mReloadCycles3{ 28 }, mDivCycles{ 28 }, + mTicksPerSample{ mPokeyClock / mSampleRate, mPokeyClock % 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; + oscb[1]->data[oscb[1]->needle++]=ch1; + oscb[2]->data[oscb[2]->needle++]=ch2; + oscb[3]->data[oscb[3]->needle++]=ch3; + } + + 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; + + uint32_t mPokeyClock; + uint64_t mTick; + uint64_t mNextTick; + uint32_t mSampleRate; + uint32_t mSamplesRemainder; + int mReloadCycles1; + int mReloadCycles3; + int mDivCycles; + std::pair mTicksPerSample; + +}; + + +Pokey::Pokey( uint32_t pokeyClock, uint32_t sampleRate ) : mPokey{}, mPokeyClock{ pokeyClock }, mSampleRate{ sampleRate } +{ +} + +Pokey::~Pokey() +{ +} + +void Pokey::write( uint8_t address, uint8_t value ) +{ + mPokey->write( address, value ); +} + +void Pokey::sampleAudio( int16_t* buf, size_t size, DivDispatchOscBuffer** oscb ) +{ + for ( size_t i = 0; i < size; ++i ) + { + buf[i] = mPokey->sampleAudio( oscb ); + } +} + +uint8_t const* Pokey::getRegisterPool() +{ + return mPokey->getRegisterPool(); +} + +void Pokey::reset() +{ + mPokey = std::make_unique( mPokeyClock, mSampleRate ); +} + +} diff --git a/src/engine/platform/sound/pokey/Pokey.hpp b/src/engine/platform/sound/pokey/Pokey.hpp new file mode 100644 index 00000000..09a21c4a --- /dev/null +++ b/src/engine/platform/sound/pokey/Pokey.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include +#include + +// can you forgive me +#include "../../../dispatch.h" + +namespace Test +{ + +class PokeyPimpl; + +class Pokey +{ +public: + + Pokey( uint32_t pokeyClock, uint32_t sampleRate ); + ~Pokey(); + + void write( uint8_t address, uint8_t value ); + void sampleAudio( int16_t* buf, size_t size, DivDispatchOscBuffer** oscb = NULL ); + + uint8_t const* getRegisterPool(); + + void reset(); + +private: + + std::unique_ptr mPokey; + uint32_t mPokeyClock; + uint32_t mSampleRate; +}; + +} From 4a7e76c4488c9ae4d426484508d2a42cc07533de Mon Sep 17 00:00:00 2001 From: Waldemar Pawlaszek Date: Thu, 22 Dec 2022 22:40:29 +0100 Subject: [PATCH 66/93] Renaming new POKEY core to AltASAP. Added core selection. --- CMakeLists.txt | 2 +- src/engine/dispatchContainer.cpp | 1 + src/engine/platform/pokey.cpp | 51 +++++++++++++++---- src/engine/platform/pokey.h | 8 ++- .../sound/pokey/{Pokey.cpp => AltASAP.cpp} | 11 ++-- .../sound/pokey/{Pokey.hpp => AltASAP.hpp} | 4 +- src/gui/gui.h | 2 + src/gui/settings.cpp | 15 +++++- 8 files changed, 71 insertions(+), 23 deletions(-) rename src/engine/platform/sound/pokey/{Pokey.cpp => AltASAP.cpp} (95%) rename src/engine/platform/sound/pokey/{Pokey.hpp => AltASAP.hpp} (75%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0b6bd139..6e37c88b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -425,7 +425,7 @@ 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/Pokey.cpp +src/engine/platform/sound/pokey/AltASAP.cpp src/engine/platform/sound/qsound.c diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index 710a2de4..161aa4e1 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -341,6 +341,7 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do break; case DIV_SYSTEM_POKEY: dispatch=new DivPlatformPOKEY; + ((DivPlatformPOKEY*)dispatch)->setAltASAP(eng->getConfInt("pokeyCore",0)==1); break; case DIV_SYSTEM_QSOUND: dispatch=new DivPlatformQSound; diff --git a/src/engine/platform/pokey.cpp b/src/engine/platform/pokey.cpp index 4a695587..8e1bef25 100644 --- a/src/engine/platform/pokey.cpp +++ b/src/engine/platform/pokey.cpp @@ -65,18 +65,22 @@ const char** DivPlatformPOKEY::getRegisterSheet() { } 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; hwrite( w.addr, w.val ); Update_pokey_sound_mz(&pokey,w.addr,w.val,0); regPool[w.addr&0x0f]=w.val; writes.pop(); } - mzpokeysnd_process_16(&pokey,&bufL[h],1); - mPokey2->sampleAudio( &bufL[h], 1 ); - bufL[h] *= 160; + mzpokeysnd_process_16(&pokey,&buf[h],1); if (++oscBufDelay>=14) { oscBufDelay=0; @@ -86,6 +90,23 @@ void DivPlatformPOKEY::acquire(short* bufL, short* bufR, size_t start, size_t le 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=14) { + oscBufDelay=0; + buf[h]=altASAP->sampleAudio(oscBuf); + } else { + buf[h]=altASAP->sampleAudio(); + } + } } void DivPlatformPOKEY::tick(bool sysTick) { @@ -373,8 +394,10 @@ DivDispatchOscBuffer* DivPlatformPOKEY::getOscBuffer(int ch) { } unsigned char* DivPlatformPOKEY::getRegisterPool() { - return const_cast( mPokey2->getRegisterPool() ); - return regPool; + if (useAltASAP) + return const_cast(altASAP->getRegisterPool()); + else + return regPool; } int DivPlatformPOKEY::getRegisterPoolSize() { @@ -392,8 +415,10 @@ void DivPlatformPOKEY::reset() { addWrite(0xffffffff,0); } - ResetPokeyState(&pokey); - mPokey2->reset(); + if (useAltASAP) + altASAP->reset(); + else + ResetPokeyState(&pokey); audctl=0; audctlChanged=true; @@ -425,7 +450,8 @@ void DivPlatformPOKEY::setFlags(const DivConfig& flags) { oscBuf[i]->rate=rate/14; } - mPokey2 = std::make_unique( chipClock, chipClock ); + if (useAltASAP) + altASAP=std::make_unique(chipClock, chipClock); } @@ -447,7 +473,8 @@ int DivPlatformPOKEY::init(DivEngine* p, int channels, int sugRate, const DivCon oscBuf[i]=new DivDispatchOscBuffer; } - MZPOKEYSND_Init(&pokey); + if (!useAltASAP) + MZPOKEYSND_Init(&pokey); setFlags(flags); reset(); @@ -458,6 +485,10 @@ 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 index 9560e113..a326bb37 100644 --- a/src/engine/platform/pokey.h +++ b/src/engine/platform/pokey.h @@ -27,7 +27,7 @@ extern "C" { #include "sound/pokey/mzpokeysnd.h" } -#include "sound/pokey/Pokey.hpp" +#include "sound/pokey/AltASAP.hpp" class DivPlatformPOKEY: public DivDispatch { @@ -52,12 +52,15 @@ class DivPlatformPOKEY: public DivDispatch { bool audctlChanged; unsigned char oscBufDelay; PokeyState pokey; - std::unique_ptr mPokey2; + std::unique_ptr 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); @@ -77,6 +80,7 @@ class DivPlatformPOKEY: public DivDispatch { const char** getRegisterSheet(); int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags); void quit(); + void setAltASAP(bool useAltASAP); ~DivPlatformPOKEY(); }; diff --git a/src/engine/platform/sound/pokey/Pokey.cpp b/src/engine/platform/sound/pokey/AltASAP.cpp similarity index 95% rename from src/engine/platform/sound/pokey/Pokey.cpp rename to src/engine/platform/sound/pokey/AltASAP.cpp index f9f110d2..37174513 100644 --- a/src/engine/platform/sound/pokey/Pokey.cpp +++ b/src/engine/platform/sound/pokey/AltASAP.cpp @@ -20,14 +20,14 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "Pokey.hpp" +#include "AltASAP.hpp" #include #include #include #include #include -namespace Test +namespace AltASAP { namespace @@ -631,12 +631,9 @@ void Pokey::write( uint8_t address, uint8_t value ) mPokey->write( address, value ); } -void Pokey::sampleAudio( int16_t* buf, size_t size, DivDispatchOscBuffer** oscb ) +int16_t Pokey::sampleAudio( DivDispatchOscBuffer** oscb ) { - for ( size_t i = 0; i < size; ++i ) - { - buf[i] = mPokey->sampleAudio( oscb ); - } + return mPokey->sampleAudio( oscb ) * 160; //just some magick value to match the audio level of mzpokeysnd } uint8_t const* Pokey::getRegisterPool() diff --git a/src/engine/platform/sound/pokey/Pokey.hpp b/src/engine/platform/sound/pokey/AltASAP.hpp similarity index 75% rename from src/engine/platform/sound/pokey/Pokey.hpp rename to src/engine/platform/sound/pokey/AltASAP.hpp index 09a21c4a..930bf6aa 100644 --- a/src/engine/platform/sound/pokey/Pokey.hpp +++ b/src/engine/platform/sound/pokey/AltASAP.hpp @@ -6,7 +6,7 @@ // can you forgive me #include "../../../dispatch.h" -namespace Test +namespace AltASAP { class PokeyPimpl; @@ -19,7 +19,7 @@ public: ~Pokey(); void write( uint8_t address, uint8_t value ); - void sampleAudio( int16_t* buf, size_t size, DivDispatchOscBuffer** oscb = NULL ); + int16_t sampleAudio( DivDispatchOscBuffer** oscb = nullptr ); uint8_t const* getRegisterPool(); diff --git a/src/gui/gui.h b/src/gui/gui.h index 805ce67c..bc12fe2c 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -1177,6 +1177,7 @@ class FurnaceGUI { int nesCore; int fdsCore; int c64Core; + int pokeyCore; int pcSpeakerOutMethod; String yrw801Path; String tg100Path; @@ -1309,6 +1310,7 @@ class FurnaceGUI { nesCore(0), fdsCore(0), c64Core(1), + pokeyCore(0), pcSpeakerOutMethod(0), yrw801Path(""), tg100Path(""), diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index d40e4dac..95f83d94 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -102,6 +102,11 @@ const char* c64Cores[]={ "reSIDfp" }; +const char* pokeyCores[]={ + "mzpokeysnd", + "altASAP" +}; + 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",0); 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",0) ); 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); From 1b2eb9cacb0fd251a7d46147b71ff6cf9f334f74 Mon Sep 17 00:00:00 2001 From: Waldemar Pawlaszek Date: Thu, 22 Dec 2022 23:04:03 +0100 Subject: [PATCH 67/93] bugfixes --- src/engine/platform/sound/pokey/AltASAP.cpp | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/engine/platform/sound/pokey/AltASAP.cpp b/src/engine/platform/sound/pokey/AltASAP.cpp index 37174513..befc5b7d 100644 --- a/src/engine/platform/sound/pokey/AltASAP.cpp +++ b/src/engine/platform/sound/pokey/AltASAP.cpp @@ -39,11 +39,6 @@ static constexpr int MuteInit = 2; static constexpr int MuteUser = 4; static constexpr int MuteSerialInput = 8; -int32_t clamp( int32_t v, int32_t lo, int32_t hi ) -{ - return v < lo ? lo : ( v > hi ? hi : v ); -} - struct PokeyBase { int64_t mPolyIndex; @@ -131,7 +126,7 @@ private: class AudioChannel { public: - AudioChannel( ActionQueue& queue, uint32_t number ) : mQueue{ queue }, mNumber{ number }, mAudf{ 0 }, mAudc{ 0 }, mPeriodCycles{ 28 }, mMute{ MuteFrequency }, mOut{ 0 }, mDelta{ 0 } + AudioChannel( ActionQueue& queue, uint32_t number ) : mQueue{ queue }, mNumber{ number }, mAudf{ 0 }, mAudc{ 0 }, mPeriodCycles{ 28 }, mMute{ MuteFrequency }, mDelta{ 0 }, mOut{ 0 } { } @@ -178,9 +173,9 @@ public: mOut ^= 1; else { - int newOut; + uint32_t newOut; if ( ( mAudc & 0x40 ) != 0 ) - newOut = 0x5370 >> (int)( poly % 15 ); // 000011101100101 + newOut = 0x5370u >> (int)( poly % 15 ); // 000011101100101 else if ( pokey.mAudctl < 0x80 ) { poly %= 131071; @@ -278,7 +273,6 @@ private: uint32_t mMute; int32_t mDelta; uint32_t mOut; - bool mInit; }; } From 44a26791c603d7e7eff6059f6bc8d5bd61fab417 Mon Sep 17 00:00:00 2001 From: Waldemar Pawlaszek Date: Thu, 22 Dec 2022 23:17:38 +0100 Subject: [PATCH 68/93] Applied code review remarks. --- src/engine/platform/pokey.cpp | 27 +++++++++++++-------- src/engine/platform/sound/pokey/AltASAP.cpp | 2 +- src/gui/settings.cpp | 4 +-- 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/engine/platform/pokey.cpp b/src/engine/platform/pokey.cpp index 8e1bef25..32c677ed 100644 --- a/src/engine/platform/pokey.cpp +++ b/src/engine/platform/pokey.cpp @@ -65,10 +65,12 @@ const char** DivPlatformPOKEY::getRegisterSheet() { } void DivPlatformPOKEY::acquire(short* bufL, short* bufR, size_t start, size_t len) { - if (useAltASAP) + if (useAltASAP) { acquireASAP(bufL, start, len); - else + } + else { acquireMZ(bufL, start, len); + } } void DivPlatformPOKEY::acquireMZ(short* buf, size_t start, size_t len) { @@ -394,10 +396,12 @@ DivDispatchOscBuffer* DivPlatformPOKEY::getOscBuffer(int ch) { } unsigned char* DivPlatformPOKEY::getRegisterPool() { - if (useAltASAP) + if (useAltASAP) { return const_cast(altASAP->getRegisterPool()); - else + } + else { return regPool; + } } int DivPlatformPOKEY::getRegisterPoolSize() { @@ -415,10 +419,12 @@ void DivPlatformPOKEY::reset() { addWrite(0xffffffff,0); } - if (useAltASAP) + if (useAltASAP) { altASAP->reset(); - else + } + else { ResetPokeyState(&pokey); + } audctl=0; audctlChanged=true; @@ -450,9 +456,9 @@ void DivPlatformPOKEY::setFlags(const DivConfig& flags) { oscBuf[i]->rate=rate/14; } - if (useAltASAP) + if (useAltASAP) { altASAP=std::make_unique(chipClock, chipClock); - + } } void DivPlatformPOKEY::poke(unsigned int addr, unsigned short val) { @@ -473,8 +479,9 @@ int DivPlatformPOKEY::init(DivEngine* p, int channels, int sugRate, const DivCon oscBuf[i]=new DivDispatchOscBuffer; } - if (!useAltASAP) + if (!useAltASAP) { MZPOKEYSND_Init(&pokey); + } setFlags(flags); reset(); @@ -487,7 +494,7 @@ void DivPlatformPOKEY::quit() { } } -void DivPlatformPOKEY::setAltASAP(bool value){ +void DivPlatformPOKEY::setAltASAP(bool value) { useAltASAP=value; } diff --git a/src/engine/platform/sound/pokey/AltASAP.cpp b/src/engine/platform/sound/pokey/AltASAP.cpp index befc5b7d..ec5bbf74 100644 --- a/src/engine/platform/sound/pokey/AltASAP.cpp +++ b/src/engine/platform/sound/pokey/AltASAP.cpp @@ -612,7 +612,7 @@ private: }; -Pokey::Pokey( uint32_t pokeyClock, uint32_t sampleRate ) : mPokey{}, mPokeyClock{ pokeyClock }, mSampleRate{ sampleRate } +Pokey::Pokey( uint32_t pokeyClock, uint32_t sampleRate ) : mPokey{ std::make_unique( mPokeyClock, mSampleRate ) }, mPokeyClock{ pokeyClock }, mSampleRate{ sampleRate } { } diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 95f83d94..9f7de2ae 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -103,8 +103,8 @@ const char* c64Cores[]={ }; const char* pokeyCores[]={ - "mzpokeysnd", - "altASAP" + "Atari800 (mzpokeysnd)", + "ASAP (C++ port)" }; const char* pcspkrOutMethods[]={ From d66042b9c72421dd340edfd6c19b1560192b70af Mon Sep 17 00:00:00 2001 From: Waldemar Pawlaszek Date: Thu, 22 Dec 2022 23:25:14 +0100 Subject: [PATCH 69/93] macOS compilation fix --- src/engine/platform/sound/pokey/AltASAP.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/engine/platform/sound/pokey/AltASAP.cpp b/src/engine/platform/sound/pokey/AltASAP.cpp index ec5bbf74..65ad339a 100644 --- a/src/engine/platform/sound/pokey/AltASAP.cpp +++ b/src/engine/platform/sound/pokey/AltASAP.cpp @@ -36,7 +36,6 @@ 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 MuteUser = 4; static constexpr int MuteSerialInput = 8; struct PokeyBase From af770ec7012b7dc0c5038526bf6773ee01e8bd72 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 22 Dec 2022 19:15:35 -0500 Subject: [PATCH 70/93] code style --- src/engine/platform/pokey.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/engine/platform/pokey.cpp b/src/engine/platform/pokey.cpp index 32c677ed..e8892b1f 100644 --- a/src/engine/platform/pokey.cpp +++ b/src/engine/platform/pokey.cpp @@ -67,8 +67,7 @@ const char** DivPlatformPOKEY::getRegisterSheet() { void DivPlatformPOKEY::acquire(short* bufL, short* bufR, size_t start, size_t len) { if (useAltASAP) { acquireASAP(bufL, start, len); - } - else { + } else { acquireMZ(bufL, start, len); } } @@ -398,8 +397,7 @@ DivDispatchOscBuffer* DivPlatformPOKEY::getOscBuffer(int ch) { unsigned char* DivPlatformPOKEY::getRegisterPool() { if (useAltASAP) { return const_cast(altASAP->getRegisterPool()); - } - else { + } else { return regPool; } } @@ -421,8 +419,7 @@ void DivPlatformPOKEY::reset() { if (useAltASAP) { altASAP->reset(); - } - else { + } else { ResetPokeyState(&pokey); } From 05d389adac7eb5af25d69fccb949e6f246e1aa17 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 22 Dec 2022 19:25:11 -0500 Subject: [PATCH 71/93] update credits --- src/gui/about.cpp | 3 +++ src/main.cpp | 2 ++ 2 files changed, 5 insertions(+) diff --git a/src/gui/about.cpp b/src/gui/about.cpp index 0d14465d..d7346638 100644 --- a/src/gui/about.cpp +++ b/src/gui/about.cpp @@ -154,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/main.cpp b/src/main.cpp index 79502ad6..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; } From 42367a5601c6832bb304a75e35b81dd63d3dc1f7 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 22 Dec 2022 19:37:10 -0500 Subject: [PATCH 72/93] POKEY: optimize ASAP core a bit --- src/engine/platform/pokey.cpp | 29 ++++++++++++++++++----------- src/engine/platform/pokey.h | 2 +- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/src/engine/platform/pokey.cpp b/src/engine/platform/pokey.cpp index e8892b1f..bb9a0a7a 100644 --- a/src/engine/platform/pokey.cpp +++ b/src/engine/platform/pokey.cpp @@ -70,9 +70,9 @@ void DivPlatformPOKEY::acquire(short* bufL, short* bufR, size_t start, size_t le } else { acquireMZ(bufL, start, len); } -} - -void DivPlatformPOKEY::acquireMZ(short* buf, size_t start, size_t len) { +} + +void DivPlatformPOKEY::acquireMZ(short* buf, size_t start, size_t len) { for (size_t h=start; hdata[oscBuf[3]->needle++]=pokey.outvol_3<<11; } } -} - -void DivPlatformPOKEY::acquireASAP(short* buf, size_t start, size_t len) { +} + +void DivPlatformPOKEY::acquireASAP(short* buf, size_t start, size_t len) { while (!writes.empty()) { QueuedWrite w=writes.front(); altASAP->write(w.addr, w.val); @@ -454,7 +454,10 @@ void DivPlatformPOKEY::setFlags(const DivConfig& flags) { } if (useAltASAP) { - altASAP=std::make_unique(chipClock, chipClock); + if (altASAP) { + delete altASAP; + } + altASAP=new AltASAP::Pokey(chipClock, chipClock); } } @@ -471,6 +474,7 @@ int DivPlatformPOKEY::init(DivEngine* p, int channels, int sugRate, const DivCon dumpWrites=false; skipRegisterWrites=false; oscBufDelay=0; + altASAP=NULL; for (int i=0; i<4; i++) { isMuted[i]=false; oscBuf[i]=new DivDispatchOscBuffer; @@ -489,10 +493,13 @@ void DivPlatformPOKEY::quit() { for (int i=0; i<4; i++) { delete oscBuf[i]; } -} - -void DivPlatformPOKEY::setAltASAP(bool value) { - useAltASAP=value; + if (altASAP) { + delete altASAP; + } +} + +void DivPlatformPOKEY::setAltASAP(bool value) { + useAltASAP=value; } DivPlatformPOKEY::~DivPlatformPOKEY() { diff --git a/src/engine/platform/pokey.h b/src/engine/platform/pokey.h index a326bb37..f242ae7b 100644 --- a/src/engine/platform/pokey.h +++ b/src/engine/platform/pokey.h @@ -52,7 +52,7 @@ class DivPlatformPOKEY: public DivDispatch { bool audctlChanged; unsigned char oscBufDelay; PokeyState pokey; - std::unique_ptr altASAP; + AltASAP::Pokey* altASAP; bool useAltASAP; unsigned char regPool[16]; friend void putDispatchChip(void*,int); From 84c4e48fbe3a3492790287d5ca3ff8cf04815bb1 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 22 Dec 2022 19:45:08 -0500 Subject: [PATCH 73/93] POKEY: optimize ASAP core a lot --- src/engine/platform/pokey.cpp | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/engine/platform/pokey.cpp b/src/engine/platform/pokey.cpp index bb9a0a7a..7427733c 100644 --- a/src/engine/platform/pokey.cpp +++ b/src/engine/platform/pokey.cpp @@ -101,7 +101,7 @@ void DivPlatformPOKEY::acquireASAP(short* buf, size_t start, size_t len) { } for (size_t h=start; h=14) { + if (++oscBufDelay>=2) { oscBufDelay=0; buf[h]=altASAP->sampleAudio(oscBuf); } else { @@ -448,16 +448,21 @@ void DivPlatformPOKEY::setFlags(const DivConfig& flags) { chipClock=COLOR_NTSC/2.0; } CHECK_CUSTOM_CLOCK; - rate=chipClock; - for (int i=0; i<4; i++) { - oscBuf[i]->rate=rate/14; - } if (useAltASAP) { + rate=chipClock/7; + for (int i=0; i<4; i++) { + oscBuf[i]->rate=rate/2; + } if (altASAP) { delete altASAP; } - altASAP=new AltASAP::Pokey(chipClock, chipClock); + altASAP=new AltASAP::Pokey(chipClock,rate); + } else { + rate=chipClock; + for (int i=0; i<4; i++) { + oscBuf[i]->rate=rate/14; + } } } From f3973a5ff5c85dc930449a71983fa33ac062483a Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 22 Dec 2022 19:47:13 -0500 Subject: [PATCH 74/93] POKEY: make ASAP core the default --- src/engine/dispatchContainer.cpp | 2 +- src/gui/gui.h | 2 +- src/gui/settings.cpp | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index 161aa4e1..668864ae 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -341,7 +341,7 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do break; case DIV_SYSTEM_POKEY: dispatch=new DivPlatformPOKEY; - ((DivPlatformPOKEY*)dispatch)->setAltASAP(eng->getConfInt("pokeyCore",0)==1); + ((DivPlatformPOKEY*)dispatch)->setAltASAP(eng->getConfInt("pokeyCore",1)==1); break; case DIV_SYSTEM_QSOUND: dispatch=new DivPlatformQSound; diff --git a/src/gui/gui.h b/src/gui/gui.h index 2b7a2be4..cd2c3247 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -1311,7 +1311,7 @@ class FurnaceGUI { nesCore(0), fdsCore(0), c64Core(1), - pokeyCore(0), + pokeyCore(1), pcSpeakerOutMethod(0), yrw801Path(""), tg100Path(""), diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 9f7de2ae..41e87d19 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -2345,7 +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",0); + settings.pokeyCore=e->getConfInt("pokeyCore",1); settings.pcSpeakerOutMethod=e->getConfInt("pcSpeakerOutMethod",0); settings.yrw801Path=e->getConfString("yrw801Path",""); settings.tg100Path=e->getConfString("tg100Path",""); @@ -2616,7 +2616,7 @@ void FurnaceGUI::commitSettings() { settings.nesCore!=e->getConfInt("nesCore",0) || settings.fdsCore!=e->getConfInt("fdsCore",0) || settings.c64Core!=e->getConfInt("c64Core",1) || - settings.pokeyCore!=e->getConfInt("pokeyCore",0) + settings.pokeyCore!=e->getConfInt("pokeyCore",1) ); e->setConf("mainFontSize",settings.mainFontSize); From 00a0b84aacaae6fd9bc4bb907ef9afd09f1b6654 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 22 Dec 2022 20:17:02 -0500 Subject: [PATCH 75/93] try to fix log issue #797 issue #798 --- src/log.cpp | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/src/log.cpp b/src/log.cpp index 889e1cb5..6cc618e4 100644 --- a/src/log.cpp +++ b/src/log.cpp @@ -29,8 +29,8 @@ int logLevel=LOGLEVEL_INFO; FILE* logFile; char* logFileBuf; -unsigned int logFilePosI=0; -unsigned int logFilePosO=0; +std::atomic logFilePosI; +std::atomic logFilePosO; std::thread* logFileThread; std::mutex logFileLock; std::mutex logFileLockI; @@ -52,11 +52,22 @@ const char* logTypes[5]={ "trace" }; -void appendLogBuf(const char* msg, size_t len) { +void appendLogBuf(const LogEntry& entry) { logFileLockI.lock(); - int remaining=logFilePosO-logFilePosI; - if (remaining<=0) remaining+=TA_LOGFILE_BUF_SIZE; + 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)&TA_LOGFILE_BUF_SIZE; if (len>=(unsigned int)remaining) { printf("line too long to fit in log buffer!\n"); @@ -101,15 +112,7 @@ int writeLog(int level, const char* msg, fmt::printf_args args) { // write to log file if (logFileAvail) { - std::string toWrite=fmt::sprintf( - "%02d:%02d:%02d [%s] %s\n", - logEntries[pos].time.tm_hour, - logEntries[pos].time.tm_min, - logEntries[pos].time.tm_sec, - logTypes[logEntries[pos].loglevel], - logEntries[pos].text - ); - appendLogBuf(toWrite.c_str(),toWrite.size()); + appendLogBuf(logEntries[pos]); logFileNotify.notify_one(); } @@ -151,7 +154,7 @@ void _logFileThread() { logFilePosO=0; } else { fwrite(logFileBuf+logFilePosO,1,logFilePosICopy-logFilePosO,logFile); - logFilePosO=logFilePosICopy; + logFilePosO=logFilePosICopy&TA_LOGFILE_BUF_MASK; } } else { // wait @@ -165,7 +168,6 @@ void _logFileThread() { bool startLogFile(const char* path) { logFileAvail=false; return false; - /* if (logFileAvail) return true; // rotate log file if possible @@ -184,7 +186,6 @@ bool startLogFile(const char* path) { logFileThread=new std::thread(_logFileThread); return true; - */ } bool finishLogFile() { From 14e70e268450e93c2d1b4fd939466173b384ed9a Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 22 Dec 2022 20:20:14 -0500 Subject: [PATCH 76/93] disable MinGW builds temporarily --- .github/workflows/build.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 420b08f8..d4adcb99 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -20,8 +20,6 @@ jobs: config: - { name: 'Windows MSVC x86', os: windows-latest, compiler: msvc, arch: x86 } - { name: 'Windows MSVC x86_64', os: windows-latest, compiler: msvc, arch: x86_64 } - - { name: 'Windows MinGW x86', os: ubuntu-20.04, compiler: mingw, arch: x86 } - - { name: 'Windows MinGW x86_64', os: ubuntu-20.04, compiler: mingw, arch: x86_64 } - { name: 'macOS x86_64', os: macos-latest, arch: x86_64 } - { name: 'macOS ARM', os: macos-latest, arch: arm64 } - { name: 'Linux x86_64', os: ubuntu-18.04, arch: x86_64 } From 45f9aa7261f812edfca8d57b2ca0ea5f4c0aba32 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 22 Dec 2022 23:41:49 -0500 Subject: [PATCH 77/93] crash test 2 --- src/log.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/log.cpp b/src/log.cpp index 6cc618e4..7a86644b 100644 --- a/src/log.cpp +++ b/src/log.cpp @@ -29,8 +29,8 @@ int logLevel=LOGLEVEL_INFO; FILE* logFile; char* logFileBuf; -std::atomic logFilePosI; -std::atomic logFilePosO; +unsigned int logFilePosI; +unsigned int logFilePosO; std::thread* logFileThread; std::mutex logFileLock; std::mutex logFileLockI; @@ -67,7 +67,9 @@ void appendLogBuf(const LogEntry& entry) { const char* msg=toWrite.c_str(); size_t len=toWrite.size(); - int remaining=(logFilePosO-logFilePosI)&TA_LOGFILE_BUF_SIZE; + printf("appendLogBuf %d %d\n",logFilePosI,(int)len); + + int remaining=(logFilePosO-logFilePosI-1)&TA_LOGFILE_BUF_SIZE; if (len>=(unsigned int)remaining) { printf("line too long to fit in log buffer!\n"); @@ -92,6 +94,8 @@ int writeLog(int level, const char* msg, fmt::printf_args args) { int pos=logPosition; logPosition=(logPosition+1)&TA_LOG_MASK; + printf("logPosition: %d\n",pos); + logEntries[pos].text=fmt::vsprintf(msg,args); // why do I have to pass a pointer // can't I just pass the time_t directly?! @@ -166,8 +170,6 @@ void _logFileThread() { } bool startLogFile(const char* path) { - logFileAvail=false; - return false; if (logFileAvail) return true; // rotate log file if possible From f1c40082d6375b2da06f1dee2373981a5024a2f3 Mon Sep 17 00:00:00 2001 From: Waldemar Pawlaszek Date: Fri, 23 Dec 2022 06:27:37 +0100 Subject: [PATCH 78/93] Fixing osciloscope volume --- src/engine/platform/sound/pokey/AltASAP.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/engine/platform/sound/pokey/AltASAP.cpp b/src/engine/platform/sound/pokey/AltASAP.cpp index 65ad339a..69fe652e 100644 --- a/src/engine/platform/sound/pokey/AltASAP.cpp +++ b/src/engine/platform/sound/pokey/AltASAP.cpp @@ -494,10 +494,12 @@ public: 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(); + //just some magick value to match the audio level of mzpokeysnd + static constexpr int16_t MAGICK_VOLUME_BOOSTER = 160; + int16_t ch0 = mAudioChannels[0].getOutput() * MAGICK_VOLUME_BOOSTER; + int16_t ch1 = mAudioChannels[1].getOutput() * MAGICK_VOLUME_BOOSTER; + int16_t ch2 = mAudioChannels[2].getOutput() * MAGICK_VOLUME_BOOSTER; + int16_t ch3 = mAudioChannels[3].getOutput() * MAGICK_VOLUME_BOOSTER; if ( oscb != nullptr ) { @@ -626,7 +628,7 @@ void Pokey::write( uint8_t address, uint8_t value ) int16_t Pokey::sampleAudio( DivDispatchOscBuffer** oscb ) { - return mPokey->sampleAudio( oscb ) * 160; //just some magick value to match the audio level of mzpokeysnd + return mPokey->sampleAudio( oscb ); } uint8_t const* Pokey::getRegisterPool() From 2423ec9bc6c5d560135257bb642de446520af5f4 Mon Sep 17 00:00:00 2001 From: Waldemar Pawlaszek Date: Fri, 23 Dec 2022 07:06:17 +0100 Subject: [PATCH 79/93] Refactoring --- src/engine/platform/pokey.cpp | 24 ++++--------- src/engine/platform/pokey.h | 2 +- src/engine/platform/sound/pokey/AltASAP.cpp | 39 ++++++++++++--------- src/engine/platform/sound/pokey/AltASAP.hpp | 6 ++-- 4 files changed, 34 insertions(+), 37 deletions(-) diff --git a/src/engine/platform/pokey.cpp b/src/engine/platform/pokey.cpp index 7427733c..1a3e127b 100644 --- a/src/engine/platform/pokey.cpp +++ b/src/engine/platform/pokey.cpp @@ -96,16 +96,16 @@ void DivPlatformPOKEY::acquireMZ(short* buf, size_t start, size_t len) { void DivPlatformPOKEY::acquireASAP(short* buf, size_t start, size_t len) { while (!writes.empty()) { QueuedWrite w=writes.front(); - altASAP->write(w.addr, w.val); + altASAP.write(w.addr, w.val); writes.pop(); } for (size_t h=start; h=2) { oscBufDelay=0; - buf[h]=altASAP->sampleAudio(oscBuf); + buf[h]=altASAP.sampleAudio(oscBuf); } else { - buf[h]=altASAP->sampleAudio(); + buf[h]=altASAP.sampleAudio(); } } } @@ -396,7 +396,7 @@ DivDispatchOscBuffer* DivPlatformPOKEY::getOscBuffer(int ch) { unsigned char* DivPlatformPOKEY::getRegisterPool() { if (useAltASAP) { - return const_cast(altASAP->getRegisterPool()); + return const_cast(altASAP.getRegisterPool()); } else { return regPool; } @@ -418,7 +418,7 @@ void DivPlatformPOKEY::reset() { } if (useAltASAP) { - altASAP->reset(); + altASAP.reset(); } else { ResetPokeyState(&pokey); } @@ -454,11 +454,9 @@ void DivPlatformPOKEY::setFlags(const DivConfig& flags) { for (int i=0; i<4; i++) { oscBuf[i]->rate=rate/2; } - if (altASAP) { - delete altASAP; - } - altASAP=new AltASAP::Pokey(chipClock,rate); + altASAP.init(chipClock,rate); } else { + MZPOKEYSND_Init(&pokey); rate=chipClock; for (int i=0; i<4; i++) { oscBuf[i]->rate=rate/14; @@ -479,16 +477,11 @@ int DivPlatformPOKEY::init(DivEngine* p, int channels, int sugRate, const DivCon dumpWrites=false; skipRegisterWrites=false; oscBufDelay=0; - altASAP=NULL; for (int i=0; i<4; i++) { isMuted[i]=false; oscBuf[i]=new DivDispatchOscBuffer; } - if (!useAltASAP) { - MZPOKEYSND_Init(&pokey); - } - setFlags(flags); reset(); return 6; @@ -498,9 +491,6 @@ void DivPlatformPOKEY::quit() { for (int i=0; i<4; i++) { delete oscBuf[i]; } - if (altASAP) { - delete altASAP; - } } void DivPlatformPOKEY::setAltASAP(bool value) { diff --git a/src/engine/platform/pokey.h b/src/engine/platform/pokey.h index f242ae7b..e68178a2 100644 --- a/src/engine/platform/pokey.h +++ b/src/engine/platform/pokey.h @@ -52,7 +52,7 @@ class DivPlatformPOKEY: public DivDispatch { bool audctlChanged; unsigned char oscBufDelay; PokeyState pokey; - AltASAP::Pokey* altASAP; + AltASAP::Pokey altASAP; bool useAltASAP; unsigned char regPool[16]; friend void putDispatchChip(void*,int); diff --git a/src/engine/platform/sound/pokey/AltASAP.cpp b/src/engine/platform/sound/pokey/AltASAP.cpp index 69fe652e..2acbd52f 100644 --- a/src/engine/platform/sound/pokey/AltASAP.cpp +++ b/src/engine/platform/sound/pokey/AltASAP.cpp @@ -283,9 +283,8 @@ 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{}, mPokeyClock{ pokeyClock * 8 }, mTick{}, mNextTick{}, mSampleRate{ sampleRate }, mSamplesRemainder{}, - mReloadCycles1{ 28 }, mReloadCycles3{ 28 }, mDivCycles{ 28 }, - mTicksPerSample{ mPokeyClock / mSampleRate, mPokeyClock % mSampleRate } + 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(); @@ -468,7 +467,7 @@ public: for ( int i = 0; i < 4; i++ ) mAudioChannels[i].doStimer( cycle ); break; - case 0x0f: + case 0x0f: { if ( value == mSkctl ) break; @@ -600,45 +599,53 @@ private: std::array mRegisterPool; - uint32_t mPokeyClock; uint64_t mTick; uint64_t mNextTick; + int64_t mReloadCycles1; + int64_t mReloadCycles3; + int64_t mDivCycles; uint32_t mSampleRate; uint32_t mSamplesRemainder; - int mReloadCycles1; - int mReloadCycles3; - int mDivCycles; std::pair mTicksPerSample; - }; - -Pokey::Pokey( uint32_t pokeyClock, uint32_t sampleRate ) : mPokey{ std::make_unique( mPokeyClock, mSampleRate ) }, mPokeyClock{ pokeyClock }, mSampleRate{ sampleRate } +//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(); } -void Pokey::reset() -{ - mPokey = std::make_unique( mPokeyClock, mSampleRate ); -} - } diff --git a/src/engine/platform/sound/pokey/AltASAP.hpp b/src/engine/platform/sound/pokey/AltASAP.hpp index 930bf6aa..c159e9d5 100644 --- a/src/engine/platform/sound/pokey/AltASAP.hpp +++ b/src/engine/platform/sound/pokey/AltASAP.hpp @@ -15,7 +15,8 @@ class Pokey { public: - Pokey( uint32_t pokeyClock, uint32_t sampleRate ); + Pokey(); + void init( uint32_t pokeyClock, uint32_t sampleRate ); ~Pokey(); void write( uint8_t address, uint8_t value ); @@ -26,10 +27,9 @@ public: void reset(); private: - - std::unique_ptr mPokey; uint32_t mPokeyClock; uint32_t mSampleRate; + std::unique_ptr mPokey; }; } From e960b6be30b07729c09e9b7f7503c358ccd50a02 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 23 Dec 2022 01:15:46 -0500 Subject: [PATCH 80/93] use assign --- src/log.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/log.cpp b/src/log.cpp index 7a86644b..46cd18cd 100644 --- a/src/log.cpp +++ b/src/log.cpp @@ -96,7 +96,7 @@ int writeLog(int level, const char* msg, fmt::printf_args args) { printf("logPosition: %d\n",pos); - 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 From e5e3a833025e9500f1609f74e26312b186e84f58 Mon Sep 17 00:00:00 2001 From: Waldemar Pawlaszek Date: Fri, 23 Dec 2022 07:31:22 +0100 Subject: [PATCH 81/93] More refactoring --- src/engine/platform/sound/pokey/AltASAP.cpp | 30 +++++++++++---------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/src/engine/platform/sound/pokey/AltASAP.cpp b/src/engine/platform/sound/pokey/AltASAP.cpp index 2acbd52f..21bc31a2 100644 --- a/src/engine/platform/sound/pokey/AltASAP.cpp +++ b/src/engine/platform/sound/pokey/AltASAP.cpp @@ -37,6 +37,9 @@ 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 { @@ -219,15 +222,15 @@ public: int32_t volume = value & 0x0f; if ( ( value & 0x10 ) != 0 ) { - mDelta = volume; + mDelta = volume * MAGICK_VOLUME_BOOSTER; } else { muteUltrasound( cycle ); if ( mDelta > 0 ) - mDelta = volume; + mDelta = volume * MAGICK_VOLUME_BOOSTER; else - mDelta = -volume; + mDelta = -volume * MAGICK_VOLUME_BOOSTER; } } @@ -283,7 +286,8 @@ 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{}, + 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 ); @@ -493,19 +497,17 @@ public: int64_t value = mQueue->pop(); if ( ( value & 7 ) == 6 ) // 6 == 4 ^ 2 { - //just some magick value to match the audio level of mzpokeysnd - static constexpr int16_t MAGICK_VOLUME_BOOSTER = 160; - int16_t ch0 = mAudioChannels[0].getOutput() * MAGICK_VOLUME_BOOSTER; - int16_t ch1 = mAudioChannels[1].getOutput() * MAGICK_VOLUME_BOOSTER; - int16_t ch2 = mAudioChannels[2].getOutput() * MAGICK_VOLUME_BOOSTER; - int16_t ch3 = mAudioChannels[3].getOutput() * MAGICK_VOLUME_BOOSTER; + 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; - oscb[1]->data[oscb[1]->needle++]=ch1; - oscb[2]->data[oscb[2]->needle++]=ch2; - oscb[3]->data[oscb[3]->needle++]=ch3; + 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(); From 35ac14d35b72c163c9507837fbb0b12d152c6cee Mon Sep 17 00:00:00 2001 From: Waldemar Pawlaszek Date: Fri, 23 Dec 2022 07:43:51 +0100 Subject: [PATCH 82/93] Applying notes. --- src/engine/platform/pokey.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/engine/platform/pokey.cpp b/src/engine/platform/pokey.cpp index 1a3e127b..a6ddb78f 100644 --- a/src/engine/platform/pokey.cpp +++ b/src/engine/platform/pokey.cpp @@ -456,7 +456,6 @@ void DivPlatformPOKEY::setFlags(const DivConfig& flags) { } altASAP.init(chipClock,rate); } else { - MZPOKEYSND_Init(&pokey); rate=chipClock; for (int i=0; i<4; i++) { oscBuf[i]->rate=rate/14; @@ -482,6 +481,10 @@ int DivPlatformPOKEY::init(DivEngine* p, int channels, int sugRate, const DivCon oscBuf[i]=new DivDispatchOscBuffer; } + if (!useAltASAP) { + MZPOKEYSND_Init(&pokey); + } + setFlags(flags); reset(); return 6; From 552040facf0a5bf476e052a81406ddd9c2e08bc3 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 23 Dec 2022 05:14:42 -0500 Subject: [PATCH 83/93] fetch_add --- src/log.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/log.cpp b/src/log.cpp index 46cd18cd..ca4ca58b 100644 --- a/src/log.cpp +++ b/src/log.cpp @@ -91,8 +91,7 @@ void appendLogBuf(const LogEntry& entry) { 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; printf("logPosition: %d\n",pos); From da9e64881dff65fcd8ee4aa58cbb4872d1b9cd3b Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 23 Dec 2022 13:55:15 -0500 Subject: [PATCH 84/93] port ExtCh op macro code to OPN family, part 4 --- src/engine/platform/ym2203ext.cpp | 120 +++++++++++++++++++++++++++--- 1 file changed, 110 insertions(+), 10 deletions(-) diff --git a/src/engine/platform/ym2203ext.cpp b/src/engine/platform/ym2203ext.cpp index 72c4e749..b027ee12 100644 --- a/src/engine/platform/ym2203ext.cpp +++ b/src/engine/platform/ym2203ext.cpp @@ -393,6 +393,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; @@ -444,15 +530,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); } } @@ -461,13 +549,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)); @@ -523,7 +621,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 From e161eb2e209e57c938ec5d2be5d2299cf198fa58 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 23 Dec 2022 14:31:00 -0500 Subject: [PATCH 85/93] port ExtCh op macro code to OPN family, part 5 --- src/engine/platform/ym2608ext.cpp | 110 ++++++++++++++++++++++++++-- src/engine/platform/ym2610bext.cpp | 110 ++++++++++++++++++++++++++-- src/engine/platform/ym2610ext.cpp | 112 ++++++++++++++++++++++++++--- 3 files changed, 310 insertions(+), 22 deletions(-) diff --git a/src/engine/platform/ym2608ext.cpp b/src/engine/platform/ym2608ext.cpp index d95582d1..6c8ccd36 100644 --- a/src/engine/platform/ym2608ext.cpp +++ b/src/engine/platform/ym2608ext.cpp @@ -411,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; @@ -462,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() { @@ -479,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); } @@ -506,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; @@ -527,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) { @@ -560,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/ym2610bext.cpp b/src/engine/platform/ym2610bext.cpp index 5cfffcf2..5c7eab18 100644 --- a/src/engine/platform/ym2610bext.cpp +++ b/src/engine/platform/ym2610bext.cpp @@ -407,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; @@ -458,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() { @@ -475,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); } @@ -502,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; @@ -518,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) { @@ -551,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 52705500..2460ae66 100644 --- a/src/engine/platform/ym2610ext.cpp +++ b/src/engine/platform/ym2610ext.cpp @@ -407,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; @@ -458,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() { @@ -475,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); } @@ -502,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; @@ -518,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) { @@ -551,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 From 3f1e9644ddf7bc6c4775b4a28a8c45fb89e6f557 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 23 Dec 2022 14:34:43 -0500 Subject: [PATCH 86/93] Revert "disable MinGW builds temporarily" This reverts commit 14e70e268450e93c2d1b4fd939466173b384ed9a. --- .github/workflows/build.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d4adcb99..420b08f8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -20,6 +20,8 @@ jobs: config: - { name: 'Windows MSVC x86', os: windows-latest, compiler: msvc, arch: x86 } - { name: 'Windows MSVC x86_64', os: windows-latest, compiler: msvc, arch: x86_64 } + - { name: 'Windows MinGW x86', os: ubuntu-20.04, compiler: mingw, arch: x86 } + - { name: 'Windows MinGW x86_64', os: ubuntu-20.04, compiler: mingw, arch: x86_64 } - { name: 'macOS x86_64', os: macos-latest, arch: x86_64 } - { name: 'macOS ARM', os: macos-latest, arch: arm64 } - { name: 'Linux x86_64', os: ubuntu-18.04, arch: x86_64 } From 07697bc888e8dee592cb7c9b60f71a9920fc3f0b Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 23 Dec 2022 14:35:56 -0500 Subject: [PATCH 87/93] go back to release mode --- .github/workflows/build.yml | 2 +- TODO.md | 1 - src/log.cpp | 4 ---- 3 files changed, 1 insertion(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 420b08f8..bcd989c2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,7 +11,7 @@ defaults: shell: bash env: - BUILD_TYPE: Debug + BUILD_TYPE: Release jobs: build: diff --git a/TODO.md b/TODO.md index c03365e4..2251b10d 100644 --- a/TODO.md +++ b/TODO.md @@ -1,5 +1,4 @@ # to-do for 0.6pre2 - YM2612 CSM (no DualPCM) -- port op macro code to all other OPN chips - bug fixes diff --git a/src/log.cpp b/src/log.cpp index ca4ca58b..1611e688 100644 --- a/src/log.cpp +++ b/src/log.cpp @@ -67,8 +67,6 @@ void appendLogBuf(const LogEntry& entry) { const char* msg=toWrite.c_str(); size_t len=toWrite.size(); - printf("appendLogBuf %d %d\n",logFilePosI,(int)len); - int remaining=(logFilePosO-logFilePosI-1)&TA_LOGFILE_BUF_SIZE; if (len>=(unsigned int)remaining) { @@ -93,8 +91,6 @@ int writeLog(int level, const char* msg, fmt::printf_args args) { time_t thisMakesNoSense=time(NULL); int pos=(logPosition.fetch_add(1))&TA_LOG_MASK; - printf("logPosition: %d\n",pos); - 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?! From f19472f84a909a0f645f39f3bced2792403c2f95 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 23 Dec 2022 15:06:44 -0500 Subject: [PATCH 88/93] Revert "update readme" This reverts commit 62b40464f179a35d13e0386e08b743e83cde862c. --- README.md | 296 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 293 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 509c7b44..e5573b4b 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,297 @@ # Furnace (chiptune tracker) -# HELP +![screenshot](papers/screenshot2.png) -Furnace has a serious crash bug! download the latest artifact (either from the Actions tab or [nightly.link](https://nightly.link/tildearrow/furnace/workflows/build/master)) or compile it and help me diagnose and track the origin! +the biggest multi-system chiptune tracker ever made! -see the [bug report](https://github.com/tildearrow/furnace/issues/793) for more info. +[downloads](#downloads) | [discussion/help](#quick-references) | [developer info](#developer-info) | [unofficial packages](#unofficial-packages) | [FAQ](#frequently-asked-questions) + +--- +## downloads + +check out the [Releases](https://github.com/tildearrow/furnace/releases) page. available for Windows, macOS and Linux (AppImage). + +[see here](https://nightly.link/tildearrow/furnace/workflows/build/master) for unstable developer builds. + +## features + +- over 50 sound chips - and counting: + - Yamaha FM chips: + - YM2151 (OPM) + - YM2203 (OPN) + - YM2413 (OPLL) + - YM2414 (OPZ) used in Yamaha TX81Z + - YM2608 (OPNA) used in PC-98 + - YM2610 (OPNB) used in Neo Geo + - YM2610B (OPNB2) + - YM2612 (OPN2) used in Sega Genesis and FM Towns + - YM3526 (OPL) used in C64 Sound Expander + - YM3812 (OPL2) + - YMF262 (OPL3) with full 4-op support! + - Y8950 (OPL with ADPCM) + - square wave chips: + - AY-3-8910/YM2149(F) used in several computers and game consoles + - Commodore VIC used in the VIC-20 + - Microchip AY8930 + - TI SN76489 used in Sega Master System and BBC Micro + - PC Speaker + - Philips SAA1099 used in SAM Coupé + - sample chips: + - Amiga + - SegaPCM - all 16 channels + - Capcom QSound + - Yamaha YMZ280B (PCMD8) + - Ricoh RF5C68 used in Sega CD and FM Towns + - OKI MSM6258 and MSM6295 + - wavetable chips: + - HuC6280 used in PC Engine + - Konami Bubble System WSG + - Konami SCC/SCC+ + - Namco arcade chips (WSG/C15/C30) + - WonderSwan + - Seta/Allumer X1-010 + - NES (Ricoh 2A03/2A07), with additional expansion sound support: + - Konami VRC6 + - Konami VRC7 + - MMC5 + - Famicom Disk System + - Sunsoft 5B + - Namco 163 + - Family Noraebang (OPLL) + - SID (6581/8580) used in Commodore 64 + - Mikey used in Atari Lynx + - ZX Spectrum beeper (SFX-like engine) + - Commodore PET + - TIA used in Atari 2600 + - Game Boy + - modern/fantasy: + - Commander X16 VERA + - tildearrow Sound Unit +- mix and match sound chips! + - over 200 ready to use presets from computers, game consoles and arcade boards... + - ...or create your own - up to 32 of them or a total of 128 channels! +- DefleMask compatibility + - loads .dmf modules from all versions (beta 1 to 1.1.3) + - saves .dmf modules - both modern and legacy + - Furnace doubles as a module downgrader + - loads/saves .dmp instruments and .dmw wavetables as well + - clean-room design (guesswork and ABX tests only, no decompilation involved) + - bug/quirk implementation for increased playback accuracy through compatibility flags +- VGM export +- modular layout that you may adapt to your needs +- audio file export - entire song, per chip or per channel +- quality emulation cores (Nuked, MAME, SameBoy, Mednafen PCE, NSFplay, puNES, reSID, Stella, SAASound, vgsound_emu and ymfm) +- wavetable synthesizer + - available on wavetable chips + - create complex sounds with ease - provide up to two wavetables, select and effect and let go! +- MIDI input support +- additional features: + - FM macros! + - negative octaves + - arbitrary pitch samples + - sample loop points + - SSG envelopes and ADPCM-B in Neo Geo + - full duty/cutoff range in C64 + - ability to change tempo mid-song + - multiple sub-songs in a module + - per-channel oscilloscope with waveform centering + - built-in sample editor + - chip mixing settings + - built-in visualizer in pattern view +- open-source under GPLv2 or later. + +--- +# quick references + + - **discussion**: see the [Discussions](https://github.com/tildearrow/furnace/discussions) section, or (preferably) the [official Discord server](https://discord.gg/EfrwT2wq7z). + - **help**: check out the [documentation](papers/doc/README.md). it's mostly incomplete, but has details on effects. + +## unofficial packages + +[![Packaging status](https://repology.org/badge/tiny-repos/furnace.svg)](https://repology.org/project/furnace/versions) + +some people have provided packages for Unix/Unix-like distributions. here's a list. + - **Arch Linux**: [furnace-git is in the AUR.](https://aur.archlinux.org/packages/furnace-git) thank you Essem! + - **FreeBSD**: [a package in ports](https://www.freshports.org/audio/furnace/) is available courtesy of ehaupt. + - **Nix**: [package](https://search.nixos.org/packages?channel=unstable&show=furnace&from=0&size=50&sort=relevance&type=packages&query=furnace) thanks to OPNA2608. + - **openSUSE**: [a package](https://software.opensuse.org/package/furnace) is available, courtesy of fpesari. + +--- +# developer info + +[![Build furnace](https://github.com/tildearrow/furnace/actions/workflows/build.yml/badge.svg)](https://github.com/tildearrow/furnace/actions/workflows/build.yml) + +if you can't download these artifacts (because GitHub requires you to be logged in), [go here](https://nightly.link/tildearrow/furnace/workflows/build/master) instead. + +**NOTE: do not download the project's source as a .zip or .tar.gz as these do not include the project's submodules which are necessary to proceed with building. please instead use Git as shown below.** + +## dependencies + +- CMake +- JACK (optional, macOS/Linux only) + +if building under Windows or macOS, no additional dependencies are required. +otherwise, you may also need the following: + +- libpulse +- libx11 +- libasound +- libGL + +some Linux distributions (e.g. Ubuntu or openSUSE) will require you to install the `-dev` versions of these. + +## getting the source + +type the following on a terminal/console: (make sure Git is installed) + +``` +git clone --recursive https://github.com/tildearrow/furnace.git +cd furnace +``` + +(the `--recursive` parameter ensures submodules are fetched as well) + +## compilation + +your typical CMake project. + +### Windows using MSVC + +as of now tildearrow uses MinGW for Windows builds, but thanks to OPNA2608 this works again! + +from the developer tools command prompt: + +``` +mkdir build +cd build +cmake .. +msbuild ALL_BUILD.vcxproj +``` + +### macOS and Linux + +``` +mkdir build +cd build +cmake .. +make +``` +Alternatively, build scripts are provided in the `scripts/` folder in the root of the repository. + +### CMake options + +To add an option from the command-line: `-D=` +Example: `cmake -DBUILD_GUI=OFF -DWARNINGS_ARE_ERRORS=ON ..` + +Available options: + +| Name | Default | Description | +| :--: | :-----: | ----------- | +| `BUILD_GUI` | `ON` | Build the tracker (disable to build only a headless player) | +| `USE_RTMIDI` | `ON` | Build with MIDI support using RtMidi | +| `USE_SDL2` | `ON` | Build with SDL2 (required to build with GUI) | +| `USE_SNDFILE` | `ON` | Build with libsndfile (required in order to work with audio files) | +| `USE_BACKWARD` | `ON` | Use backward-cpp to print a backtrace on crash/abort | +| `WITH_JACK` | `ON` if system-installed JACK detected, otherwise `OFF` | Whether to build with JACK support. Auto-detects if JACK is available | +| `SYSTEM_FFTW` | `OFF` | Use a system-installed version of FFTW instead of the vendored one | +| `SYSTEM_FMT` | `OFF` | Use a system-installed version of fmt instead of the vendored one | +| `SYSTEM_LIBSNDFILE` | `OFF` | Use a system-installed version of libsndfile instead of the vendored one | +| `SYSTEM_RTMIDI` | `OFF` | Use a system-installed version of RtMidi instead of the vendored one | +| `SYSTEM_ZLIB` | `OFF` | Use a system-installed version of zlib instead of the vendored one | +| `SYSTEM_SDL2` | `OFF` | Use a system-installed version of SDL2 instead of the vendored one | +| `WARNINGS_ARE_ERRORS` | `OFF` (but consider enabling this & reporting any errors that arise from it!) | Whether warnings in furnace's C++ code should be treated as errors | +| `WITH_DEMOS` | `ON` | Install demo songs on `make install` | +| `WITH_INSTRUMENTS` | `ON` | Install demo instruments on `make install` | + +## console usage + +``` +./furnace +``` + +this opens the program. + +``` +./furnace -console +``` + +this will play a compatible file. + +``` +./furnace -console -view commands +``` + +this will play a compatible file and enable the commands view. + +**note that these commands only actually work in Linux environments. on other command lines, such as Windows' Command Prompt, or MacOS Terminal, it may not work correctly.** + +--- +# frequently asked questions + +> woah! 50 sound chips?! I can't believe it! + +yup, it's real. + +> where's the manual? + +see [papers/](papers/doc/README.md). it's kind of incomplete, but at least the sound chips section is there. + +> it doesn't open under macOS! + +this is due to Apple's application signing policy. a workaround is to right click on the Furnace app icon and select Open. + +**as of Monterey, this workaround no longer works (especially on ARM).** yeah, Apple has decided to be strict on the matter. +if you happen to be on that version, use this workaround instead (on a Terminal): + +``` +xattr -d com.apple.quarantine /path/to/Furnace.app +``` + +(replace /path/to/ with the path where Furnace.app is located) + +you may need to log out and/or reboot after doing this. + +> how do I use C64 absolute filter/duty? + +on Instrument Editor in the C64 tab there are two options to toggle these. +also provided are two effects: + +- `3xxx`: set fine duty. +- `4xxx`: set fine cutoff. `xxx` range is 000-7ff. +additionally, you can change the cutoff and/or duty as a macro inside an instrument by clicking the `absolute cutoff macro` and/or `absolute duty macro` checkbox at the bottom of the instrument. (for the filter, you also need to click the checkbox that says `volume macro is cutoff macro`.) + +> how do I use PCM on a PCM-capable chip? + +two possibilities: +- the recommended way is by creating the "Sample" type instrument and assigning a sample to it. +- otherwise you may employ the DefleMask-compatible method, using `17xx` effect. + +> my .dmf song sounds very odd at a certain point + +file a bug report. use the Issues page. it's probably another playback inaccuracy. + +> my .dmf song sounds correct, but it doesn't in DefleMask + +file a bug report **here**. it still is a playback inaccuracy. + +> my song sounds terrible after saving as .dmf! + +the DefleMask format has several limitations. save in Furnace song format instead (.fur). + +> how do I solo channels? + +right click on the channel name. + +--- +# footnotes + +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. + + +despite the fact this program works with the .dmf file format, it is NOT affiliated with Delek or DefleMask in any way, nor it is a replacement for the original program. From 75416fc63e9a80405abe30d3fa333c89ae7aaa34 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 23 Dec 2022 19:00:03 -0500 Subject: [PATCH 89/93] port ExtCh op macro code to OPN family, part 6 --- src/engine/platform/ym2203ext.cpp | 5 ----- src/engine/platform/ym2608.cpp | 8 ++++++-- src/engine/platform/ym2610.cpp | 8 ++++++-- src/engine/platform/ym2610b.cpp | 8 ++++++-- 4 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/engine/platform/ym2203ext.cpp b/src/engine/platform/ym2203ext.cpp index b027ee12..dd3e8595 100644 --- a/src/engine/platform/ym2203ext.cpp +++ b/src/engine/platform/ym2203ext.cpp @@ -173,11 +173,6 @@ int DivPlatformYM2203Ext::dispatch(DivCommand c) { immWrite(0x27,extMode?0x40:0); break; } - case DIV_CMD_FM_LFO: { // ??? - lfoValue=(c.value&7)|((c.value>>4)<<3); - rWrite(0x22,lfoValue); - 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)); 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/ym2610.cpp b/src/engine/platform/ym2610.cpp index 842d728c..5aa13499 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 4bec0544..00adbdd1 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 From dff3ddeb3ae0583c34e603dbf50ee16cf3e55248 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 23 Dec 2022 19:06:10 -0500 Subject: [PATCH 90/93] port ExtCh op macro code to OPN family, part 7 --- src/engine/platform/genesis.cpp | 2 +- src/engine/platform/genesisext.cpp | 14 +++++++------- src/engine/platform/ym2203ext.cpp | 2 +- src/engine/platform/ym2608ext.cpp | 2 +- src/engine/platform/ym2610bext.cpp | 2 +- src/engine/platform/ym2610ext.cpp | 2 +- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/engine/platform/genesis.cpp b/src/engine/platform/genesis.cpp index c375e51f..c6a5abc7 100644 --- a/src/engine/platform/genesis.cpp +++ b/src/engine/platform/genesis.cpp @@ -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==csmChan && extMode && softPCM) { // CSM + if (c.chan==csmChan && extMode) { // CSM chan[c.chan].macroInit(ins); chan[c.chan].insChanged=false; diff --git a/src/engine/platform/genesisext.cpp b/src/engine/platform/genesisext.cpp index c2040eff..20268de9 100644 --- a/src/engine/platform/genesisext.cpp +++ b/src/engine/platform/genesisext.cpp @@ -587,7 +587,7 @@ void DivPlatformGenesisExt::tick(bool sysTick) { } } - if (extMode && softPCM) { + 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; @@ -620,7 +620,7 @@ void DivPlatformGenesisExt::tick(bool sysTick) { immWrite(0x28,writeMask); } - if (extMode && softPCM) { + if (extMode) { if (chan[csmChan].keyOn) { immWrite(0x27,0x81); chan[csmChan].keyOn=false; @@ -685,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; } } @@ -700,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; } diff --git a/src/engine/platform/ym2203ext.cpp b/src/engine/platform/ym2203ext.cpp index dd3e8595..12605a93 100644 --- a/src/engine/platform/ym2203ext.cpp +++ b/src/engine/platform/ym2203ext.cpp @@ -601,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; } diff --git a/src/engine/platform/ym2608ext.cpp b/src/engine/platform/ym2608ext.cpp index 6c8ccd36..47a01d13 100644 --- a/src/engine/platform/ym2608ext.cpp +++ b/src/engine/platform/ym2608ext.cpp @@ -639,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; } diff --git a/src/engine/platform/ym2610bext.cpp b/src/engine/platform/ym2610bext.cpp index 5c7eab18..207708bd 100644 --- a/src/engine/platform/ym2610bext.cpp +++ b/src/engine/platform/ym2610bext.cpp @@ -630,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; } diff --git a/src/engine/platform/ym2610ext.cpp b/src/engine/platform/ym2610ext.cpp index 2460ae66..b4da0878 100644 --- a/src/engine/platform/ym2610ext.cpp +++ b/src/engine/platform/ym2610ext.cpp @@ -630,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; } From 37dbc52a68b234ddd4d594961f48e56e9a90523f Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 23 Dec 2022 19:26:42 -0500 Subject: [PATCH 91/93] YM2612: CSM without DualPCM --- src/engine/dispatchContainer.cpp | 6 ++++++ src/engine/platform/genesis.cpp | 4 ++-- src/engine/platform/genesisext.cpp | 4 ++++ src/engine/platform/genesisext.h | 1 + src/gui/guiConst.cpp | 2 ++ 5 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index 668864ae..0017ea23 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -201,6 +201,12 @@ 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_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)); diff --git a/src/engine/platform/genesis.cpp b/src/engine/platform/genesis.cpp index c6a5abc7..76294524 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) { @@ -1217,7 +1217,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/genesisext.cpp b/src/engine/platform/genesisext.cpp index 20268de9..ae6ac70b 100644 --- a/src/engine/platform/genesisext.cpp +++ b/src/engine/platform/genesisext.cpp @@ -746,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/gui/guiConst.cpp b/src/gui/guiConst.cpp index cb6b24dc..0c8a6d02 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -913,6 +913,7 @@ const FurnaceGUIColorDef guiColors[GUI_COLOR_MAX]={ const int availableSystems[]={ DIV_SYSTEM_YM2612, DIV_SYSTEM_YM2612_EXT, + DIV_SYSTEM_YM2612_CSM, DIV_SYSTEM_YM2612_DUALPCM, DIV_SYSTEM_YM2612_DUALPCM_EXT, DIV_SYSTEM_SMS, @@ -994,6 +995,7 @@ const int availableSystems[]={ const int chipsFM[]={ DIV_SYSTEM_YM2612, DIV_SYSTEM_YM2612_EXT, + DIV_SYSTEM_YM2612_CSM, DIV_SYSTEM_YM2612_DUALPCM, DIV_SYSTEM_YM2612_DUALPCM_EXT, DIV_SYSTEM_YM2151, From 81bda09625e842975a16603d314f484d4995e2b0 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 23 Dec 2022 19:27:30 -0500 Subject: [PATCH 92/93] update to-do list --- TODO.md | 1 - 1 file changed, 1 deletion(-) diff --git a/TODO.md b/TODO.md index 2251b10d..137e4f7b 100644 --- a/TODO.md +++ b/TODO.md @@ -1,4 +1,3 @@ # to-do for 0.6pre2 -- YM2612 CSM (no DualPCM) - bug fixes From 3685772728730388f8ae483daf55c1f74fb790df Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 23 Dec 2022 19:31:24 -0500 Subject: [PATCH 93/93] GUI: add some CSM presets --- src/gui/presets.cpp | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/gui/presets.cpp b/src/gui/presets.cpp index 061173d3..ee957691 100644 --- a/src/gui/presets.cpp +++ b/src/gui/presets.cpp @@ -55,6 +55,12 @@ 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_DUALPCM, 64, 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, "") @@ -1076,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, ""), @@ -1148,6 +1170,11 @@ 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_DUALPCM, 64, 0, "ladderEffect=true") @@ -1183,6 +1210,11 @@ 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_DUALPCM, 64, 0, "")