diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000..6b5659d3 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1 @@ + diff --git a/.gitmodules b/.gitmodules index 435f43c7..d63fd70b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -19,3 +19,6 @@ [submodule "extern/adpcm"] path = extern/adpcm url = https://github.com/superctr/adpcm +[submodule "extern/Nuked-OPL3"] + path = extern/Nuked-OPL3 + url = https://github.com/nukeykt/Nuked-OPL3.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 7211eb3d..adc5cbad 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -226,6 +226,7 @@ extern/adpcm/ymz_codec.c extern/Nuked-OPN2/ym3438.c extern/opm/opm.c extern/Nuked-OPLL/opll.c +extern/Nuked-OPL3/opl3.c src/engine/platform/sound/sn76496.cpp src/engine/platform/sound/ay8910.cpp src/engine/platform/sound/saa1099.cpp @@ -298,9 +299,11 @@ src/engine/platform/ym2610b.cpp src/engine/platform/ym2610bext.cpp src/engine/platform/ay.cpp src/engine/platform/ay8930.cpp +src/engine/platform/opl.cpp src/engine/platform/tia.cpp src/engine/platform/saa.cpp src/engine/platform/amiga.cpp +src/engine/platform/pcspkr.cpp src/engine/platform/segapcm.cpp src/engine/platform/qsound.cpp src/engine/platform/vera.cpp diff --git a/README.md b/README.md index bed970b1..e1d97726 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ this is a work-in-progress chiptune tracker compatible with DefleMask modules (. - accurate emulation cores whether possible (Nuked, MAME, SameBoy, Mednafen PCE, puNES, reSID, Stella, SAASound and ymfm) - additional features on top: - FM macros! + - negative octaves - arbitrary pitch samples - sample loop points - SSG envelopes in Neo Geo @@ -186,6 +187,10 @@ also provided are two effects: - `3xxx`: set fine duty. - `4xxx`: set fine cutoff. `xxx` range is 000-7ff. +> how do I use PCM on a PCM-capable system? + +Two possibilities: the recommended way is via creating the "Amiga/Sample" type instrument and assigning sample to it, or via old, Deflemask-compatible method, using `17xx` effect + > my song sounds very odd at a certain point file a bug report. use the Issues page. diff --git a/extern/Nuked-OPL3 b/extern/Nuked-OPL3 new file mode 160000 index 00000000..bb5c8d08 --- /dev/null +++ b/extern/Nuked-OPL3 @@ -0,0 +1 @@ +Subproject commit bb5c8d08a85779c42b75c79d7b84f365a1b93b66 diff --git a/papers/format.md b/papers/format.md index 8e8c62e2..ff068d83 100644 --- a/papers/format.md +++ b/papers/format.md @@ -173,6 +173,8 @@ size | description | - 0xa9: SegaPCM (for Deflemask Compatibility) - 5 channels | - 0xaa: MSM6295 - 4 channels | - 0xab: MSM6258 - 1 channel + | - 0xac: Commander X16 (VERA) - 17 channels + | - 0xb0: Seta/Allumer X1-010 - 16 channels | - 0xde: YM2610B extended - 19 channels | - 0xe0: QSound - 19 channels | - (compound!) means that the system is composed of two or more chips, diff --git a/src/engine/dispatch.h b/src/engine/dispatch.h index 300b0033..3c39b03b 100644 --- a/src/engine/dispatch.h +++ b/src/engine/dispatch.h @@ -29,6 +29,12 @@ #define addWrite(a,v) regWrites.push_back(DivRegWrite(a,v)); +// HOW TO ADD A NEW COMMAND: +// add it to this enum. then see playback.cpp. +// there is a const char* cmdName[] array, which contains the command +// names as strings for the commands (and other debug stuff). +// +// if you miss it, the program will crash or misbehave at some point. enum DivDispatchCmds { DIV_CMD_NOTE_ON=0, DIV_CMD_NOTE_OFF, @@ -310,6 +316,11 @@ class DivDispatch { */ virtual void notifyInsDeletion(void* ins); + /** + * notify that playback stopped. + */ + virtual void notifyPlaybackStop(); + /** * force-retrigger instruments. */ diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index e34eebbb..f072bd40 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -34,9 +34,11 @@ #include "platform/ym2610bext.h" #include "platform/ay.h" #include "platform/ay8930.h" +#include "platform/opl.h" #include "platform/tia.h" #include "platform/saa.h" #include "platform/amiga.h" +#include "platform/pcspkr.h" #include "platform/segapcm.h" #include "platform/qsound.h" #include "platform/vera.h" @@ -210,6 +212,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do ((DivPlatformOPLL*)dispatch)->setVRC7(sys==DIV_SYSTEM_VRC7); ((DivPlatformOPLL*)dispatch)->setProperDrums(sys==DIV_SYSTEM_OPLL_DRUMS); break; + case DIV_SYSTEM_OPL3: + dispatch=new DivPlatformOPL; + break; case DIV_SYSTEM_SAA1099: { int saaCore=eng->getConfInt("saaCore",0); if (saaCore<0 || saaCore>2) saaCore=0; @@ -217,6 +222,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do ((DivPlatformSAA1099*)dispatch)->setCore((DivSAACores)saaCore); break; } + case DIV_SYSTEM_PCSPKR: + dispatch=new DivPlatformPCSpeaker; + break; case DIV_SYSTEM_LYNX: dispatch=new DivPlatformLynx; break; diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 4f387b27..886c4b9d 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -828,6 +828,9 @@ void DivEngine::stop() { sPreview.sample=-1; sPreview.wave=-1; sPreview.pos=0; + for (int i=0; inotifyPlaybackStop(); + } isBusy.unlock(); } diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 8b5cfad5..ec7cd1cb 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -1112,6 +1112,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { logW("%d: sample depth is wrong! (%d)\n",i,sample->depth); sample->depth=16; } + sample->samples=(double)sample->samples/samplePitches[pitch]; sample->init(sample->samples); unsigned int k=0; diff --git a/src/engine/instrument.h b/src/engine/instrument.h index 5f11df65..1bb61f17 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -23,6 +23,9 @@ #include "dataErrors.h" #include "../ta-utils.h" +// NOTICE! +// before adding new instrument types to this struct, please ask me first. +// absolutely zero support granted to conflicting formats. enum DivInstrumentType { DIV_INS_STD=0, DIV_INS_FM=1, diff --git a/src/engine/platform/abstract.cpp b/src/engine/platform/abstract.cpp index f61bc5be..7b6115ae 100644 --- a/src/engine/platform/abstract.cpp +++ b/src/engine/platform/abstract.cpp @@ -97,6 +97,10 @@ void DivDispatch::notifyInsDeletion(void* ins) { } +void DivDispatch::notifyPlaybackStop() { + +} + void DivDispatch::forceIns() { } diff --git a/src/engine/platform/amiga.cpp b/src/engine/platform/amiga.cpp index 3ee2fd39..c5face68 100644 --- a/src/engine/platform/amiga.cpp +++ b/src/engine/platform/amiga.cpp @@ -84,7 +84,7 @@ void DivPlatformAmiga::acquire(short* bufL, short* bufR, size_t start, size_t le } else { chan[i].sample=-1; } - if (chan[i].freq<124) { + /*if (chan[i].freq<124) { if (++chan[i].busClock>=512) { unsigned int rAmount=(124-chan[i].freq)*2; if (chan[i].audPos>=rAmount) { @@ -92,7 +92,7 @@ void DivPlatformAmiga::acquire(short* bufL, short* bufR, size_t start, size_t le } chan[i].busClock=0; } - } + }*/ chan[i].audSub+=MAX(114,chan[i].freq); } } diff --git a/src/engine/platform/arcade.cpp b/src/engine/platform/arcade.cpp index 15a25517..1af99eb7 100644 --- a/src/engine/platform/arcade.cpp +++ b/src/engine/platform/arcade.cpp @@ -495,8 +495,8 @@ int DivPlatformArcade::dispatch(DivCommand c) { chan[c.chan].ins=c.value; break; case DIV_CMD_PANNING: { - chan[c.chan].chVolL=((c.value>>4)==1); - chan[c.chan].chVolR=((c.value&15)==1); + chan[c.chan].chVolL=((c.value>>4)>0); + chan[c.chan].chVolR=((c.value&15)>0); if (isMuted[c.chan]) { rWrite(chanOffs[c.chan]+ADDR_LR_FB_ALG,(chan[c.chan].state.alg&7)|(chan[c.chan].state.fb<<3)); } else { diff --git a/src/engine/platform/ay.cpp b/src/engine/platform/ay.cpp index 2bcada1e..0777fca8 100644 --- a/src/engine/platform/ay.cpp +++ b/src/engine/platform/ay.cpp @@ -491,6 +491,12 @@ void DivPlatformAY8910::setFlags(unsigned int flags) { case 8: chipClock=COLOR_PAL*3.0/16.0; break; + case 9: + chipClock=COLOR_PAL/4.0; + break; + case 10: + chipClock=2097152; + break; default: chipClock=COLOR_NTSC/2.0; break; diff --git a/src/engine/platform/ay8930.cpp b/src/engine/platform/ay8930.cpp index 040800af..d87045a6 100644 --- a/src/engine/platform/ay8930.cpp +++ b/src/engine/platform/ay8930.cpp @@ -552,6 +552,12 @@ void DivPlatformAY8930::setFlags(unsigned int flags) { case 8: chipClock=COLOR_PAL*3.0/16.0; break; + case 9: + chipClock=COLOR_PAL/4.0; + break; + case 10: + chipClock=2097152; + break; default: chipClock=COLOR_NTSC/2.0; break; diff --git a/src/engine/platform/gb.cpp b/src/engine/platform/gb.cpp index f02ea197..942032e3 100644 --- a/src/engine/platform/gb.cpp +++ b/src/engine/platform/gb.cpp @@ -340,6 +340,7 @@ int DivPlatformGB::dispatch(DivCommand c) { case DIV_CMD_PANNING: { lastPan&=~(0x11<0)|(((c.value>>4)>0)<<4); lastPan|=c.value<0)|(((c.value>>4)>0)<<1); } rWrite(chanOffs[c.chan]+ADDR_LRAF,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|(chan[c.chan].state.fms&7)|((chan[c.chan].state.ams&3)<<4)); break; diff --git a/src/engine/platform/genesisext.cpp b/src/engine/platform/genesisext.cpp index 364505db..02305a9a 100644 --- a/src/engine/platform/genesisext.cpp +++ b/src/engine/platform/genesisext.cpp @@ -106,16 +106,10 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) { opChan[ch].ins=c.value; break; case DIV_CMD_PANNING: { - switch (c.value) { - case 0x01: - opChan[ch].pan=1; - break; - case 0x10: - opChan[ch].pan=2; - break; - default: - opChan[ch].pan=3; - break; + if (c.value==0) { + opChan[ch].pan=3; + } else { + opChan[ch].pan=((c.value&15)>0)|(((c.value>>4)>0)<<1); } // TODO: ??? rWrite(chanOffs[2]+0xb4,(opChan[ch].pan<<6)|(chan[2].state.fms&7)|((chan[2].state.ams&3)<<4)); @@ -378,4 +372,4 @@ void DivPlatformGenesisExt::quit() { } DivPlatformGenesisExt::~DivPlatformGenesisExt() { -} \ No newline at end of file +} diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp new file mode 100644 index 00000000..ebf7dc92 --- /dev/null +++ b/src/engine/platform/opl.cpp @@ -0,0 +1,845 @@ +/** + * 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 "opl.h" +#include "../engine.h" +#include +#include + +#define rWrite(a,v) if (!skipRegisterWrites) {pendingWrites[a]=v;} +#define immWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} } + +#define CHIP_FREQBASE 4720272 + +// N = invalid +#define N 255 + +const unsigned char slotsOPL2[4][20]={ + {0, 1, 2, 6, 7, 8, 12, 13, 14}, // OP1 + {3, 4, 5, 9, 10, 11, 15, 16, 17}, // OP2 + {N, N, N, N, N, N, N, N, N}, + {N, N, N, N, N, N, N, N, N} +}; + +const unsigned char slotsOPL2Drums[4][20]={ + {0, 1, 2, 6, 7, 8, 12, 16, 14, 17, 13}, // OP1 + {3, 4, 5, 9, 10, 11, 15, N, N, N, N}, // OP2 + {N, N, N, N, N, N, N, N, N, N, N}, + {N, N, N, N, N, N, N, N, N, N, N} +}; + +const unsigned char chanMapOPL2[20]={ + 0, 1, 2, 3, 4, 5, 6, 7, 8, N, N, N, N, N, N, N, N, N, N, N +}; + +const unsigned char slotsOPL3[4][20]={ + {0, 6, 1, 7, 2, 8, 18, 24, 19, 25, 20, 26, 30, 31, 32, 12, 13, 14}, // OP1 + {3, 9, 4, 10, 5, 11, 21, 27, 22, 28, 23, 29, 33, 34, 35, 15, 16, 17}, // OP2 + {6, N, 7, N, 8, N, 24, N, 25, N, 26, N, N, N, N, N, N, N}, // OP3 + {9, N, 10, N, 11, N, 27, N, 28, N, 29, N, N, N, N, N, N, N} // OP4 +}; + +const unsigned char slotsOPL3Drums[4][20]={ + {0, 6, 1, 7, 2, 8, 18, 24, 19, 25, 20, 26, 30, 31, 32, 12, 16, 14, 17, 13}, // OP1 + {3, 9, 4, 10, 5, 11, 21, 27, 22, 28, 23, 29, 33, 34, 35, N, N, N, N, N}, // OP2 + {6, N, 7, N, 8, N, 24, N, 25, N, 26, N, N, N, N, N, N, N, N, N}, // OP3 + {9, N, 10, N, 11, N, 27, N, 28, N, 29, N, N, N, N, N, N, N, N, N} // OP4 +}; + +const unsigned char chanMapOPL3[20]={ + 0, 3, 1, 4, 2, 5, 9, 12, 10, 13, 11, 14, 15, 16, 17, 6, 7, 8, N, N +}; + +#undef N + +const char* DivPlatformOPL::getEffectName(unsigned char effect) { + switch (effect) { + case 0x10: + return "10xy: Setup LFO (x: enable; y: speed)"; + break; + case 0x11: + return "11xx: Set feedback (0 to 7)"; + break; + case 0x12: + return "12xx: Set level of operator 1 (0 highest, 3F lowest)"; + break; + case 0x13: + return "13xx: Set level of operator 2 (0 highest, 3F lowest)"; + break; + case 0x14: + return "14xx: Set level of operator 3 (0 highest, 3F lowest; 4-op only)"; + break; + case 0x15: + return "15xx: Set level of operator 4 (0 highest, 3F lowest; 4-op only)"; + break; + case 0x16: + return "16xy: Set operator multiplier (x: operator from 1 to 4; y: multiplier)"; + break; + case 0x17: + return "17xx: Enable channel 6 DAC"; + break; + case 0x18: + return "18xx: Toggle extended channel 3 mode"; + break; + case 0x19: + return "19xx: Set attack of all operators (0 to F)"; + break; + case 0x1a: + return "1Axx: Set attack of operator 1 (0 to F)"; + break; + case 0x1b: + return "1Bxx: Set attack of operator 2 (0 to F)"; + break; + case 0x1c: + return "1Cxx: Set attack of operator 3 (0 to F; 4-op only)"; + break; + case 0x1d: + return "1Dxx: Set attack of operator 4 (0 to F; 4-op only)"; + break; + case 0x20: + return "20xy: Set PSG noise mode (x: preset freq/ch3 freq; y: thin pulse/noise)"; + break; + } + return NULL; +} + +void DivPlatformOPL::acquire_nuked(short* bufL, short* bufR, size_t start, size_t len) { + static short o[2]; + static int os[2]; + + for (size_t h=start; h>8)<<1),w.val); + //printf("write: %x = %.2x\n",w.addr,w.val); + lastBusy=0; + regPool[w.addr&0x1ff]=w.val; + writes.pop(); + } else { + lastBusy++; + //printf("busycounter: %d\n",lastBusy); + OPL3_WriteReg(&fm,0x0+((w.addr>>8)<<1),w.addr); + w.addrOrVal=true; + } + } + + OPL3_Generate(&fm,o); os[0]+=o[0]; os[1]+=o[1]; + + if (os[0]<-32768) os[0]=-32768; + if (os[0]>32767) os[0]=32767; + + if (os[1]<-32768) os[1]=-32768; + if (os[1]>32767) os[1]=32767; + + bufL[h]=os[0]; + bufR[h]=os[1]; + } +} + +void DivPlatformOPL::acquire(short* bufL, short* bufR, size_t start, size_t len) { + //if (useYMFM) { + // acquire_ymfm(bufL,bufR,start,len); + //} else { + acquire_nuked(bufL,bufR,start,len); + //} +} + +void DivPlatformOPL::tick() { + /* + for (int i=0; i<20; i++) { + if (i==2 && extMode) continue; + chan[i].std.next(); + + if (chan[i].std.hadVol) { + chan[i].outVol=(chan[i].vol*MIN(127,chan[i].std.vol))/127; + 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 (isOutput[chan[i].state.alg][j]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } + } + } + } + + if (chan[i].std.hadArp) { + if (!chan[i].inPorta) { + if (chan[i].std.arpMode) { + chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp); + } else { + chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+(signed char)chan[i].std.arp); + } + } + chan[i].freqChanged=true; + } else { + if (chan[i].std.arpMode && chan[i].std.finishedArp) { + chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note); + chan[i].freqChanged=true; + } + } + + if (chan[i].std.hadAlg) { + chan[i].state.alg=chan[i].std.alg; + rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); + if (!parent->song.algMacroBehavior) for (int j=0; j<4; j++) { + unsigned short baseAddr=chanOffs[i]|opOffs[j]; + DivInstrumentFM::Operator& op=chan[i].state.op[j]; + if (isMuted[i]) { + rWrite(baseAddr+ADDR_TL,127); + } else { + if (isOutput[chan[i].state.alg][j]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } + } + } + } + if (chan[i].std.hadFb) { + chan[i].state.fb=chan[i].std.fb; + rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); + } + if (chan[i].std.hadFms) { + chan[i].state.fms=chan[i].std.fms; + 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].std.hadAms) { + chan[i].state.ams=chan[i].std.ams; + rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); + } + for (int j=0; j<4; j++) { + unsigned short baseAddr=chanOffs[i]|opOffs[j]; + DivInstrumentFM::Operator& op=chan[i].state.op[j]; + DivMacroInt::IntOp& m=chan[i].std.op[j]; + if (m.hadAm) { + op.am=m.am; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + if (m.hadAr) { + op.ar=m.ar; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + if (m.hadDr) { + op.dr=m.dr; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + if (m.hadMult) { + op.mult=m.mult; + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + } + if (m.hadRr) { + op.rr=m.rr; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + if (m.hadSl) { + op.sl=m.sl; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + if (m.hadTl) { + op.tl=127-m.tl; + if (isMuted[i]) { + rWrite(baseAddr+ADDR_TL,127); + } else { + if (isOutput[chan[i].state.alg][j]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } + } + } + if (m.hadRs) { + op.rs=m.rs; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + if (m.hadDt) { + op.dt=m.dt; + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + } + if (m.hadD2r) { + op.d2r=m.d2r; + rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); + } + if (m.hadSsg) { + op.ssgEnv=m.ssg; + rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); + } + } + + if (chan[i].keyOn || chan[i].keyOff) { + immWrite(0x28,0x00|konOffs[i]); + chan[i].keyOff=false; + } + } + */ + + for (int i=0; i<512; i++) { + if (pendingWrites[i]!=oldWrites[i]) { + immWrite(i,pendingWrites[i]&0xff); + oldWrites[i]=pendingWrites[i]; + } + } + + /* + for (int i=0; i<20; i++) { + if (chan[i].freqChanged) { + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq)); + if (chan[i].freq>262143) chan[i].freq=262143; + int freqt=toFreq(chan[i].freq); + immWrite(chanOffs[i]+ADDR_FREQH,freqt>>8); + immWrite(chanOffs[i]+ADDR_FREQ,freqt&0xff); + if (chan[i].furnaceDac && dacMode) { + double off=1.0; + if (dacSample>=0 && dacSamplesong.sampleLen) { + DivSample* s=parent->getSample(dacSample); + if (s->centerRate<1) { + off=1.0; + } else { + off=8363.0/(double)s->centerRate; + } + } + dacRate=(1280000*1.25*off)/MAX(1,chan[i].baseFreq); + if (dacRate<1) dacRate=1; + if (dumpWrites) addWrite(0xffff0001,1280000/dacRate); + } + chan[i].freqChanged=false; + } + if (chan[i].keyOn) { + immWrite(0x28,0xf0|konOffs[i]); + chan[i].keyOn=false; + } + } + */ +} + +int DivPlatformOPL::octave(int freq) { + if (freq>=82432) { + return 128; + } else if (freq>=41216) { + return 64; + } else if (freq>=20608) { + return 32; + } else if (freq>=10304) { + return 16; + } else if (freq>=5152) { + return 8; + } else if (freq>=2576) { + return 4; + } else if (freq>=1288) { + return 2; + } else { + return 1; + } + return 1; +} + +int DivPlatformOPL::toFreq(int freq) { + if (freq>=82432) { + return 0x3800|((freq>>7)&0x7ff); + } else if (freq>=41216) { + return 0x3000|((freq>>6)&0x7ff); + } else if (freq>=20608) { + return 0x2800|((freq>>5)&0x7ff); + } else if (freq>=10304) { + return 0x2000|((freq>>4)&0x7ff); + } else if (freq>=5152) { + return 0x1800|((freq>>3)&0x7ff); + } else if (freq>=2576) { + return 0x1000|((freq>>2)&0x7ff); + } else if (freq>=1288) { + return 0x800|((freq>>1)&0x7ff); + } else { + return freq&0x7ff; + } +} + +void DivPlatformOPL::muteChannel(int ch, bool mute) { + isMuted[ch]=mute; + /* + for (int j=0; j<4; j++) { + unsigned short baseAddr=chanOffs[ch]|opOffs[j]; + DivInstrumentFM::Operator& op=chan[ch].state.op[j]; + if (isMuted[ch]) { + rWrite(baseAddr+ADDR_TL,127); + } else { + if (isOutput[chan[ch].state.alg][j]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[ch].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } + } + } + rWrite(chanOffs[ch]+ADDR_LRAF,(isMuted[ch]?0:(chan[ch].pan<<6))|(chan[ch].state.fms&7)|((chan[ch].state.ams&3)<<4)); + */ +} + +int DivPlatformOPL::dispatch(DivCommand c) { + switch (c.cmd) { + case DIV_CMD_NOTE_ON: { + DivInstrument* ins=parent->getIns(chan[c.chan].ins); + + if (chan[c.chan].insChanged) { + chan[c.chan].state=ins->fm; + } + + chan[c.chan].std.init(ins); + if (!chan[c.chan].std.willVol) { + chan[c.chan].outVol=chan[c.chan].vol; + } + + /* + for (int i=0; i<4; i++) { + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + if (isMuted[c.chan]) { + rWrite(baseAddr+ADDR_TL,127); + } else { + if (isOutput[chan[c.chan].state.alg][i]) { + if (!chan[c.chan].active || chan[c.chan].insChanged) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127)); + } + } else { + if (chan[c.chan].insChanged) { + rWrite(baseAddr+ADDR_TL,op.tl); + } + } + } + if (chan[c.chan].insChanged) { + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); + } + } + if (chan[c.chan].insChanged) { + rWrite(chanOffs[c.chan]+ADDR_FB_ALG,(chan[c.chan].state.alg&7)|(chan[c.chan].state.fb<<3)); + rWrite(chanOffs[c.chan]+ADDR_LRAF,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|(chan[c.chan].state.fms&7)|((chan[c.chan].state.ams&3)<<4)); + } + */ + chan[c.chan].insChanged=false; + + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value); + chan[c.chan].note=c.value; + chan[c.chan].freqChanged=true; + } + chan[c.chan].keyOn=true; + chan[c.chan].active=true; + break; + } + case DIV_CMD_NOTE_OFF: + if (c.chan==5) { + dacSample=-1; + if (dumpWrites) addWrite(0xffff0002,0); + } + chan[c.chan].keyOff=true; + chan[c.chan].keyOn=false; + chan[c.chan].active=false; + break; + case DIV_CMD_NOTE_OFF_ENV: + if (c.chan==5) { + dacSample=-1; + if (dumpWrites) addWrite(0xffff0002,0); + } + chan[c.chan].keyOff=true; + chan[c.chan].keyOn=false; + chan[c.chan].active=false; + chan[c.chan].std.release(); + break; + case DIV_CMD_ENV_RELEASE: + chan[c.chan].std.release(); + break; + case DIV_CMD_VOLUME: { + chan[c.chan].vol=c.value; + if (!chan[c.chan].std.hasVol) { + chan[c.chan].outVol=c.value; + } + /* + for (int i=0; i<4; i++) { + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + if (isMuted[c.chan]) { + rWrite(baseAddr+ADDR_TL,127); + } else { + if (isOutput[chan[c.chan].state.alg][i]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } + } + } + */ + break; + } + case DIV_CMD_GET_VOLUME: { + return chan[c.chan].vol; + break; + } + case DIV_CMD_INSTRUMENT: + if (chan[c.chan].ins!=c.value || c.value2==1) { + chan[c.chan].insChanged=true; + } + chan[c.chan].ins=c.value; + break; + case DIV_CMD_PANNING: { + switch (c.value) { + case 0x01: + chan[c.chan].pan=1; + break; + case 0x10: + chan[c.chan].pan=2; + break; + default: + chan[c.chan].pan=3; + break; + } + //rWrite(chanOffs[c.chan]+ADDR_LRAF,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|(chan[c.chan].state.fms&7)|((chan[c.chan].state.ams&3)<<4)); + break; + } + case DIV_CMD_PITCH: { + chan[c.chan].pitch=c.value; + chan[c.chan].freqChanged=true; + break; + } + case DIV_CMD_NOTE_PORTA: { + int destFreq=NOTE_FREQUENCY(c.value2); + int newFreq; + bool return2=false; + if (destFreq>chan[c.chan].baseFreq) { + newFreq=chan[c.chan].baseFreq+c.value*octave(chan[c.chan].baseFreq); + if (newFreq>=destFreq) { + newFreq=destFreq; + return2=true; + } + } else { + newFreq=chan[c.chan].baseFreq-c.value*octave(chan[c.chan].baseFreq); + if (newFreq<=destFreq) { + newFreq=destFreq; + return2=true; + } + } + if (!chan[c.chan].portaPause) { + if (octave(chan[c.chan].baseFreq)!=octave(newFreq)) { + chan[c.chan].portaPause=true; + break; + } + } + chan[c.chan].baseFreq=newFreq; + chan[c.chan].portaPause=false; + chan[c.chan].freqChanged=true; + if (return2) { + chan[c.chan].inPorta=false; + return 2; + } + break; + } + case DIV_CMD_SAMPLE_MODE: { + dacMode=c.value; + rWrite(0x2b,c.value<<7); + break; + } + case DIV_CMD_SAMPLE_BANK: + sampleBank=c.value; + if (sampleBank>(parent->song.sample.size()/12)) { + sampleBank=parent->song.sample.size()/12; + } + break; + case DIV_CMD_LEGATO: { + chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value); + chan[c.chan].note=c.value; + chan[c.chan].freqChanged=true; + break; + } + case DIV_CMD_FM_LFO: { + lfoValue=(c.value&7)|((c.value>>4)<<3); + rWrite(0x22,lfoValue); + break; + } + case DIV_CMD_FM_FB: { + chan[c.chan].state.fb=c.value&7; + //rWrite(chanOffs[c.chan]+ADDR_FB_ALG,(chan[c.chan].state.alg&7)|(chan[c.chan].state.fb<<3)); + break; + } + case DIV_CMD_FM_MULT: { + /* + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.mult=c.value2&15; + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + */ + break; + } + case DIV_CMD_FM_TL: { + /* + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.tl=c.value2; + if (isMuted[c.chan]) { + rWrite(baseAddr+ADDR_TL,127); + } else { + if (isOutput[chan[c.chan].state.alg][c.value]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } + } + */ + break; + } + case DIV_CMD_FM_AR: { + /* + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.ar=c.value2&31; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + } else { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.ar=c.value2&31; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + */ + + break; + } + case DIV_ALWAYS_SET_VOLUME: + return 0; + break; + case DIV_CMD_GET_VOLMAX: + return 127; + break; + case DIV_CMD_PRE_PORTA: + chan[c.chan].inPorta=c.value; + break; + case DIV_CMD_PRE_NOTE: + break; + default: + //printf("WARNING: unimplemented command %d\n",c.cmd); + break; + } + return 1; +} + +void DivPlatformOPL::forceIns() { + /* + for (int i=0; i<20; i++) { + 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 (isOutput[chan[i].state.alg][j]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } + } + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + 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 (chan[i].active) { + chan[i].keyOn=true; + chan[i].freqChanged=true; + } + } + if (dacMode) { + rWrite(0x2b,0x80); + } + immWrite(0x22,lfoValue); + */ +} + +void DivPlatformOPL::toggleRegisterDump(bool enable) { + DivDispatch::toggleRegisterDump(enable); +} + +void* DivPlatformOPL::getChanState(int ch) { + return &chan[ch]; +} + +unsigned char* DivPlatformOPL::getRegisterPool() { + return regPool; +} + +int DivPlatformOPL::getRegisterPoolSize() { + return 512; +} + +void DivPlatformOPL::reset() { + while (!writes.empty()) writes.pop(); + memset(regPool,0,512); + /* + if (useYMFM) { + fm_ymfm->reset(); + } + */ + OPL3_Reset(&fm,rate); + if (dumpWrites) { + addWrite(0xffffffff,0); + } + for (int i=0; i<20; i++) { + chan[i]=DivPlatformOPL::Channel(); + chan[i].vol=0x3f; + chan[i].outVol=0x3f; + } + + for (int i=0; i<512; i++) { + oldWrites[i]=-1; + pendingWrites[i]=-1; + } + + lastBusy=60; + dacMode=0; + dacPeriod=0; + dacPos=0; + dacRate=0; + dacSample=-1; + sampleBank=0; + lfoValue=8; + + extMode=false; + + // LFO + immWrite(0x22,lfoValue); + + delay=0; +} + +bool DivPlatformOPL::isStereo() { + return true; +} + +bool DivPlatformOPL::keyOffAffectsArp(int ch) { + return (ch>5); +} + +bool DivPlatformOPL::keyOffAffectsPorta(int ch) { + return (ch>5); +} + +void DivPlatformOPL::notifyInsChange(int ins) { + for (int i=0; i<20; i++) { + if (chan[i].ins==ins) { + chan[i].insChanged=true; + } + } +} + +void DivPlatformOPL::notifyInsDeletion(void* ins) { +} + +void DivPlatformOPL::poke(unsigned int addr, unsigned short val) { + immWrite(addr,val); +} + +void DivPlatformOPL::poke(std::vector& wlist) { + for (DivRegWrite& i: wlist) immWrite(i.addr,i.val); +} + +int DivPlatformOPL::getPortaFloor(int ch) { + return (ch>5)?12:0; +} + +void DivPlatformOPL::setYMFM(bool use) { + useYMFM=use; +} + +void DivPlatformOPL::setOPLType(int type) { + switch (type) { + case 1: case 2: + slotsNonDrums=(const unsigned char**)slotsOPL2; + slotsDrums=(const unsigned char**)slotsOPL2Drums; + chanMap=chanMapOPL2; + break; + case 3: + slotsNonDrums=(const unsigned char**)slotsOPL3; + slotsDrums=(const unsigned char**)slotsOPL3Drums; + chanMap=chanMapOPL3; + break; + } + oplType=type; +} + +void DivPlatformOPL::setFlags(unsigned int flags) { + /* + if (flags==3) { + chipClock=COLOR_NTSC*12.0/7.0; + } else if (flags==2) { + chipClock=8000000.0; + } else if (flags==1) { + chipClock=COLOR_PAL*12.0/7.0; + } else { + chipClock=COLOR_NTSC*15.0/7.0; + } + ladder=flags&0x80000000; + OPN2_SetChipType(ladder?ym3438_mode_ym2612:0); + if (useYMFM) { + if (fm_ymfm!=NULL) delete fm_ymfm; + if (ladder) { + fm_ymfm=new ymfm::ym2612(iface); + } else { + fm_ymfm=new ymfm::ym3438(iface); + } + rate=chipClock/144; + } else { + rate=chipClock/36; + }*/ + + chipClock=COLOR_NTSC*4.0; + rate=chipClock/32; +} + +int DivPlatformOPL::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { + parent=p; + dumpWrites=false; + ladder=false; + skipRegisterWrites=false; + for (int i=0; i<20; i++) { + isMuted[i]=false; + } + setFlags(flags); + + reset(); + return 10; +} + +void DivPlatformOPL::quit() { +} + +DivPlatformOPL::~DivPlatformOPL() { +} diff --git a/src/engine/platform/opl.h b/src/engine/platform/opl.h new file mode 100644 index 00000000..ab6226a4 --- /dev/null +++ b/src/engine/platform/opl.h @@ -0,0 +1,125 @@ +/** + * 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 _OPL_H +#define _OPL_H +#include "../dispatch.h" +#include "../macroInt.h" +#include +#include "../../../extern/Nuked-OPL3/opl3.h" + +class DivPlatformOPL: public DivDispatch { + protected: + struct Channel { + DivInstrumentFM state; + DivMacroInt std; + unsigned char freqH, freqL; + int freq, baseFreq, pitch, note; + unsigned char ins; + bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, furnaceDac, inPorta; + int vol, outVol; + unsigned char pan; + Channel(): + freqH(0), + freqL(0), + freq(0), + baseFreq(0), + pitch(0), + note(0), + ins(-1), + active(false), + insChanged(true), + freqChanged(false), + keyOn(false), + keyOff(false), + portaPause(false), + furnaceDac(false), + inPorta(false), + vol(0), + pan(3) {} + }; + Channel chan[20]; + bool isMuted[20]; + struct QueuedWrite { + unsigned short addr; + unsigned char val; + bool addrOrVal; + QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {} + }; + std::queue writes; + opl3_chip fm; + const unsigned char** slotsNonDrums; + const unsigned char** slotsDrums; + const unsigned char** slots; + const unsigned char* chanMap; + int delay, oplType; + unsigned char lastBusy; + + unsigned char regPool[512]; + + bool dacMode; + int dacPeriod; + int dacRate; + unsigned int dacPos; + int dacSample; + unsigned char sampleBank; + unsigned char lfoValue; + + bool extMode, useYMFM; + bool ladder; + + short oldWrites[512]; + short pendingWrites[512]; + + int octave(int freq); + int toFreq(int freq); + + friend void putDispatchChan(void*,int,int); + + void acquire_nuked(short* bufL, short* bufR, size_t start, size_t len); + //void acquire_ymfm(short* bufL, short* bufR, size_t start, size_t len); + + public: + void acquire(short* bufL, short* bufR, size_t start, size_t len); + int dispatch(DivCommand c); + void* getChanState(int chan); + unsigned char* getRegisterPool(); + int getRegisterPoolSize(); + void reset(); + void forceIns(); + void tick(); + void muteChannel(int ch, bool mute); + bool isStereo(); + void setYMFM(bool use); + void setOPLType(int type); + bool keyOffAffectsArp(int ch); + bool keyOffAffectsPorta(int ch); + void toggleRegisterDump(bool enable); + void setFlags(unsigned int flags); + void notifyInsChange(int ins); + void notifyInsDeletion(void* ins); + int getPortaFloor(int ch); + void poke(unsigned int addr, unsigned short val); + void poke(std::vector& wlist); + const char* getEffectName(unsigned char effect); + int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); + void quit(); + ~DivPlatformOPL(); +}; +#endif diff --git a/src/engine/platform/opll.cpp b/src/engine/platform/opll.cpp index 5ed5b6e3..fd1d61d6 100644 --- a/src/engine/platform/opll.cpp +++ b/src/engine/platform/opll.cpp @@ -29,17 +29,14 @@ const char* DivPlatformOPLL::getEffectName(unsigned char effect) { switch (effect) { - case 0x10: - return "10xy: Setup LFO (x: enable; y: speed)"; - break; case 0x11: return "11xx: Set feedback (0 to 7)"; break; case 0x12: - return "12xx: Set level of operator 1 (0 highest, 7F lowest)"; + return "12xx: Set level of operator 1 (0 highest, 3F lowest)"; break; case 0x13: - return "13xx: Set level of operator 2 (0 highest, 7F lowest)"; + return "13xx: Set level of operator 2 (0 highest, F lowest)"; break; case 0x16: return "16xy: Set operator multiplier (x: operator from 1 to 2; y: multiplier)"; @@ -50,13 +47,13 @@ const char* DivPlatformOPLL::getEffectName(unsigned char effect) { } break; case 0x19: - return "19xx: Set attack of all operators (0 to 1F)"; + return "19xx: Set attack of all operators (0 to F)"; break; case 0x1a: - return "1Axx: Set attack of operator 1 (0 to 1F)"; + return "1Axx: Set attack of operator 1 (0 to F)"; break; case 0x1b: - return "1Bxx: Set attack of operator 2 (0 to 1F)"; + return "1Bxx: Set attack of operator 2 (0 to F)"; break; } return NULL; diff --git a/src/engine/platform/pcspkr.cpp b/src/engine/platform/pcspkr.cpp new file mode 100644 index 00000000..1ff8ae0c --- /dev/null +++ b/src/engine/platform/pcspkr.cpp @@ -0,0 +1,413 @@ +/** + * 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 "pcspkr.h" +#include "../engine.h" +#include + +#ifdef __linux__ +#include +#include +#include +#include +#include +#include +#endif + +#define PCSPKR_DIVIDER 4 +#define CHIP_DIVIDER 1 + +const char* regCheatSheetPCSpeaker[]={ + "Period", "0", + NULL +}; + +const char** DivPlatformPCSpeaker::getRegisterSheet() { + return regCheatSheetPCSpeaker; +} + +const char* DivPlatformPCSpeaker::getEffectName(unsigned char effect) { + return NULL; +} + +const float cut=0.05; +const float reso=0.06; + +void DivPlatformPCSpeaker::acquire_unfilt(short* bufL, short* bufR, size_t start, size_t len) { + for (size_t i=start; i(freq>>1) && !isMuted[0])?32767:0; + } else { + bufL[i]=0; + } + } +} + +void DivPlatformPCSpeaker::acquire_cone(short* bufL, short* bufR, size_t start, size_t len) { + for (size_t i=start; i((freq+16)>>1) && !isMuted[0])?1:0; + low+=0.04*band; + band+=0.04*(next-low-band); + float out=(low+band)*0.75; + if (out>1.0) out=1.0; + if (out<-1.0) out=-1.0; + bufL[i]=out*32767; + } else { + bufL[i]=0; + } + } +} + +void DivPlatformPCSpeaker::acquire_piezo(short* bufL, short* bufR, size_t start, size_t len) { + for (size_t i=start; i((freq+64)>>1) && !isMuted[0])?1:0; + low+=cut*band; + band+=cut*(next-low-(reso*band)); + float out=band*0.15-(next-low)*0.06; + if (out>1.0) out=1.0; + if (out<-1.0) out=-1.0; + bufL[i]=out*32767; + } else { + bufL[i]=0; + } + } +} + +void DivPlatformPCSpeaker::beepFreq(int freq) { +#ifdef __linux__ + static struct input_event ie; + if (beepFD>=0) { + gettimeofday(&ie.time,NULL); + ie.type=EV_SND; + ie.code=SND_TONE; + if (freq>0) { + ie.value=chipClock/freq; + } else { + ie.value=0; + } + if (write(beepFD,&ie,sizeof(struct input_event))<0) { + perror("error while writing frequency!"); + } else { + //printf("writing freq: %d\n",freq); + } + } +#endif +} + +void DivPlatformPCSpeaker::acquire_real(short* bufL, short* bufR, size_t start, size_t len) { + if (lastOn!=on || lastFreq!=freq) { + lastOn=on; + lastFreq=freq; + beepFreq((on && !isMuted[0])?freq:0); + } + for (size_t i=start; icalcFreq(chan[i].baseFreq,chan[i].pitch,true)-1; + if (chan[i].freq<0) chan[i].freq=0; + if (chan[i].freq>65535) chan[i].freq=65535; + if (chan[i].keyOn) { + on=true; + } + if (chan[i].keyOff) { + on=false; + } + freq=chan[i].freq; + if (chan[i].keyOn) chan[i].keyOn=false; + if (chan[i].keyOff) chan[i].keyOff=false; + chan[i].freqChanged=false; + } + } +} + +int DivPlatformPCSpeaker::dispatch(DivCommand c) { + switch (c.cmd) { + case DIV_CMD_NOTE_ON: + 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].std.init(parent->getIns(chan[c.chan].ins)); + break; + case DIV_CMD_NOTE_OFF: + chan[c.chan].active=false; + chan[c.chan].keyOff=true; + chan[c.chan].std.init(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; + } + break; + case DIV_CMD_VOLUME: + if (chan[c.chan].vol!=c.value) { + chan[c.chan].vol=c.value; + if (!chan[c.chan].std.hasVol) { + chan[c.chan].outVol=c.value; + } + if (chan[c.chan].active) { + on=chan[c.chan].vol; + } + } + break; + case DIV_CMD_GET_VOLUME: + return chan[c.chan].vol; + break; + case DIV_CMD_PITCH: + chan[c.chan].pitch=c.value; + chan[c.chan].freqChanged=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: + if (c.chan==3) break; + chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+((chan[c.chan].std.willArp && !chan[c.chan].std.arpMode)?(chan[c.chan].std.arp):(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].std.init(parent->getIns(chan[c.chan].ins)); + } + chan[c.chan].inPorta=c.value; + break; + case DIV_CMD_GET_VOLMAX: + return 1; + break; + case DIV_ALWAYS_SET_VOLUME: + return 1; + break; + default: + break; + } + return 1; +} + +void DivPlatformPCSpeaker::muteChannel(int ch, bool mute) { + isMuted[ch]=mute; +} + +void DivPlatformPCSpeaker::forceIns() { + for (int i=0; i<1; i++) { + chan[i].insChanged=true; + } +} + +void* DivPlatformPCSpeaker::getChanState(int ch) { + return &chan[ch]; +} + +unsigned char* DivPlatformPCSpeaker::getRegisterPool() { + if (on) { + regPool[0]=freq; + regPool[1]=freq>>8; + } else { + regPool[0]=0; + regPool[1]=0; + } + return regPool; +} + +int DivPlatformPCSpeaker::getRegisterPoolSize() { + return 2; +} + +void DivPlatformPCSpeaker::reset() { + for (int i=0; i<1; i++) { + chan[i]=DivPlatformPCSpeaker::Channel(); + } + if (dumpWrites) { + addWrite(0xffffffff,0); + } + + on=false; + lastOn=false; + freq=0; + lastFreq=0; + pos=0; + flip=false; + low=0; + band=0; + + if (speakerType==3) { +#ifdef __linux__ + if (beepFD==-1) { + beepFD=open("/dev/input/by-path/platform-pcspkr-event-spkr",O_WRONLY); + if (beepFD<0) { + perror("error while opening PC speaker"); + } + } +#endif + beepFreq(0); + } else { + beepFreq(0); + } + + memset(regPool,0,2); +} + +bool DivPlatformPCSpeaker::keyOffAffectsArp(int ch) { + return true; +} + +void DivPlatformPCSpeaker::setFlags(unsigned int flags) { + chipClock=COLOR_NTSC/3.0; + rate=chipClock/PCSPKR_DIVIDER; + speakerType=flags&3; +} + +void DivPlatformPCSpeaker::notifyInsDeletion(void* ins) { + for (int i=0; i<1; i++) { + chan[i].std.notifyInsDeletion((DivInstrument*)ins); + } +} + +void DivPlatformPCSpeaker::notifyPlaybackStop() { + beepFreq(0); +} + +void DivPlatformPCSpeaker::poke(unsigned int addr, unsigned short val) { + // ??? +} + +void DivPlatformPCSpeaker::poke(std::vector& wlist) { + // ??? +} + +int DivPlatformPCSpeaker::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { + parent=p; + dumpWrites=false; + skipRegisterWrites=false; + beepFD=-1; + for (int i=0; i<1; i++) { + isMuted[i]=false; + } + setFlags(flags); + + reset(); + return 5; +} + +void DivPlatformPCSpeaker::quit() { + if (speakerType==3) { + beepFreq(0); + } +#ifdef __linux__ + if (beepFD>=0) close(beepFD); +#endif +} + +DivPlatformPCSpeaker::~DivPlatformPCSpeaker() { +} diff --git a/src/engine/platform/pcspkr.h b/src/engine/platform/pcspkr.h new file mode 100644 index 00000000..17dedf24 --- /dev/null +++ b/src/engine/platform/pcspkr.h @@ -0,0 +1,95 @@ +/** + * 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 _PCSPKR_H +#define _PCSPKR_H + +#include "../dispatch.h" +#include "../macroInt.h" + +class DivPlatformPCSpeaker: public DivDispatch { + struct Channel { + int freq, baseFreq, pitch, note; + unsigned char ins, duty, sweep; + bool active, insChanged, freqChanged, sweepChanged, keyOn, keyOff, inPorta, furnaceDac; + signed char vol, outVol, wave; + DivMacroInt std; + Channel(): + freq(0), + baseFreq(0), + pitch(0), + note(0), + ins(-1), + duty(0), + sweep(8), + active(false), + insChanged(true), + freqChanged(false), + sweepChanged(false), + keyOn(false), + keyOff(false), + inPorta(false), + furnaceDac(false), + vol(15), + outVol(15), + wave(-1) {} + }; + Channel chan[1]; + bool isMuted[1]; + bool on, flip, lastOn; + int pos, speakerType, beepFD; + float low, band; + float low2, high2, band2; + float low3, band3; + unsigned short freq, lastFreq; + unsigned char regPool[2]; + + friend void putDispatchChan(void*,int,int); + + void beepFreq(int freq); + + void acquire_unfilt(short* bufL, short* bufR, size_t start, size_t len); + void acquire_cone(short* bufL, short* bufR, size_t start, size_t len); + void acquire_piezo(short* bufL, short* bufR, size_t start, size_t len); + void acquire_real(short* bufL, short* bufR, size_t start, size_t len); + + public: + void acquire(short* bufL, short* bufR, size_t start, size_t len); + int dispatch(DivCommand c); + void* getChanState(int chan); + unsigned char* getRegisterPool(); + int getRegisterPoolSize(); + void reset(); + void forceIns(); + void tick(); + void muteChannel(int ch, bool mute); + bool keyOffAffectsArp(int ch); + void setFlags(unsigned int flags); + void notifyInsDeletion(void* ins); + void notifyPlaybackStop(); + void poke(unsigned int addr, unsigned short val); + void poke(std::vector& wlist); + const char** getRegisterSheet(); + const char* getEffectName(unsigned char effect); + int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); + void quit(); + ~DivPlatformPCSpeaker(); +}; + +#endif diff --git a/src/engine/platform/ym2610.cpp b/src/engine/platform/ym2610.cpp index dd2447da..1bd460fa 100644 --- a/src/engine/platform/ym2610.cpp +++ b/src/engine/platform/ym2610.cpp @@ -892,16 +892,10 @@ int DivPlatformYM2610::dispatch(DivCommand c) { chan[c.chan].ins=c.value; break; case DIV_CMD_PANNING: { - switch (c.value) { - case 0x01: - chan[c.chan].pan=1; - break; - case 0x10: - chan[c.chan].pan=2; - break; - default: - chan[c.chan].pan=3; - break; + if (c.value==0) { + chan[c.chan].pan=3; + } else { + chan[c.chan].pan=((c.value&15)>0)|(((c.value>>4)>0)<<1); } if (c.chan>12) { immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6)); diff --git a/src/engine/platform/ym2610b.cpp b/src/engine/platform/ym2610b.cpp index 27bb2afb..a6bcf9ac 100644 --- a/src/engine/platform/ym2610b.cpp +++ b/src/engine/platform/ym2610b.cpp @@ -955,16 +955,10 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { chan[c.chan].ins=c.value; break; case DIV_CMD_PANNING: { - switch (c.value) { - case 0x01: - chan[c.chan].pan=1; - break; - case 0x10: - chan[c.chan].pan=2; - break; - default: - chan[c.chan].pan=3; - break; + if (c.value==0) { + chan[c.chan].pan=3; + } else { + chan[c.chan].pan=((c.value&15)>0)|(((c.value>>4)>0)<<1); } if (c.chan>14) { immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6)); diff --git a/src/engine/platform/ym2610bext.cpp b/src/engine/platform/ym2610bext.cpp index f48fe19a..5b3c3872 100644 --- a/src/engine/platform/ym2610bext.cpp +++ b/src/engine/platform/ym2610bext.cpp @@ -97,16 +97,10 @@ int DivPlatformYM2610BExt::dispatch(DivCommand c) { opChan[ch].ins=c.value; break; case DIV_CMD_PANNING: { - switch (c.value) { - case 0x01: - opChan[ch].pan=1; - break; - case 0x10: - opChan[ch].pan=2; - break; - default: - opChan[ch].pan=3; - break; + if (c.value==0) { + opChan[ch].pan=3; + } else { + opChan[ch].pan=((c.value&15)>0)|(((c.value>>4)>0)<<1); } DivInstrument* ins=parent->getIns(opChan[ch].ins); // TODO: ??? @@ -334,4 +328,4 @@ void DivPlatformYM2610BExt::quit() { } DivPlatformYM2610BExt::~DivPlatformYM2610BExt() { -} \ No newline at end of file +} diff --git a/src/engine/platform/ym2610ext.cpp b/src/engine/platform/ym2610ext.cpp index aff4bbf3..5e633eb2 100644 --- a/src/engine/platform/ym2610ext.cpp +++ b/src/engine/platform/ym2610ext.cpp @@ -97,16 +97,10 @@ int DivPlatformYM2610Ext::dispatch(DivCommand c) { opChan[ch].ins=c.value; break; case DIV_CMD_PANNING: { - switch (c.value) { - case 0x01: - opChan[ch].pan=1; - break; - case 0x10: - opChan[ch].pan=2; - break; - default: - opChan[ch].pan=3; - break; + if (c.value==0) { + opChan[ch].pan=3; + } else { + opChan[ch].pan=((c.value&15)>0)|(((c.value>>4)>0)<<1); } DivInstrument* ins=parent->getIns(opChan[ch].ins); // TODO: ??? @@ -334,4 +328,4 @@ void DivPlatformYM2610Ext::quit() { } DivPlatformYM2610Ext::~DivPlatformYM2610Ext() { -} \ No newline at end of file +} diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index e1a7e3a4..e49545f1 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -42,6 +42,7 @@ const char* notes[12]={ "C-", "C#", "D-", "D#", "E-", "F-", "F#", "G-", "G#", "A-", "A#", "B-" }; +// update this when adding new commands. const char* cmdName[DIV_CMD_MAX]={ "NOTE_ON", "NOTE_OFF", @@ -224,14 +225,6 @@ bool DivEngine::perSystemEffect(int ch, unsigned char effect, unsigned char effe return false; } break; - case DIV_SYSTEM_LYNX: - if (effect>=0x30 && effect<0x40) { - int value = ((int)(effect&0x0f)<<8)|effectVal; - dispatchCmd(DivCommand(DIV_CMD_LYNX_LFSR_LOAD,ch,value)); - break; - } - return false; - break; case DIV_SYSTEM_OPLL_DRUMS: switch (effect) { case 0x18: // drum mode toggle @@ -545,6 +538,14 @@ bool DivEngine::perSystemPostEffect(int ch, unsigned char effect, unsigned char return false; } break; + case DIV_SYSTEM_LYNX: + if (effect>=0x30 && effect<0x40) { + int value = ((int)(effect&0x0f)<<8)|effectVal; + dispatchCmd(DivCommand(DIV_CMD_LYNX_LFSR_LOAD,ch,value)); + break; + } + return false; + break; default: return false; } diff --git a/src/engine/song.h b/src/engine/song.h index 133175c0..fd1df2b9 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -195,6 +195,8 @@ struct DivSong { // - 6: 0.89MHz (Sunsoft 5B) // - 7: 1.67MHz // - 8: 0.83MHz (Sunsoft 5B on PAL) + // - 9: 1.10MHz (Gamate/VIC-20 PAL) + // - 10: 2.097152MHz (Game Boy) // - bit 4-5: chip type (ignored on AY8930) // - 0: AY-3-8910 or similar // - 1: YM2149 @@ -216,6 +218,12 @@ struct DivSong { // - 1: Amiga 1200 // - bit 8-14: stereo separation // - 0 is 0% while 127 is 100% + // - PC Speaker: + // - bit 0-1: speaker type + // - 0: unfiltered + // - 1: cone + // - 2: piezo + // - 3: real (TODO) // - QSound: // - bit 12-20: echo feedback // - Valid values are 0-255 diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 79335dfd..f13ad55d 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -17,7 +17,6 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include #define _USE_MATH_DEFINES #include "gui.h" #include "util.h" @@ -623,6 +622,10 @@ void FurnaceGUI::drawEditControls() { e->noteOff(activeNotes[i].chan); } activeNotes.clear(); + + if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { + nextWindow=GUI_WINDOW_PATTERN; + } } ImGui::Text("Edit Step"); @@ -630,6 +633,10 @@ void FurnaceGUI::drawEditControls() { if (ImGui::InputInt("##EditStep",&editStep,1,1)) { if (editStep>=e->song.patLen) editStep=e->song.patLen-1; if (editStep<0) editStep=0; + + if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { + nextWindow=GUI_WINDOW_PATTERN; + } } if (ImGui::Button(ICON_FA_PLAY "##Play")) { @@ -649,9 +656,9 @@ void FurnaceGUI::drawEditControls() { ImGui::Text("Follow"); ImGui::SameLine(); - ImGui::Checkbox("Orders",&followOrders); + unimportant(ImGui::Checkbox("Orders",&followOrders)); ImGui::SameLine(); - ImGui::Checkbox("Pattern",&followPattern); + unimportant(ImGui::Checkbox("Pattern",&followPattern)); bool repeatPattern=e->getRepeatPattern(); if (ImGui::Checkbox("Repeat pattern",&repeatPattern)) { @@ -713,6 +720,10 @@ void FurnaceGUI::drawEditControls() { e->noteOff(activeNotes[i].chan); } activeNotes.clear(); + + if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { + nextWindow=GUI_WINDOW_PATTERN; + } } ImGui::SameLine(); @@ -722,14 +733,18 @@ void FurnaceGUI::drawEditControls() { if (ImGui::InputInt("##EditStep",&editStep,1,1)) { if (editStep>=e->song.patLen) editStep=e->song.patLen-1; if (editStep<0) editStep=0; + + if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { + nextWindow=GUI_WINDOW_PATTERN; + } } ImGui::SameLine(); ImGui::Text("Follow"); ImGui::SameLine(); - ImGui::Checkbox("Orders",&followOrders); + unimportant(ImGui::Checkbox("Orders",&followOrders)); ImGui::SameLine(); - ImGui::Checkbox("Pattern",&followPattern); + unimportant(ImGui::Checkbox("Pattern",&followPattern)); } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_EDIT_CONTROLS; ImGui::End(); @@ -776,6 +791,10 @@ void FurnaceGUI::drawEditControls() { e->noteOff(activeNotes[i].chan); } activeNotes.clear(); + + if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { + nextWindow=GUI_WINDOW_PATTERN; + } } ImGui::Text("Step"); @@ -783,16 +802,20 @@ void FurnaceGUI::drawEditControls() { if (ImGui::InputInt("##EditStep",&editStep,0,0)) { if (editStep>=e->song.patLen) editStep=e->song.patLen-1; if (editStep<0) editStep=0; + + if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { + nextWindow=GUI_WINDOW_PATTERN; + } } ImGui::Text("Foll."); ImGui::PushStyleColor(ImGuiCol_Button,ImVec4(0.2f,(followOrders)?0.6f:0.2f,0.2f,1.0f)); - if (ImGui::SmallButton("Ord##FollowOrders")) { + if (ImGui::SmallButton("Ord##FollowOrders")) { handleUnimportant followOrders=!followOrders; } ImGui::PopStyleColor(); ImGui::PushStyleColor(ImGuiCol_Button,ImVec4(0.2f,(followPattern)?0.6f:0.2f,0.2f,1.0f)); - if (ImGui::SmallButton("Pat##FollowPattern")) { + if (ImGui::SmallButton("Pat##FollowPattern")) { handleUnimportant followPattern=!followPattern; } ImGui::PopStyleColor(); @@ -860,6 +883,10 @@ void FurnaceGUI::drawEditControls() { e->noteOff(activeNotes[i].chan); } activeNotes.clear(); + + if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { + nextWindow=GUI_WINDOW_PATTERN; + } } ImGui::Text("Step"); @@ -869,11 +896,15 @@ void FurnaceGUI::drawEditControls() { if (ImGui::InputInt("##EditStep",&editStep,1,1)) { if (editStep>=e->song.patLen) editStep=e->song.patLen-1; if (editStep<0) editStep=0; + + if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { + nextWindow=GUI_WINDOW_PATTERN; + } } ImGui::NextColumn(); - ImGui::Checkbox("Follow orders",&followOrders); - ImGui::Checkbox("Follow pattern",&followPattern); + unimportant(ImGui::Checkbox("Follow orders",&followOrders)); + unimportant(ImGui::Checkbox("Follow pattern",&followPattern)); } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_EDIT_CONTROLS; ImGui::End(); @@ -1169,10 +1200,15 @@ void FurnaceGUI::drawInsList() { if (ImGui::Selectable(name.c_str(),curIns==i)) { curIns=i; } + if (settings.insFocusesPattern && patternOpen && ImGui::IsItemActivated()) { + nextWindow=GUI_WINDOW_PATTERN; + curIns=i; + } ImGui::PopStyleColor(); if (ImGui::IsItemHovered()) { if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { insEditOpen=true; + nextWindow=GUI_WINDOW_INS_EDIT; } } } @@ -4589,6 +4625,7 @@ bool FurnaceGUI::loop() { sysAddOption(DIV_SYSTEM_YM2610B_EXT); sysAddOption(DIV_SYSTEM_AY8910); sysAddOption(DIV_SYSTEM_AMIGA); + sysAddOption(DIV_SYSTEM_PCSPKR); sysAddOption(DIV_SYSTEM_OPLL); sysAddOption(DIV_SYSTEM_OPLL_DRUMS); sysAddOption(DIV_SYSTEM_VRC7); @@ -4782,6 +4819,14 @@ bool FurnaceGUI::loop() { e->setSysFlags(i,(flags&(~15))|8,restart); updateWindowTitle(); } + if (ImGui::RadioButton("1.10MHz (Gamate/VIC-20 PAL)",(flags&15)==9)) { + e->setSysFlags(i,(flags&(~15))|9,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("2^21Hz (Game Boy)",(flags&15)==10)) { + e->setSysFlags(i,(flags&(~15))|10,restart); + updateWindowTitle(); + } if (e->song.system[i]==DIV_SYSTEM_AY8910) { ImGui::Text("Chip type:"); if (ImGui::RadioButton("AY-3-8910",(flags&0x30)==0)) { @@ -4843,6 +4888,26 @@ bool FurnaceGUI::loop() { } break; } + case DIV_SYSTEM_PCSPKR: { + ImGui::Text("Speaker type:"); + if (ImGui::RadioButton("Unfiltered",(flags&3)==0)) { + e->setSysFlags(i,(flags&(~3))|0,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("Cone",(flags&3)==1)) { + e->setSysFlags(i,(flags&(~3))|1,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("Piezo",(flags&3)==2)) { + e->setSysFlags(i,(flags&(~3))|2,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("Use system beeper (Linux only!)",(flags&3)==3)) { + e->setSysFlags(i,(flags&(~3))|3,restart); + updateWindowTitle(); + } + break; + } case DIV_SYSTEM_QSOUND: { ImGui::Text("Echo delay:"); int echoBufSize=2725 - (flags & 4095); @@ -4906,6 +4971,7 @@ bool FurnaceGUI::loop() { sysChangeOption(i,DIV_SYSTEM_YM2610B_EXT); sysChangeOption(i,DIV_SYSTEM_AY8910); sysChangeOption(i,DIV_SYSTEM_AMIGA); + sysChangeOption(i,DIV_SYSTEM_PCSPKR); sysChangeOption(i,DIV_SYSTEM_OPLL); sysChangeOption(i,DIV_SYSTEM_OPLL_DRUMS); sysChangeOption(i,DIV_SYSTEM_VRC7); @@ -5072,19 +5138,19 @@ bool FurnaceGUI::loop() { ImGui::DockSpaceOverViewport(); + drawPattern(); drawEditControls(); drawSongInfo(); drawOrders(); - drawInsList(); - drawInsEdit(); - drawWaveList(); - drawWaveEdit(); drawSampleList(); drawSampleEdit(); + drawWaveList(); + drawWaveEdit(); + drawInsList(); + drawInsEdit(); drawMixer(); drawOsc(); drawVolMeter(); - drawPattern(); drawSettings(); drawDebug(); drawStats(); diff --git a/src/gui/gui.h b/src/gui/gui.h index e58bd17b..7d7f6b4a 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -29,6 +29,9 @@ #define rightClickable if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) ImGui::SetKeyboardFocusHere(-1); +#define handleUnimportant if (settings.insFocusesPattern && patternOpen) {nextWindow=GUI_WINDOW_PATTERN;} +#define unimportant(x) if (x) {handleUnimportant} + enum FurnaceGUIColors { GUI_COLOR_BACKGROUND=0, GUI_COLOR_FRAME_BACKGROUND, @@ -495,6 +498,7 @@ class FurnaceGUI { int viewPrevPattern; int guiColorsBase; int avoidRaisingPattern; + int insFocusesPattern; unsigned int maxUndoSteps; String mainFontPath; String patFontPath; @@ -537,6 +541,7 @@ class FurnaceGUI { viewPrevPattern(1), guiColorsBase(0), avoidRaisingPattern(0), + insFocusesPattern(1), maxUndoSteps(100), mainFontPath(""), patFontPath(""), diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 1650fd15..b6c1c3fd 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -60,7 +60,7 @@ const char* ssgEnvTypes[8]={ }; const char* fmParamNames[3][27]={ - {"Algorithm", "Feedback", "LFO > Freq", "LFO > Amp", "Attack", "Decay", "Decay 2", "Release", "Sustain", "Level", "EnvScale", "Multiplier", "Detune", "Detune 2", "SSG-EG", "AM", "AM Depth", "Vibrato Depth", "EnvAlternate", "EnvAlternate", "LevelScale/key", "Sustain", "Vibrato", "Waveform", "EnvScale/key", "OP2 HalfSine", "OP1 HalfSine"}, + {"Algorithm", "Feedback", "LFO > Freq", "LFO > Amp", "Attack", "Decay", "Decay 2", "Release", "Sustain", "Level", "EnvScale", "Multiplier", "Detune", "Detune 2", "SSG-EG", "AM", "AM Depth", "Vibrato Depth", "Sustained", "Sustained", "Level Scaling", "Sustain", "Vibrato", "Waveform", "Key Scale Rate", "OP2 Half Sine", "OP1 Half Sine"}, {"ALG", "FB", "FMS/PMS", "AMS", "AR", "DR", "SR", "RR", "SL", "TL", "KS", "MULT", "DT", "DT2", "SSG-EG", "AM", "AMD", "FMD", "EGT", "EGT", "KSL", "SUS", "VIB", "WS", "KSR", "DC", "DM"}, {"ALG", "FB", "FMS/PMS", "AMS", "AR", "DR", "D2R", "RR", "SL", "TL", "RS", "MULT", "DT", "DT2", "SSG-EG", "AM", "DAM", "DVB", "EGT", "EGS", "KSL", "SUS", "VIB", "WS", "KSR", "DC", "DM"} }; diff --git a/src/gui/orders.cpp b/src/gui/orders.cpp index 94191ec0..22bd7e0d 100644 --- a/src/gui/orders.cpp +++ b/src/gui/orders.cpp @@ -75,6 +75,10 @@ void FurnaceGUI::drawOrders() { e->setOrder(i); curNibble=false; orderCursor=-1; + + if (orderEditMode==0) { + handleUnimportant; + } } ImGui::PopStyleColor(); for (int j=0; jgetTotalChannelCount(); j++) { @@ -111,6 +115,10 @@ void FurnaceGUI::drawOrders() { curNibble=false; } } + + if (orderEditMode==0) { + handleUnimportant; + } } if (!pat->name.empty() && ImGui::IsItemHovered()) { ImGui::SetTooltip("%s",pat->name.c_str()); @@ -148,21 +156,21 @@ void FurnaceGUI::drawOrders() { ImGui::EndTable(); } ImGui::NextColumn(); - if (ImGui::Button(ICON_FA_PLUS)) { + if (ImGui::Button(ICON_FA_PLUS)) { handleUnimportant // add order row (new) doAction(GUI_ACTION_ORDERS_ADD); } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Add new order"); } - if (ImGui::Button(ICON_FA_MINUS)) { + if (ImGui::Button(ICON_FA_MINUS)) { handleUnimportant // remove this order row doAction(GUI_ACTION_ORDERS_REMOVE); } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Remove order"); - } - if (ImGui::Button(ICON_FA_FILES_O)) { + } + if (ImGui::Button(ICON_FA_FILES_O)) { handleUnimportant // duplicate order row doAction(GUI_ACTION_ORDERS_DUPLICATE); } @@ -172,21 +180,21 @@ void FurnaceGUI::drawOrders() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Duplicate order (right-click to deep clone)"); } - if (ImGui::Button(ICON_FA_ANGLE_UP)) { + if (ImGui::Button(ICON_FA_ANGLE_UP)) { handleUnimportant // move order row up doAction(GUI_ACTION_ORDERS_MOVE_UP); } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Move order up"); } - if (ImGui::Button(ICON_FA_ANGLE_DOWN)) { + if (ImGui::Button(ICON_FA_ANGLE_DOWN)) { handleUnimportant // move order row down doAction(GUI_ACTION_ORDERS_MOVE_DOWN); } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Move order down"); } - if (ImGui::Button(ICON_FA_ANGLE_DOUBLE_DOWN)) { + if (ImGui::Button(ICON_FA_ANGLE_DOUBLE_DOWN)) { handleUnimportant // duplicate order row at end doAction(GUI_ACTION_ORDERS_DUPLICATE_END); } @@ -196,7 +204,7 @@ void FurnaceGUI::drawOrders() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Duplicate order at end of song (right-click to deep clone)"); } - if (ImGui::Button(changeAllOrders?ICON_FA_LINK"##ChangeAll":ICON_FA_CHAIN_BROKEN"##ChangeAll")) { + if (ImGui::Button(changeAllOrders?ICON_FA_LINK"##ChangeAll":ICON_FA_CHAIN_BROKEN"##ChangeAll")) { handleUnimportant // whether to change one or all orders in a row changeAllOrders=!changeAllOrders; } @@ -217,7 +225,7 @@ void FurnaceGUI::drawOrders() { } else { orderEditModeLabel=ICON_FA_MOUSE_POINTER "##OrderEditMode"; } - if (ImGui::Button(orderEditModeLabel)) { + if (ImGui::Button(orderEditModeLabel)) { handleUnimportant orderEditMode++; if (orderEditMode>3) orderEditMode=0; curNibble=false; diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 304db046..3f0e532d 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -167,6 +167,11 @@ void FurnaceGUI::drawSettings() { settings.avoidRaisingPattern=avoidRaisingPatternB; } + bool insFocusesPatternB=settings.insFocusesPattern; + if (ImGui::Checkbox("Focus pattern editor when selecting instrument",&insFocusesPatternB)) { + settings.insFocusesPattern=insFocusesPatternB; + } + bool restartOnFlagChangeB=settings.restartOnFlagChange; if (ImGui::Checkbox("Restart song when changing system properties",&restartOnFlagChangeB)) { settings.restartOnFlagChange=restartOnFlagChangeB; @@ -388,7 +393,7 @@ void FurnaceGUI::drawSettings() { } bool macroViewB=settings.macroView; - if (ImGui::Checkbox("Classic macro view (standard macros only)",¯oViewB)) { + if (ImGui::Checkbox("Classic macro view (standard macros only; deprecated!)",¯oViewB)) { settings.macroView=macroViewB; } @@ -829,6 +834,14 @@ void FurnaceGUI::drawSettings() { #define LOAD_KEYBIND(x,y) \ actionKeys[x]=e->getConfInt("keybind_" #x,y); +#define clampSetting(x,minV,maxV) \ + if (xmaxV) { \ + x=maxV; \ + } + void FurnaceGUI::syncSettings() { settings.mainFontSize=e->getConfInt("mainFontSize",18); settings.patFontSize=e->getConfInt("patFontSize",18); @@ -856,7 +869,6 @@ void FurnaceGUI::syncSettings() { settings.allowEditDocking=e->getConfInt("allowEditDocking",0); settings.chipNames=e->getConfInt("chipNames",0); settings.overflowHighlight=e->getConfInt("overflowHighlight",0); - if (settings.fmNames<0 || settings.fmNames>2) settings.fmNames=0; settings.partyTime=e->getConfInt("partyTime",0); settings.germanNotation=e->getConfInt("germanNotation",0); settings.stepOnDelete=e->getConfInt("stepOnDelete",0); @@ -870,6 +882,44 @@ void FurnaceGUI::syncSettings() { settings.viewPrevPattern=e->getConfInt("viewPrevPattern",1); settings.guiColorsBase=e->getConfInt("guiColorsBase",0); settings.avoidRaisingPattern=e->getConfInt("avoidRaisingPattern",0); + settings.insFocusesPattern=e->getConfInt("insFocusesPattern",1); + + clampSetting(settings.mainFontSize,2,96); + clampSetting(settings.patFontSize,2,96); + clampSetting(settings.iconSize,2,48); + clampSetting(settings.audioEngine,0,1); + clampSetting(settings.audioQuality,0,1); + clampSetting(settings.audioBufSize,32,4096); + clampSetting(settings.audioRate,8000,384000); + clampSetting(settings.arcadeCore,0,1); + clampSetting(settings.ym2612Core,0,1); + clampSetting(settings.saaCore,0,1); + clampSetting(settings.mainFont,0,6); + clampSetting(settings.patFont,0,6); + clampSetting(settings.patRowsBase,0,1); + clampSetting(settings.orderRowsBase,0,1); + clampSetting(settings.soloAction,0,2); + clampSetting(settings.pullDeleteBehavior,0,1); + clampSetting(settings.wrapHorizontal,0,2); + clampSetting(settings.wrapVertical,0,2); + clampSetting(settings.macroView,0,1); + clampSetting(settings.fmNames,0,2); + clampSetting(settings.allowEditDocking,0,1); + clampSetting(settings.chipNames,0,1); + clampSetting(settings.overflowHighlight,0,1); + clampSetting(settings.partyTime,0,1); + clampSetting(settings.germanNotation,0,1); + clampSetting(settings.stepOnDelete,0,1); + clampSetting(settings.scrollStep,0,1); + clampSetting(settings.sysSeparators,0,1); + clampSetting(settings.forceMono,0,1); + clampSetting(settings.controlLayout,0,3); + clampSetting(settings.statusDisplay,0,3); + clampSetting(settings.dpiScale,0.0f,4.0f); + clampSetting(settings.viewPrevPattern,0,1); + clampSetting(settings.guiColorsBase,0,1); + clampSetting(settings.avoidRaisingPattern,0,1); + clampSetting(settings.insFocusesPattern,0,1); // keybinds LOAD_KEYBIND(GUI_ACTION_OPEN,FURKMOD_CMD|SDLK_o); @@ -1067,6 +1117,7 @@ void FurnaceGUI::commitSettings() { e->setConf("viewPrevPattern",settings.viewPrevPattern); e->setConf("guiColorsBase",settings.guiColorsBase); e->setConf("avoidRaisingPattern",settings.avoidRaisingPattern); + e->setConf("insFocusesPattern",settings.insFocusesPattern); PUT_UI_COLOR(GUI_COLOR_BACKGROUND); PUT_UI_COLOR(GUI_COLOR_FRAME_BACKGROUND);