From 344f8d3a2269f255e8e407ca740fcf871588eba6 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 18 Jan 2026 18:31:37 -0500 Subject: [PATCH] Namco 163: add waveform position latch option for FamiTracker compatibility its default value will be decided by a poll issue #2476 --- src/engine/fileOps/ftm.cpp | 4 +-- src/engine/platform/n163.cpp | 49 ++++++++++++++++++++++++++++++------ src/engine/platform/n163.h | 7 ++++-- src/gui/sysConf.cpp | 6 +++++ 4 files changed, 55 insertions(+), 11 deletions(-) diff --git a/src/engine/fileOps/ftm.cpp b/src/engine/fileOps/ftm.cpp index 52cfc0b42..67c46ebb3 100644 --- a/src/engine/fileOps/ftm.cpp +++ b/src/engine/fileOps/ftm.cpp @@ -711,6 +711,7 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si ds.system[systemID] = DIV_SYSTEM_N163; ds.systemFlags[systemID].set("channels", (int)n163Chans - 1); ds.systemChans[systemID]=CLAMP(n163Chans,1,8); + ds.systemFlags[systemID].set("posLatch",true); systemID++; for (int ch = 0; ch < (int)n163Chans; ch++) { @@ -1948,8 +1949,7 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si // - in FamiTracker this is in bytes // - a value of 7F has special meaning if (pat->newData[row][DIV_PAT_FXVAL(j)]==0x7f) { - pat->newData[row][DIV_PAT_FX(j)]=-1; - pat->newData[row][DIV_PAT_FXVAL(j)]=-1; + pat->newData[row][DIV_PAT_FXVAL(j)]=0xff; } else { pat->newData[row][DIV_PAT_FXVAL(j)]=MIN(pat->newData[row][DIV_PAT_FXVAL(j)]<<1,0xff); } diff --git a/src/engine/platform/n163.cpp b/src/engine/platform/n163.cpp index bf9d73a42..b27f7f4e7 100644 --- a/src/engine/platform/n163.cpp +++ b/src/engine/platform/n163.cpp @@ -20,6 +20,7 @@ #include "n163.h" #include "../engine.h" #include "../../ta-log.h" +#include "IconsFontAwesome4.h" #include #define rWrite(a,v) if (!skipRegisterWrites) {writes.push(QueuedWrite(a,v)); if (dumpWrites) {addWrite(a,v);} } @@ -206,7 +207,7 @@ void DivPlatformN163::tick(bool sysTick) { } chan[i].freqChanged=true; } - if (chan[i].std.duty.had) { + if (chan[i].std.duty.had && !chan[i].wavePosLatch) { if (chan[i].curWavePos!=chan[i].std.duty.val) { chan[i].curWavePos=chan[i].std.duty.val; chan[i].waveChanged=true; @@ -230,7 +231,7 @@ void DivPlatformN163::tick(bool sysTick) { } chan[i].freqChanged=true; } - if (chan[i].std.ex1.had) { + if (chan[i].std.ex1.had && !chan[i].wavePosLatch) { if (chan[i].curWaveLen!=(chan[i].std.ex1.val&0xfc)) { chan[i].curWaveLen=chan[i].std.ex1.val&0xfc; chan[i].freqChanged=true; @@ -310,11 +311,13 @@ int DivPlatformN163::dispatch(DivCommand c) { if (ins->n163.wave>=0) { chan[c.chan].wave=ins->n163.wave; } - chan[c.chan].wavePos=ins->n163.perChanPos?ins->n163.wavePosCh[c.chan&7]:ins->n163.wavePos; - chan[c.chan].waveLen=ins->n163.perChanPos?ins->n163.waveLenCh[c.chan&7]:ins->n163.waveLen; - chan[c.chan].waveMode=ins->n163.waveMode; - chan[c.chan].curWavePos=chan[c.chan].wavePos; - chan[c.chan].curWaveLen=chan[c.chan].waveLen; + if (!chan[c.chan].wavePosLatch) { + chan[c.chan].wavePos=ins->n163.perChanPos?ins->n163.wavePosCh[c.chan&7]:ins->n163.wavePos; + chan[c.chan].waveLen=ins->n163.perChanPos?ins->n163.waveLenCh[c.chan&7]:ins->n163.waveLen; + chan[c.chan].waveMode=ins->n163.waveMode; + chan[c.chan].curWavePos=chan[c.chan].wavePos; + chan[c.chan].curWaveLen=chan[c.chan].waveLen; + } chan[c.chan].ws.init(NULL,chan[c.chan].waveLen,15,true); if (chan[c.chan].wave<0) { chan[c.chan].wave=0; @@ -425,6 +428,23 @@ int DivPlatformN163::dispatch(DivCommand c) { chan[c.chan].waveUpdated=true; } } + + // wave position latching. + // if enabled in chip flags, setting wave pos through effects will "lock" it + // to a specific value until a wave pos effect with value FE or FF is used. + if (posLatch) { + chan[c.chan].wavePosLatch=true; + if (c.value>=0xfe) { + chan[c.chan].wavePosLatch=false; + + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_N163); + chan[c.chan].wavePos=ins->n163.perChanPos?ins->n163.wavePosCh[c.chan&7]:ins->n163.wavePos; + chan[c.chan].waveLen=ins->n163.perChanPos?ins->n163.waveLenCh[c.chan&7]:ins->n163.waveLen; + chan[c.chan].waveMode=ins->n163.waveMode; + chan[c.chan].curWavePos=chan[c.chan].wavePos; + chan[c.chan].curWaveLen=chan[c.chan].waveLen; + } + } break; case DIV_CMD_N163_WAVE_LENGTH: if (c.value2&1) { @@ -543,6 +563,20 @@ DivDispatchOscBuffer* DivPlatformN163::getOscBuffer(int ch) { return oscBuf[ch]; } +DivChannelModeHints DivPlatformN163::getModeHints(int ch) { + DivChannelModeHints ret; + if (!posLatch) return ret; + + ret.count=1; + ret.hint[0]=ICON_FA_LOCK; + ret.type[0]=0; + if (chan[ch].wavePosLatch) { + ret.type[0]=21; + } + + return ret; +} + unsigned char* DivPlatformN163::getRegisterPool() { return regPool; } @@ -601,6 +635,7 @@ void DivPlatformN163::setFlags(const DivConfig& flags) { CHECK_CUSTOM_CLOCK; initChanMax=chanMax=flags.getInt("channels",7)&7; multiplex=!flags.getBool("multiplex",false); // not accurate in real hardware + posLatch=flags.getBool("posLatch",false); rate=chipClock; rate/=15; n163.set_multiplex(multiplex); diff --git a/src/engine/platform/n163.h b/src/engine/platform/n163.h index d447ec0fd..7fcd22b1a 100644 --- a/src/engine/platform/n163.h +++ b/src/engine/platform/n163.h @@ -31,9 +31,10 @@ class DivPlatformN163: public DivDispatch { short wave, wavePos, waveLen; short curWavePos, curWaveLen; bool waveMode; + bool wavePosLatch; bool volumeChanged; bool waveChanged, waveUpdated; - DivWaveSynth ws; + DivWaveSynth ws; Channel(): SharedChannel(15), resVol(15), @@ -43,6 +44,7 @@ class DivPlatformN163: public DivDispatch { curWavePos(0), curWaveLen(0), waveMode(0), + wavePosLatch(false), volumeChanged(false), waveChanged(false), waveUpdated(false) {} @@ -61,7 +63,7 @@ class DivPlatformN163: public DivDispatch { unsigned char initChanMax; unsigned char chanMax; short loadWave, loadPos; - bool multiplex, lenCompensate; + bool multiplex, lenCompensate, posLatch; n163_core n163; unsigned char regPool[128]; @@ -83,6 +85,7 @@ class DivPlatformN163: public DivDispatch { void forceIns(); void tick(bool sysTick=true); void muteChannel(int ch, bool mute); + DivChannelModeHints getModeHints(int chan); const DivMemoryComposition* getMemCompo(int index); void setFlags(const DivConfig& flags); void notifyWaveChange(int wave); diff --git a/src/gui/sysConf.cpp b/src/gui/sysConf.cpp index e4e497123..05b648ea5 100644 --- a/src/gui/sysConf.cpp +++ b/src/gui/sysConf.cpp @@ -1259,6 +1259,7 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl int channels=flags.getInt("channels",7)+1; bool multiplex=flags.getBool("multiplex",false); bool lenCompensate=flags.getBool("lenCompensate",false); + bool posLatch=flags.getBool("posLatch",false); ImGui::Text(_("Clock rate:")); ImGui::Indent(); @@ -1304,6 +1305,10 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl if (ImGui::Checkbox(_("Scale frequency to wave length"),&lenCompensate)) { altered=true; } + if (ImGui::Checkbox(_("Waveform position latch"),&posLatch)) { + altered=true; + } + ImGui::SetItemTooltip(_("when enabled, a waveform position effect will lock the position, preventing instrument changes from changing it.\nuse a wave position effect with value FE or FF to unlock it.")); if (altered) { e->lockSave([&]() { @@ -1311,6 +1316,7 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl flags.set("channels",channels-1); flags.set("multiplex",multiplex); flags.set("lenCompensate",lenCompensate); + flags.set("posLatch",posLatch); }); } break;