diff --git a/papers/format.md b/papers/format.md index 005345755..9ce20d2d7 100644 --- a/papers/format.md +++ b/papers/format.md @@ -29,6 +29,7 @@ furthermore, an `or reserved` indicates this field is always present, but is res the format versions are: +- 79: Furnace dev79 - 78: Furnace dev78 - 77: Furnace dev77 - 76: Furnace dev76 @@ -579,6 +580,19 @@ size | description --- | **OPZ instrument extra data** (>=77) 1 | fms2 1 | ams2 + --- | **wavetable synth data** (>=79) + 4 | first wave + 4 | second wave + 1 | rate divider + 1 | effect + | - bit 7: single or dual effect + 1 | enabled + 1 | global + 1 | speed (+1) + 1 | parameter 1 + 1 | parameter 2 + 1 | parameter 3 + 1 | parameter 4 ``` # wavetable diff --git a/src/engine/engine.h b/src/engine/engine.h index e5240ab9e..677cdeaaa 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -42,8 +42,8 @@ #define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock(); #define BUSY_END isBusy.unlock(); softLocked=false; -#define DIV_VERSION "dev78" -#define DIV_ENGINE_VERSION 78 +#define DIV_VERSION "dev79" +#define DIV_ENGINE_VERSION 79 // for imports #define DIV_VERSION_MOD 0xff01 diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 8ed6551b9..7988992ba 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -69,7 +69,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { } ds.version=(unsigned char)reader.readC(); logI("module version %d (0x%.2x)\n",ds.version,ds.version); - if (ds.version>0x19) { + if (ds.version>0x1a) { logE("this version is not supported by Furnace yet!\n"); lastError="this version is not supported by Furnace yet"; delete[] file; @@ -579,6 +579,13 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { } else { wave->data[j]=reader.readI(); } + wave->data[j]&=wave->max; + } + // #FDS4Bit + if (ds.system[0]==DIV_SYSTEM_NES_FDS && ds.version<0x1a) { + for (int j=0; jlen; j++) { + wave->data[j]*=4; + } } ds.wave.push_back(wave); } @@ -2222,7 +2229,7 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) { SafeWriter* DivEngine::saveDMF(unsigned char version) { // fail if version is not supported - if (version<24 || version>25) { + if (version<24 || version>26) { logE("cannot save in this version!\n"); lastError="invalid version to save in! this is a bug!"; return NULL; @@ -2273,6 +2280,12 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) { lastError="NES + VRC7 not supported in 1.0/legacy .dmf!"; return NULL; } + // fail if the system is FDS and version<25 + if (version<25 && song.system[0]==DIV_SYSTEM_NES && song.system[1]==DIV_SYSTEM_FDS) { + logE("FDS not supported in 1.0/legacy .dmf!\n"); + lastError="FDS not supported in 1.0/legacy .dmf!"; + return NULL; + } // fail if the system is Furnace-exclusive if (!isFlat && systemToFileDMF(song.system[0])==0) { logE("cannot save Furnace-exclusive system song!\n"); @@ -2308,7 +2321,7 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) { sys=DIV_SYSTEM_NES_VRC7; } else if (song.system[0]==DIV_SYSTEM_NES && song.system[1]==DIV_SYSTEM_FDS) { w->writeC(systemToFileDMF(DIV_SYSTEM_NES_FDS)); - sys=DIV_SYSTEM_NES_VRC7; + sys=DIV_SYSTEM_NES_FDS; } else { w->writeC(systemToFileDMF(song.system[0])); sys=song.system[0]; @@ -2481,7 +2494,13 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) { w->writeC(song.wave.size()); for (DivWavetable* i: song.wave) { w->writeI(i->len); - w->write(i->data,4*i->len); + if (sys==DIV_SYSTEM_NES_FDS && version<26) { + for (int j=0; jlen; j++) { + w->writeI(i->data[j]>>2); + } + } else { + w->write(i->data,4*i->len); + } } for (int i=0; iwriteC(fm.fms2); w->writeC(fm.ams2); + + // wave synth + w->writeI(ws.wave1); + w->writeI(ws.wave2); + w->writeC(ws.rateDivider); + w->writeC(ws.effect); + w->writeC(ws.enabled); + w->writeC(ws.global); + w->writeC(ws.speed); + w->writeC(ws.param1); + w->writeC(ws.param2); + w->writeC(ws.param3); + w->writeC(ws.param4); } DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) { @@ -895,6 +908,21 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) { fm.fms2=reader.readC(); fm.ams2=reader.readC(); } + + // wave synth + if (version>=79) { + ws.wave1=reader.readI(); + ws.wave2=reader.readI(); + ws.rateDivider=reader.readC(); + ws.effect=reader.readC(); + ws.enabled=reader.readC(); + ws.global=reader.readC(); + ws.speed=reader.readC(); + ws.param1=reader.readC(); + ws.param2=reader.readC(); + ws.param3=reader.readC(); + ws.param4=reader.readC(); + } return DIV_DATA_SUCCESS; } diff --git a/src/engine/instrument.h b/src/engine/instrument.h index 1c8525436..8109030ed 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -485,6 +485,9 @@ enum DivWaveSynthEffects { DIV_WS_SUBTRACT, DIV_WS_AVERAGE, DIV_WS_PHASE, + + DIV_WS_SINGLE_MAX, + // two waveform effects DIV_WS_NONE_DUAL=128, DIV_WS_WIPE, @@ -493,25 +496,25 @@ enum DivWaveSynthEffects { DIV_WS_OVERLAY, DIV_WS_NEGATIVE_OVERLAY, DIV_WS_PHASE_DUAL, + + DIV_WS_DUAL_MAX }; struct DivInstrumentWaveSynth { int wave1, wave2; - unsigned char rateDivider, width, height; - DivWaveSynthEffects effect; + unsigned char rateDivider; + unsigned char effect; bool oneShot, enabled, global; unsigned char speed, param1, param2, param3, param4; DivInstrumentWaveSynth(): wave1(0), wave2(0), rateDivider(1), - width(32), - height(32), effect(DIV_WS_NONE), oneShot(false), enabled(false), global(false), - speed(1), + speed(0), param1(0), param2(0), param3(0), diff --git a/src/engine/platform/bubsyswsg.cpp b/src/engine/platform/bubsyswsg.cpp index 406ac69fb..989619722 100644 --- a/src/engine/platform/bubsyswsg.cpp +++ b/src/engine/platform/bubsyswsg.cpp @@ -71,17 +71,13 @@ void DivPlatformBubSysWSG::acquire(short* bufL, short* bufR, size_t start, size_ } void DivPlatformBubSysWSG::updateWave(int ch) { - DivWavetable* wt=parent->getWave(chan[ch].wave); + //DivWavetable* wt=parent->getWave(chan[ch].wave); for (int i=0; i<32; i++) { - if (wt->max>0 && wt->len>0) { - int data=wt->data[i*wt->len/32]*15/wt->max; // 4 bit PROM at bubble system - if (data<0) data=0; - if (data>15) data=15; - chan[ch].waveROM[i]=data-8; // convert to signed - } + // convert to signed + chan[ch].waveROM[i]=chan[ch].ws.output[i]-8; } if (chan[ch].active) { - rWrite(2+ch,(chan[ch].wave<<5)|chan[ch].outVol); + rWrite(2+ch,(ch<<5)|chan[ch].outVol); } } @@ -108,12 +104,17 @@ void DivPlatformBubSysWSG::tick() { } } if (chan[i].std.hadWave) { - if (chan[i].wave!=chan[i].std.wave) { + if (chan[i].wave!=chan[i].std.wave || chan[i].ws.activeChanged()) { chan[i].wave=chan[i].std.wave; - updateWave(i); + chan[i].ws.changeWave1(chan[i].wave); if (!chan[i].keyOff) chan[i].keyOn=true; } } + if (chan[i].active) { + if (chan[i].ws.tick()) { + updateWave(i); + } + } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { //DivInstrument* ins=parent->getIns(chan[i].ins); chan[i].freq=0x1000-parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true); @@ -123,10 +124,7 @@ void DivPlatformBubSysWSG::tick() { rWrite(i,chan[i].freq); k005289->update(i); if (chan[i].keyOn) { - if (chan[i].wave<0) { - chan[i].wave=0; - updateWave(i); - } + // ??? } if (chan[i].keyOff) { rWrite(2+i,(chan[i].wave<<5)|0); @@ -151,6 +149,12 @@ int DivPlatformBubSysWSG::dispatch(DivCommand c) { chan[c.chan].keyOn=true; rWrite(2+c.chan,(chan[c.chan].wave<<5)|chan[c.chan].vol); chan[c.chan].std.init(ins); + if (chan[c.chan].wave<0) { + chan[c.chan].wave=0; + chan[c.chan].ws.changeWave1(chan[c.chan].wave); + } + chan[c.chan].ws.init(ins,32,15,chan[c.chan].insChanged); + chan[c.chan].insChanged=false; break; } case DIV_CMD_NOTE_OFF: @@ -188,7 +192,7 @@ int DivPlatformBubSysWSG::dispatch(DivCommand c) { break; case DIV_CMD_WAVE: chan[c.chan].wave=c.value; - updateWave(c.chan); + chan[c.chan].ws.changeWave1(chan[c.chan].wave); chan[c.chan].keyOn=true; break; case DIV_CMD_NOTE_PORTA: { @@ -270,6 +274,8 @@ void DivPlatformBubSysWSG::reset() { memset(regPool,0,4*2); for (int i=0; i<2; i++) { chan[i]=DivPlatformBubSysWSG::Channel(); + chan[i].ws.setEngine(parent); + chan[i].ws.init(NULL,32,15,false); } if (dumpWrites) { addWrite(0xffffffff,0); @@ -288,6 +294,7 @@ bool DivPlatformBubSysWSG::keyOffAffectsArp(int ch) { void DivPlatformBubSysWSG::notifyWaveChange(int wave) { for (int i=0; i<2; i++) { if (chan[i].wave==wave) { + chan[i].ws.changeWave1(chan[i].wave); updateWave(i); } } diff --git a/src/engine/platform/bubsyswsg.h b/src/engine/platform/bubsyswsg.h index 5fda0fd95..6c0e2261e 100644 --- a/src/engine/platform/bubsyswsg.h +++ b/src/engine/platform/bubsyswsg.h @@ -23,6 +23,7 @@ #include "../dispatch.h" #include #include "../macroInt.h" +#include "../waveSynth.h" #include "sound/k005289/k005289.hpp" class DivPlatformBubSysWSG: public DivDispatch { @@ -33,6 +34,7 @@ class DivPlatformBubSysWSG: public DivDispatch { signed char vol, outVol, wave; signed char waveROM[32] = {0}; // 4 bit PROM per channel on bubble system DivMacroInt std; + DivWaveSynth ws; Channel(): freq(0), baseFreq(0), diff --git a/src/engine/platform/fds.cpp b/src/engine/platform/fds.cpp index ede447df8..cd70dfacd 100644 --- a/src/engine/platform/fds.cpp +++ b/src/engine/platform/fds.cpp @@ -89,18 +89,10 @@ void DivPlatformFDS::acquire(short* bufL, short* bufR, size_t start, size_t len) } void DivPlatformFDS::updateWave() { - DivWavetable* wt=parent->getWave(chan[0].wave); // TODO: master volume rWrite(0x4089,0x80); for (int i=0; i<64; i++) { - if (wt->max<1 || wt->len<1) { - rWrite(0x4040+i,0); - } else { - int data=wt->data[i*wt->len/64]*63/wt->max; - if (data<0) data=0; - if (data>63) data=63; - rWrite(0x4040+i,data); - } + rWrite(0x4040+i,ws.output[i]); } rWrite(0x4089,0); } @@ -157,12 +149,18 @@ void DivPlatformFDS::tick() { } }*/ if (chan[i].std.hadWave) { - if (chan[i].wave!=chan[i].std.wave) { + if (chan[i].wave!=chan[i].std.wave || ws.activeChanged()) { chan[i].wave=chan[i].std.wave; - updateWave(); + ws.changeWave1(chan[i].wave); //if (!chan[i].keyOff) chan[i].keyOn=true; } } + if (chan[i].active) { + if (ws.tick()) { + updateWave(); + if (!chan[i].keyOff) chan[i].keyOn=true; + } + } if (chan[i].std.hadEx1) { // mod depth chan[i].modOn=chan[i].std.ex1; chan[i].modDepth=chan[i].std.ex1; @@ -190,10 +188,7 @@ void DivPlatformFDS::tick() { if (chan[i].freq>4095) chan[i].freq=4095; if (chan[i].freq<0) chan[i].freq=0; if (chan[i].keyOn) { - if (chan[i].wave<0) { - chan[i].wave=0; - updateWave(); - } + // ??? } if (chan[i].keyOff) { rWrite(0x4080,0x80); @@ -209,20 +204,20 @@ void DivPlatformFDS::tick() { int DivPlatformFDS::dispatch(DivCommand c) { switch (c.cmd) { - case DIV_CMD_NOTE_ON: + case DIV_CMD_NOTE_ON: { + DivInstrument* ins=parent->getIns(chan[c.chan].ins); if (c.value!=DIV_NOTE_NULL) { chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value); chan[c.chan].freqChanged=true; chan[c.chan].note=c.value; } if (chan[c.chan].insChanged) { - DivInstrument* ins=parent->getIns(chan[c.chan].ins); if (ins->fds.initModTableWithFirstWave) { // compatible if (chan[c.chan].wave==-1) { DivWavetable* wt=parent->getWave(0); for (int i=0; i<32; i++) { if (wt->max<1 || wt->len<1) { - rWrite(0x4040+i,0); + chan[c.chan].modTable[i]=0; } else { int data=wt->data[i*MIN(32,wt->len)/32]*7/wt->max; if (data<0) data=0; @@ -253,9 +248,16 @@ int DivPlatformFDS::dispatch(DivCommand c) { } chan[c.chan].active=true; chan[c.chan].keyOn=true; - chan[c.chan].std.init(parent->getIns(chan[c.chan].ins)); + chan[c.chan].std.init(ins); + if (chan[c.chan].wave<0) { + chan[c.chan].wave=0; + ws.changeWave1(chan[c.chan].wave); + } + ws.init(ins,64,63,chan[c.chan].insChanged); rWrite(0x4080,0x80|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; @@ -268,6 +270,7 @@ int DivPlatformFDS::dispatch(DivCommand c) { 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: @@ -289,7 +292,7 @@ int DivPlatformFDS::dispatch(DivCommand c) { case DIV_CMD_WAVE: if (chan[c.chan].wave!=c.value) { chan[c.chan].wave=c.value; - updateWave(); + ws.changeWave1(chan[c.chan].wave); } break; case DIV_CMD_FDS_MOD_DEPTH: @@ -406,6 +409,8 @@ void DivPlatformFDS::reset() { for (int i=0; i<1; i++) { chan[i]=DivPlatformFDS::Channel(); } + ws.setEngine(parent); + ws.init(NULL,64,63,false); if (dumpWrites) { addWrite(0xffffffff,0); } diff --git a/src/engine/platform/fds.h b/src/engine/platform/fds.h index d9f145e1c..d8d588a23 100644 --- a/src/engine/platform/fds.h +++ b/src/engine/platform/fds.h @@ -22,6 +22,7 @@ #include "../dispatch.h" #include "../macroInt.h" +#include "../waveSynth.h" class DivPlatformFDS: public DivDispatch { struct Channel { @@ -59,6 +60,7 @@ class DivPlatformFDS: public DivDispatch { }; Channel chan[1]; bool isMuted[1]; + DivWaveSynth ws; unsigned char apuType; struct _fds* fds; unsigned char regPool[128]; diff --git a/src/engine/platform/gb.cpp b/src/engine/platform/gb.cpp index d1d580c84..263e8e2ba 100644 --- a/src/engine/platform/gb.cpp +++ b/src/engine/platform/gb.cpp @@ -91,20 +91,11 @@ void DivPlatformGB::acquire(short* bufL, short* bufR, size_t start, size_t len) } void DivPlatformGB::updateWave() { - DivWavetable* wt=parent->getWave(chan[2].wave); rWrite(0x1a,0); for (int i=0; i<16; i++) { - if (wt->max<1 || wt->len<1) { - rWrite(0x30+i,0); - } else { - int nibble1=15-((wt->data[(i*2)*wt->len/32]*15)/wt->max); - int nibble2=15-((wt->data[(1+i*2)*wt->len/32]*15)/wt->max); - if (nibble1<0) nibble1=0; - if (nibble1>15) nibble1=15; - if (nibble2<0) nibble2=0; - if (nibble2>15) nibble2=15; - rWrite(0x30+i,(nibble1<<4)|nibble2); - } + int nibble1=15-ws.output[i<<1]; + int nibble2=15-ws.output[1+(i<<1)]; + rWrite(0x30+i,(nibble1<<4)|nibble2); } } @@ -194,10 +185,16 @@ void DivPlatformGB::tick() { } } } - if (chan[i].std.hadWave) { - if (chan[i].wave!=chan[i].std.wave) { + if (i==2 && chan[i].std.hadWave) { + if (chan[i].wave!=chan[i].std.wave || ws.activeChanged()) { chan[i].wave=chan[i].std.wave; - if (i==2) { + ws.changeWave1(chan[i].wave); + if (!chan[i].keyOff) chan[i].keyOn=true; + } + } + if (i==2) { + if (chan[i].active) { + if (ws.tick()) { updateWave(); if (!chan[i].keyOff) chan[i].keyOn=true; } @@ -222,10 +219,6 @@ void DivPlatformGB::tick() { } if (chan[i].keyOn) { if (i==2) { // wave - if (chan[i].wave<0) { - chan[i].wave=0; - updateWave(); - } rWrite(16+i*5,0x80); rWrite(16+i*5+2,gbVolMap[chan[i].vol]); } else { @@ -261,7 +254,8 @@ void DivPlatformGB::muteChannel(int ch, bool mute) { int DivPlatformGB::dispatch(DivCommand c) { switch (c.cmd) { - case DIV_CMD_NOTE_ON: + case DIV_CMD_NOTE_ON: { + DivInstrument* ins=parent->getIns(chan[c.chan].ins); if (c.value!=DIV_NOTE_NULL) { if (c.chan==3) { // noise chan[c.chan].baseFreq=c.value; @@ -273,8 +267,17 @@ int DivPlatformGB::dispatch(DivCommand c) { } chan[c.chan].active=true; chan[c.chan].keyOn=true; - chan[c.chan].std.init(parent->getIns(chan[c.chan].ins)); + chan[c.chan].std.init(ins); + if (c.chan==2) { + if (chan[c.chan].wave<0) { + chan[c.chan].wave=0; + ws.changeWave1(chan[c.chan].wave); + } + ws.init(ins,32,15,chan[c.chan].insChanged); + } + chan[c.chan].insChanged=false; break; + } case DIV_CMD_NOTE_OFF: chan[c.chan].active=false; chan[c.chan].keyOff=true; @@ -287,6 +290,7 @@ int DivPlatformGB::dispatch(DivCommand c) { 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; if (c.chan!=2) { DivInstrument* ins=parent->getIns(chan[c.chan].ins); chan[c.chan].vol=ins->gb.envVol; @@ -312,7 +316,7 @@ int DivPlatformGB::dispatch(DivCommand c) { case DIV_CMD_WAVE: if (c.chan!=2) break; chan[c.chan].wave=c.value; - updateWave(); + ws.changeWave1(chan[c.chan].wave); chan[c.chan].keyOn=true; break; case DIV_CMD_NOTE_PORTA: { @@ -415,6 +419,8 @@ void DivPlatformGB::reset() { for (int i=0; i<4; i++) { chan[i]=DivPlatformGB::Channel(); } + ws.setEngine(parent); + ws.init(NULL,32,15,false); if (dumpWrites) { addWrite(0xffffffff,0); } @@ -445,6 +451,7 @@ void DivPlatformGB::notifyInsChange(int ins) { void DivPlatformGB::notifyWaveChange(int wave) { if (chan[2].wave==wave) { + ws.changeWave1(wave); updateWave(); if (!chan[2].keyOff) chan[2].keyOn=true; } diff --git a/src/engine/platform/gb.h b/src/engine/platform/gb.h index 04e36ad3c..5a345e45d 100644 --- a/src/engine/platform/gb.h +++ b/src/engine/platform/gb.h @@ -22,6 +22,7 @@ #include "../dispatch.h" #include "../macroInt.h" +#include "../waveSynth.h" #include "sound/gb/gb.h" class DivPlatformGB: public DivDispatch { @@ -53,6 +54,7 @@ class DivPlatformGB: public DivDispatch { Channel chan[4]; bool isMuted[4]; unsigned char lastPan; + DivWaveSynth ws; GB_gameboy_t* gb; unsigned char regPool[128]; diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index b0894fe70..fcba9f80c 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -947,7 +947,7 @@ void DivPlatformOPL::reset() { drumVol[3]=0; drumVol[4]=0; - if (oplType==1) { // disable waveforms + if (oplType==2) { // enable OPL2 waveforms immWrite(0x01,0x20); } diff --git a/src/engine/platform/pce.cpp b/src/engine/platform/pce.cpp index bf7d0d8db..aa78aa625 100644 --- a/src/engine/platform/pce.cpp +++ b/src/engine/platform/pce.cpp @@ -131,18 +131,10 @@ void DivPlatformPCE::acquire(short* bufL, short* bufR, size_t start, size_t len) } void DivPlatformPCE::updateWave(int ch) { - DivWavetable* wt=parent->getWave(chan[ch].wave); chWrite(ch,0x04,0x5f); chWrite(ch,0x04,0x1f); for (int i=0; i<32; i++) { - if (wt->max<1 || wt->len<1) { - chWrite(ch,0x06,0); - } else { - int data=wt->data[i*wt->len/32]*31/wt->max; - if (data<0) data=0; - if (data>31) data=31; - chWrite(ch,0x06,data); - } + chWrite(ch,0x06,chan[ch].ws.output[i]); } if (chan[ch].active) { chWrite(ch,0x04,0x80|chan[ch].outVol); @@ -198,12 +190,17 @@ void DivPlatformPCE::tick() { } } if (chan[i].std.hadWave && !chan[i].pcm) { - if (chan[i].wave!=chan[i].std.wave) { + if (chan[i].wave!=chan[i].std.wave || chan[i].ws.activeChanged()) { chan[i].wave=chan[i].std.wave; - updateWave(i); + chan[i].ws.changeWave1(chan[i].wave); if (!chan[i].keyOff) chan[i].keyOn=true; } } + if (chan[i].active) { + if (chan[i].ws.tick()) { + updateWave(i); + } + } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { //DivInstrument* ins=parent->getIns(chan[i].ins); chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true); @@ -224,10 +221,6 @@ void DivPlatformPCE::tick() { chWrite(i,0x02,chan[i].freq&0xff); chWrite(i,0x03,chan[i].freq>>8); if (chan[i].keyOn) { - if (chan[i].wave<0) { - chan[i].wave=0; - updateWave(i); - } //rWrite(16+i*5,0x80); //chWrite(i,0x04,0x80|chan[i].vol); } @@ -310,6 +303,12 @@ int DivPlatformPCE::dispatch(DivCommand c) { chan[c.chan].keyOn=true; chWrite(c.chan,0x04,0x80|chan[c.chan].vol); chan[c.chan].std.init(ins); + if (chan[c.chan].wave<0) { + chan[c.chan].wave=0; + chan[c.chan].ws.changeWave1(chan[c.chan].wave); + } + chan[c.chan].ws.init(ins,32,31,chan[c.chan].insChanged); + chan[c.chan].insChanged=false; break; } case DIV_CMD_NOTE_OFF: @@ -327,6 +326,7 @@ int DivPlatformPCE::dispatch(DivCommand c) { 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: @@ -350,7 +350,7 @@ int DivPlatformPCE::dispatch(DivCommand c) { break; case DIV_CMD_WAVE: chan[c.chan].wave=c.value; - updateWave(c.chan); + chan[c.chan].ws.changeWave1(chan[c.chan].wave); chan[c.chan].keyOn=true; break; case DIV_CMD_PCE_LFO_MODE: @@ -462,6 +462,8 @@ void DivPlatformPCE::reset() { memset(regPool,0,128); for (int i=0; i<6; i++) { chan[i]=DivPlatformPCE::Channel(); + chan[i].ws.setEngine(parent); + chan[i].ws.init(NULL,32,31,false); } if (dumpWrites) { addWrite(0xffffffff,0); @@ -499,6 +501,7 @@ bool DivPlatformPCE::keyOffAffectsArp(int ch) { void DivPlatformPCE::notifyWaveChange(int wave) { for (int i=0; i<6; i++) { if (chan[i].wave==wave) { + chan[i].ws.changeWave1(wave); updateWave(i); } } diff --git a/src/engine/platform/pce.h b/src/engine/platform/pce.h index 2a5bd4fba..2e8614ba8 100644 --- a/src/engine/platform/pce.h +++ b/src/engine/platform/pce.h @@ -23,6 +23,7 @@ #include "../dispatch.h" #include #include "../macroInt.h" +#include "../waveSynth.h" #include "sound/pce_psg.h" class DivPlatformPCE: public DivDispatch { @@ -35,6 +36,7 @@ class DivPlatformPCE: public DivDispatch { bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, noise, pcm, furnaceDac; signed char vol, outVol, wave; DivMacroInt std; + DivWaveSynth ws; Channel(): freq(0), baseFreq(0), diff --git a/src/engine/platform/swan.cpp b/src/engine/platform/swan.cpp index 0f32e9a08..6966a2d1c 100644 --- a/src/engine/platform/swan.cpp +++ b/src/engine/platform/swan.cpp @@ -111,22 +111,11 @@ void DivPlatformSwan::acquire(short* bufL, short* bufR, size_t start, size_t len } void DivPlatformSwan::updateWave(int ch) { - DivWavetable* wt=parent->getWave(chan[ch].wave); unsigned char addr=0x40+ch*16; - if (wt->max<1 || wt->len<1) { - for (int i=0; i<16; i++) { - rWrite(addr+i,0); - } - } else { - for (int i=0; i<16; i++) { - int nibble1=(wt->data[(i*2)*wt->len/32]*15)/wt->max; - int nibble2=(wt->data[(1+i*2)*wt->len/32]*15)/wt->max; - if (nibble1<0) nibble1=0; - if (nibble1>15) nibble1=15; - if (nibble2<0) nibble2=0; - if (nibble2>15) nibble2=15; - rWrite(addr+i,nibble1|(nibble2<<4)); - } + for (int i=0; i<16; i++) { + int nibble1=chan[ch].ws.output[i<<1]; + int nibble2=chan[ch].ws.output[1+(i<<1)]; + rWrite(addr+i,nibble1|(nibble2<<4)); } } @@ -179,13 +168,16 @@ void DivPlatformSwan::tick() { } } if (chan[i].std.hadWave && !(i==1 && pcm)) { - if (chan[i].wave!=chan[i].std.wave) { + if (chan[i].wave!=chan[i].std.wave || chan[i].ws.activeChanged()) { chan[i].wave=chan[i].std.wave; - updateWave(i); + chan[i].ws.changeWave1(chan[i].wave); } } if (chan[i].active) { sndCtrl|=(1<calcFreq(chan[i].baseFreq,chan[i].pitch,true); @@ -211,10 +203,6 @@ void DivPlatformSwan::tick() { if (!chan[i].std.willVol) { calcAndWriteOutVol(i,15); } - if (chan[i].wave<0) { - chan[i].wave=0; - updateWave(i); - } chan[i].keyOn=false; } if (chan[i].keyOff) { @@ -300,6 +288,12 @@ int DivPlatformSwan::dispatch(DivCommand c) { chan[c.chan].active=true; chan[c.chan].keyOn=true; chan[c.chan].std.init(ins); + if (chan[c.chan].wave<0) { + chan[c.chan].wave=0; + chan[c.chan].ws.changeWave1(chan[c.chan].wave); + } + chan[c.chan].ws.init(ins,32,15,chan[c.chan].insChanged); + chan[c.chan].insChanged=false; break; } case DIV_CMD_NOTE_OFF: @@ -319,6 +313,7 @@ int DivPlatformSwan::dispatch(DivCommand c) { 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: @@ -338,7 +333,7 @@ int DivPlatformSwan::dispatch(DivCommand c) { break; case DIV_CMD_WAVE: chan[c.chan].wave=c.value; - updateWave(c.chan); + chan[c.chan].ws.changeWave1(chan[c.chan].wave); chan[c.chan].keyOn=true; break; case DIV_CMD_WS_SWEEP_TIME: @@ -458,6 +453,8 @@ void DivPlatformSwan::reset() { chan[i]=Channel(); chan[i].vol=15; chan[i].pan=0xff; + chan[i].ws.setEngine(parent); + chan[i].ws.init(NULL,32,15,false); rWrite(0x08+i,0xff); } if (dumpWrites) { @@ -484,6 +481,7 @@ bool DivPlatformSwan::isStereo() { void DivPlatformSwan::notifyWaveChange(int wave) { for (int i=0; i<4; i++) { if (chan[i].wave==wave) { + chan[i].ws.changeWave1(wave); updateWave(i); } } diff --git a/src/engine/platform/swan.h b/src/engine/platform/swan.h index 47470f57b..610884b00 100644 --- a/src/engine/platform/swan.h +++ b/src/engine/platform/swan.h @@ -22,6 +22,7 @@ #include "../dispatch.h" #include "../macroInt.h" +#include "../waveSynth.h" #include "sound/swan.h" #include @@ -32,6 +33,7 @@ class DivPlatformSwan: public DivDispatch { bool active, insChanged, freqChanged, keyOn, keyOff, inPorta; int vol, outVol, wave; DivMacroInt std; + DivWaveSynth ws; Channel(): freq(0), baseFreq(0), diff --git a/src/engine/platform/x1_010.cpp b/src/engine/platform/x1_010.cpp index 6834986f6..68f6db297 100644 --- a/src/engine/platform/x1_010.cpp +++ b/src/engine/platform/x1_010.cpp @@ -274,19 +274,12 @@ double DivPlatformX1_010::NoteX1_010(int ch, int note) { } void DivPlatformX1_010::updateWave(int ch) { - DivWavetable* wt=parent->getWave(chan[ch].wave); if (chan[ch].active) { - chan[ch].waveBank ^= 1; + chan[ch].waveBank^=1; } for (int i=0; i<128; i++) { - if (wt->max<1 || wt->len<1) { - waveWrite(ch,i,0); - } else { - int data=wt->data[i*wt->len/128]*255/wt->max; - if (data<0) data=0; - if (data>255) data=255; - waveWrite(ch,i,data); - } + int data=chan[ch].ws.output[i]; + waveWrite(ch,i,data); } if (!chan[ch].pcm) { chWrite(ch,1,(chan[ch].waveBank<<4)|(ch&0xf)); @@ -371,10 +364,10 @@ void DivPlatformX1_010::tick() { } } if (chan[i].std.hadWave && !chan[i].pcm) { - if (chan[i].wave!=chan[i].std.wave) { + if (chan[i].wave!=chan[i].std.wave || chan[i].ws.activeChanged()) { chan[i].wave=chan[i].std.wave; if (!chan[i].pcm) { - updateWave(i); + chan[i].ws.changeWave1(chan[i].wave); if (!chan[i].keyOff) chan[i].keyOn=true; } } @@ -458,6 +451,11 @@ void DivPlatformX1_010::tick() { if (!chan[i].std.willEx3) chan[i].autoEnvNum=1; } } + if (chan[i].active) { + if (chan[i].ws.tick()) { + updateWave(i); + } + } if (chan[i].envChanged) { chan[i].lvol=isMuted[i]?0:(((chan[i].outVol&0xf)*((chan[i].pan>>4)&0xf))/15); chan[i].rvol=isMuted[i]?0:(((chan[i].outVol&0xf)*((chan[i].pan>>0)&0xf))/15); @@ -575,6 +573,12 @@ int DivPlatformX1_010::dispatch(DivCommand c) { chan[c.chan].keyOn=true; chan[c.chan].envChanged=true; chan[c.chan].std.init(ins); + if (chan[c.chan].wave<0) { + chan[c.chan].wave=0; + chan[c.chan].ws.changeWave1(chan[c.chan].wave); + } + chan[c.chan].ws.init(ins,128,255,chan[c.chan].insChanged); + chan[c.chan].insChanged=false; refreshControl(c.chan); break; } @@ -591,6 +595,7 @@ int DivPlatformX1_010::dispatch(DivCommand c) { 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: @@ -618,7 +623,7 @@ int DivPlatformX1_010::dispatch(DivCommand c) { break; case DIV_CMD_WAVE: chan[c.chan].wave=c.value; - updateWave(c.chan); + chan[c.chan].ws.changeWave1(chan[c.chan].wave); chan[c.chan].keyOn=true; break; case DIV_CMD_X1_010_ENVELOPE_SHAPE: @@ -813,6 +818,8 @@ void DivPlatformX1_010::reset() { for (int i=0; i<16; i++) { chan[i]=DivPlatformX1_010::Channel(); chan[i].reset(); + chan[i].ws.setEngine(parent); + chan[i].ws.init(NULL,128,255,false); } x1_010->reset(); sampleBank=0; @@ -833,6 +840,7 @@ bool DivPlatformX1_010::keyOffAffectsArp(int ch) { void DivPlatformX1_010::notifyWaveChange(int wave) { for (int i=0; i<16; i++) { if (chan[i].wave==wave) { + chan[i].ws.changeWave1(wave); updateWave(i); } } diff --git a/src/engine/platform/x1_010.h b/src/engine/platform/x1_010.h index 389fa7bf7..2bfb78cc9 100644 --- a/src/engine/platform/x1_010.h +++ b/src/engine/platform/x1_010.h @@ -23,6 +23,7 @@ #include "../dispatch.h" #include "../engine.h" #include "../macroInt.h" +#include "../waveSynth.h" #include "sound/x1_010/x1_010.hpp" class DivX1_010Interface: public x1_010_mem_intf { @@ -86,6 +87,7 @@ class DivPlatformX1_010: public DivDispatch { unsigned char waveBank; Envelope env; DivMacroInt std; + DivWaveSynth ws; void reset() { freq = baseFreq = pitch = note = 0; wave = sample = ins = -1; diff --git a/src/engine/song.h b/src/engine/song.h index 7dca56b47..438c22143 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -102,6 +102,8 @@ struct DivSong { // version number used for saving the song. // Furnace will save using the latest possible version, // known version numbers: + // - 26: v1.1.3 + // - changes height of FDS wave to 6-bit (it was 4-bit before) // - 25: v1.1 // - adds pattern names (in a rather odd way) // - introduces SMS+OPLL system diff --git a/src/engine/waveSynth.cpp b/src/engine/waveSynth.cpp index ca3f342c7..d658be178 100644 --- a/src/engine/waveSynth.cpp +++ b/src/engine/waveSynth.cpp @@ -1,5 +1,99 @@ #include "waveSynth.h" +#include "engine.h" +#include "instrument.h" + +bool DivWaveSynth::activeChanged() { + if (activeChangedB) { + activeChangedB=false; + return true; + } + return false; +} bool DivWaveSynth::tick() { - return false; + bool updated=first; + first=false; + if (!state.enabled) return updated; + + if (--divCounter<=0) { + // run effect + switch (state.effect) { + case DIV_WS_INVERT: + for (int i=0; i<=state.speed; i++) { + output[pos]=height-output[pos]; + if (++pos>=width) pos=0; + } + updated=true; + break; + } + divCounter=state.rateDivider; + } + + return updated; +} + +void DivWaveSynth::changeWave1(int num) { + DivWavetable* w1=e->getWave(num); + for (int i=0; imax<1 || w1->len<1) { + wave1[i]=0; + output[i]=0; + } else { + int data=w1->data[i*w1->len/width]*height/w1->max; + if (data<0) data=0; + if (data>height) data=height; + wave1[i]=data; + output[i]=data; + } + } + first=true; +} + +void DivWaveSynth::changeWave2(int num) { + DivWavetable* w2=e->getWave(num); + for (int i=0; imax<1 || w2->len<1) { + wave2[i]=0; + } else { + int data=w2->data[i*w2->len/width]*height/w2->max; + if (data<0) data=0; + if (data>height) data=height; + wave2[i]=data; + } + } + first=true; +} + +void DivWaveSynth::setEngine(DivEngine* engine) { + e=engine; +} + +void DivWaveSynth::init(DivInstrument* which, int w, int h, bool insChanged) { + width=w; + height=h; + if (width<0) width=0; + if (width>256) width=256; + if (e==NULL) return; + if (which==NULL) { + if (state.enabled) activeChangedB=true; + state=DivInstrumentWaveSynth(); + return; + } + if (!which->ws.enabled) { + if (state.enabled) activeChangedB=true; + state=DivInstrumentWaveSynth(); + return; + } else { + if (!state.enabled) activeChangedB=true; + } + state=which->ws; + if (insChanged || !state.global) { + pos=0; + stage=0; + divCounter=1+state.rateDivider; + first=true; + + changeWave1(state.wave1); + changeWave2(state.wave2); + } } \ No newline at end of file diff --git a/src/engine/waveSynth.h b/src/engine/waveSynth.h index 5e5fd6540..70fb27389 100644 --- a/src/engine/waveSynth.h +++ b/src/engine/waveSynth.h @@ -23,22 +23,60 @@ #include "instrument.h" #include "wavetable.h" +class DivEngine; + class DivWaveSynth { - DivInstrument* ins; - int pos, stage, divCounter; - int output[256]; + DivEngine* e; + DivInstrumentWaveSynth state; + int pos, stage, divCounter, width, height; + bool first, activeChangedB; + unsigned char wave1[256]; + unsigned char wave2[256]; public: + /** + * the output. + */ + int output[256]; + /** + * check whether the "active" status has changed. + * @return truth. + */ + bool activeChanged(); /** * tick this DivWaveSynth. * @return whether the wave has changed. */ bool tick(); - void init(DivInstrument* ins); + /** + * change the first wave. + * @param num wavetable number. + */ + void changeWave1(int num); + /** + * change the second wave. + * @param num wavetable number. + */ + void changeWave2(int num); + /** + * initialize this DivWaveSynth. + * @param which the instrument. + * @param width the system's wave width. + * @param height the system's wave height. + * @param insChanged whether the instrument has changed. + */ + void init(DivInstrument* which, int width, int height, bool insChanged=false); + void setEngine(DivEngine* engine); DivWaveSynth(): - ins(NULL), + e(NULL), pos(0), stage(0), - divCounter(0) { + divCounter(0), + width(32), + height(31), + first(false), + activeChangedB(false) { + memset(wave1,0,sizeof(int)*256); + memset(wave2,0,sizeof(int)*256); memset(output,0,sizeof(int)*256); } }; diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 6c35f6c37..bae782ff4 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1016,11 +1016,17 @@ void FurnaceGUI::valueInput(int num, bool direct, int target) { if (!settings.effectCursorDir) { editAdvance(); } else { - if (cursor.xFine&1) { - cursor.xFine++; + if (settings.effectCursorDir==2) { + if (++cursor.xFine>=(3+(e->song.pat[cursor.xCoarse].effectRows*2))) { + cursor.xFine=3; + } } else { - editAdvance(); - cursor.xFine--; + if (cursor.xFine&1) { + cursor.xFine++; + } else { + editAdvance(); + cursor.xFine--; + } } } } @@ -1300,8 +1306,8 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { hasOpened=fileDialog->openSave( "Save File", {"Furnace song", "*.fur", - "DefleMask 1.1 module", "*.dmf"}, - "Furnace song{.fur},DefleMask 1.1 module{.dmf}", + "DefleMask 1.1.3 module", "*.dmf"}, + "Furnace song{.fur},DefleMask 1.1.3 module{.dmf}", workingDirSong, dpiScale ); @@ -2714,7 +2720,7 @@ bool FurnaceGUI::loop() { showError(fmt::sprintf("Error while saving file! (%s)",lastError)); } } else { - if (save(copyOfName,25)>0) { + if (save(copyOfName,26)>0) { showError(fmt::sprintf("Error while saving file! (%s)",lastError)); } } diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index abc4af172..14ca65b40 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -157,6 +157,25 @@ const int orderedOps[4]={ 0, 2, 1, 3 }; +const char* singleWSEffects[6]={ + "None", + "Invert", + "Add", + "Subtract", + "Average", + "Phase", +}; + +const char* dualWSEffects[7]={ + "None (dual)", + "Wipe", + "Fade", + "Wipe (ping-pong)", + "Overlay", + "Negative Overlay", + "Phase (dual)", +}; + String macroHoverNote(int id, float val) { if (val<-60 || val>=120) return "???"; return fmt::sprintf("%d: %s",id,noteNames[(int)val+60]); @@ -2301,6 +2320,109 @@ void FurnaceGUI::drawInsEdit() { } ImGui::EndTabItem(); } + if (ins->type==DIV_INS_GB || + ins->type==DIV_INS_AMIGA || + ins->type==DIV_INS_X1_010 || + ins->type==DIV_INS_N163 || + ins->type==DIV_INS_FDS || + ins->type==DIV_INS_SWAN || + ins->type==DIV_INS_PCE || + ins->type==DIV_INS_SCC) { + if (ImGui::BeginTabItem("Wavetable")) { + ImGui::Checkbox("Enable synthesizer",&ins->ws.enabled); + ImGui::SameLine(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ins->ws.effect&0x80) { + if ((ins->ws.effect&0x7f)>=DIV_WS_DUAL_MAX) { + ins->ws.effect=0; + } + } else { + if ((ins->ws.effect&0x7f)>=DIV_WS_SINGLE_MAX) { + ins->ws.effect=0; + } + } + if (ImGui::BeginCombo("##WSEffect",(ins->ws.effect&0x80)?dualWSEffects[ins->ws.effect&0x7f]:singleWSEffects[ins->ws.effect&0x7f])) { + ImGui::Text("Single-waveform"); + ImGui::Indent(); + for (int i=0; iws.effect=i; + } + } + ImGui::Unindent(); + ImGui::Text("Dual-waveform"); + ImGui::Indent(); + for (int i=129; iws.effect=i; + } + } + ImGui::Unindent(); + ImGui::EndCombo(); + } + if (ImGui::BeginTable("WSPreview",2)) { + DivWavetable* wave1=e->getWave(ins->ws.wave1); + DivWavetable* wave2=e->getWave(ins->ws.wave2); + float wavePreview1[256]; + float wavePreview2[256]; + for (int i=0; ilen; i++) { + if (wave1->data[i]>wave1->max) { + wavePreview1[i]=wave1->max; + } else { + wavePreview1[i]=wave1->data[i]; + } + } + for (int i=0; ilen; i++) { + if (wave2->data[i]>wave2->max) { + wavePreview2[i]=wave2->max; + } else { + wavePreview2[i]=wave2->data[i]; + } + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImVec2 size1=ImVec2(ImGui::GetContentRegionAvail().x,64.0f*dpiScale); + PlotNoLerp("##WaveformP1",wavePreview1,wave1->len+1,0,NULL,0,wave1->max,size1); + ImGui::TableNextColumn(); + ImVec2 size2=ImVec2(ImGui::GetContentRegionAvail().x,64.0f*dpiScale); + PlotNoLerp("##WaveformP2",wavePreview2,wave2->len+1,0,NULL,0,wave2->max,size2); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Wave 1"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::InputInt("##SelWave1",&ins->ws.wave1,1,4)) { + if (ins->ws.wave1<0) ins->ws.wave1=0; + if (ins->ws.wave1>=(int)e->song.wave.size()) ins->ws.wave1=e->song.wave.size()-1; + } + ImGui::TableNextColumn(); + ImGui::Text("Wave 2"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::InputInt("##SelWave2",&ins->ws.wave2,1,4)) { + if (ins->ws.wave2<0) ins->ws.wave2=0; + if (ins->ws.wave2>=(int)e->song.wave.size()) ins->ws.wave2=e->song.wave.size()-1; + } + ImGui::EndTable(); + } + + ImGui::InputScalar("Update Rate",ImGuiDataType_U8,&ins->ws.rateDivider,&_ONE,&_SEVEN); + int speed=ins->ws.speed+1; + if (ImGui::InputInt("Speed",&speed,1,16)) { + if (speed<1) speed=1; + if (speed>256) speed=256; + ins->ws.speed=speed-1; + } + + ImGui::InputScalar("Amount",ImGuiDataType_U8,&ins->ws.param1,&_ONE,&_SEVEN); + + ImGui::Checkbox("Global",&ins->ws.global); + + ImGui::EndTabItem(); + } + } if (ImGui::BeginTabItem("Macros")) { float asFloat[256]; int asInt[256]; diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 30fe2141d..1cbd6a06e 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -250,11 +250,6 @@ void FurnaceGUI::drawSettings() { settings.stepOnInsert=stepOnInsertB; } - bool effectCursorDirB=settings.effectCursorDir; - if (ImGui::Checkbox("Move cursor to effect value on effect input",&effectCursorDirB)) { - settings.effectCursorDir=effectCursorDirB; - } - bool cursorPastePosB=settings.cursorPastePos; if (ImGui::Checkbox("Move cursor to end of clipboard content when pasting",&cursorPastePosB)) { settings.cursorPastePos=cursorPastePosB; @@ -315,6 +310,17 @@ void FurnaceGUI::drawSettings() { settings.scrollStep=1; } + ImGui::Text("Effect input cursor behavior:"); + if (ImGui::RadioButton("Move down##eicb0",settings.effectCursorDir==0)) { + settings.effectCursorDir=0; + } + if (ImGui::RadioButton("Move to effect value (otherwise move down)##eicb1",settings.effectCursorDir==1)) { + settings.effectCursorDir=1; + } + if (ImGui::RadioButton("Move to effect value/next effect and wrap around##eicb2",settings.effectCursorDir==2)) { + settings.effectCursorDir=2; + } + ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Audio/MIDI")) { @@ -1503,7 +1509,7 @@ void FurnaceGUI::syncSettings() { clampSetting(settings.loadJapanese,0,1); clampSetting(settings.fmLayout,0,3); clampSetting(settings.susPosition,0,1); - clampSetting(settings.effectCursorDir,0,1); + clampSetting(settings.effectCursorDir,0,2); clampSetting(settings.cursorPastePos,0,1); clampSetting(settings.titleBarInfo,0,3); clampSetting(settings.titleBarSys,0,1);