From 96715ed88c3722db9a653d4617d9716982a1deff Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 15 Apr 2022 05:37:23 -0500 Subject: [PATCH] add experimental low-latency mode do not use (yet)! --- src/engine/engine.cpp | 1 + src/engine/engine.h | 7 +- src/engine/macroInt.cpp | 18 ++- src/engine/macroInt.h | 12 ++ src/engine/platform/amiga.cpp | 1 + src/engine/platform/arcade.cpp | 1 + src/engine/platform/ay.cpp | 1 + src/engine/platform/ay8930.cpp | 1 + src/engine/platform/bubsyswsg.cpp | 1 + src/engine/platform/c64.cpp | 1 + src/engine/platform/fds.cpp | 1 + src/engine/platform/gb.cpp | 1 + src/engine/platform/genesis.cpp | 1 + src/engine/platform/lynx.cpp | 6 +- src/engine/platform/mmc5.cpp | 1 + src/engine/platform/n163.cpp | 1 + src/engine/platform/nes.cpp | 1 + src/engine/platform/opl.cpp | 1 + src/engine/platform/opll.cpp | 1 + src/engine/platform/pce.cpp | 1 + src/engine/platform/pcspkr.cpp | 1 + src/engine/platform/pet.cpp | 1 + src/engine/platform/qsound.cpp | 1 + src/engine/platform/saa.cpp | 1 + src/engine/platform/segapcm.cpp | 1 + src/engine/platform/sms.cpp | 1 + src/engine/platform/swan.cpp | 1 + src/engine/platform/tia.cpp | 1 + src/engine/platform/tx81z.cpp | 1 + src/engine/platform/vera.cpp | 1 + src/engine/platform/vic20.cpp | 1 + src/engine/platform/vrc6.cpp | 1 + src/engine/platform/x1_010.cpp | 1 + src/engine/platform/ym2610.cpp | 1 + src/engine/platform/ym2610b.cpp | 1 + src/engine/playback.cpp | 234 +++++++++++++++--------------- src/gui/gui.h | 2 + src/gui/settings.cpp | 11 ++ 38 files changed, 201 insertions(+), 120 deletions(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index ee4b4b20e..81d7947a7 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -2232,6 +2232,7 @@ bool DivEngine::initAudioBackend() { lowQuality=getConfInt("audioQuality",0); forceMono=getConfInt("forceMono",0); + lowLatency=getConfInt("lowLatency",0); metroVol=(float)(getConfInt("metroVol",100))/100.0f; if (metroVol<0.0f) metroVol=0.0f; if (metroVol>2.0f) metroVol=2.0f; diff --git a/src/engine/engine.h b/src/engine/engine.h index 0613d3edd..6bcaa38c9 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -202,8 +202,9 @@ class DivEngine { bool firstTick; bool skipping; bool midiIsDirect; + bool lowLatency; int softLockCount; - int ticks, curRow, curOrder, remainingLoops, nextSpeed; + int subticks, ticks, curRow, curOrder, remainingLoops, nextSpeed; double divider; int cycles; double clockDrift; @@ -303,6 +304,7 @@ class DivEngine { float* oscBuf[2]; float oscSize; int oscReadPos, oscWritePos; + int tickMult; void runExportThread(); void nextBuf(float** in, float** out, int inChans, int outChans, unsigned int size); @@ -745,7 +747,9 @@ class DivEngine { firstTick(false), skipping(false), midiIsDirect(false), + lowLatency(false), softLockCount(0), + subticks(0), ticks(0), curRow(0), curOrder(0), @@ -789,6 +793,7 @@ class DivEngine { oscSize(1), oscReadPos(0), oscWritePos(0), + tickMult(1), adpcmAMem(NULL), adpcmAMemLen(0), adpcmBMem(NULL), diff --git a/src/engine/macroInt.cpp b/src/engine/macroInt.cpp index 21ca93a1a..ae6caf5df 100644 --- a/src/engine/macroInt.cpp +++ b/src/engine/macroInt.cpp @@ -19,6 +19,7 @@ #include "macroInt.h" #include "instrument.h" +#include "engine.h" void DivMacroStruct::doMacro(DivInstrumentMacro& source, bool released) { if (finished) { @@ -51,9 +52,16 @@ void DivMacroInt::next() { if (ins==NULL) return; // run macros // TODO: potentially get rid of list to avoid allocations - for (size_t i=0; idoMacro(*macroSource[i],released); + if (--subTick<=0) { + if (e==NULL) { + subTick=1; + } else { + subTick=e->tickMult; + } + for (size_t i=0; idoMacro(*macroSource[i],released); + } } } } @@ -62,6 +70,10 @@ void DivMacroInt::release() { released=true; } +void DivMacroInt::setEngine(DivEngine* eng) { + e=eng; +} + #define ADD_MACRO(m,s) \ macroList[macroListLen]=&m; \ macroSource[macroListLen++]=&s; diff --git a/src/engine/macroInt.h b/src/engine/macroInt.h index 5dc30aa06..886ecbe23 100644 --- a/src/engine/macroInt.h +++ b/src/engine/macroInt.h @@ -22,6 +22,8 @@ #include "instrument.h" +class DivEngine; + struct DivMacroStruct { int pos; int val; @@ -49,10 +51,12 @@ struct DivMacroStruct { }; class DivMacroInt { + DivEngine* e; DivInstrument* ins; DivMacroStruct* macroList[128]; DivInstrumentMacro* macroSource[128]; size_t macroListLen; + int subTick; bool released; public: // common macro @@ -102,6 +106,12 @@ class DivMacroInt { */ void next(); + /** + * set the engine. + * @param the engine + */ + void setEngine(DivEngine* eng); + /** * initialize the macro interpreter. * @param which an instrument, or NULL. @@ -115,8 +125,10 @@ class DivMacroInt { void notifyInsDeletion(DivInstrument* which); DivMacroInt(): + e(NULL), ins(NULL), macroListLen(0), + subTick(0), released(false), vol(), arp(), diff --git a/src/engine/platform/amiga.cpp b/src/engine/platform/amiga.cpp index d0b1f4417..b8419d197 100644 --- a/src/engine/platform/amiga.cpp +++ b/src/engine/platform/amiga.cpp @@ -414,6 +414,7 @@ void* DivPlatformAmiga::getChanState(int ch) { void DivPlatformAmiga::reset() { for (int i=0; i<4; i++) { chan[i]=DivPlatformAmiga::Channel(); + chan[i].std.setEngine(parent); chan[i].ws.setEngine(parent); chan[i].ws.init(NULL,32,255); filter[0][i]=0; diff --git a/src/engine/platform/arcade.cpp b/src/engine/platform/arcade.cpp index 3885619b1..a7ab53ec0 100644 --- a/src/engine/platform/arcade.cpp +++ b/src/engine/platform/arcade.cpp @@ -735,6 +735,7 @@ void DivPlatformArcade::reset() { } for (int i=0; i<8; i++) { chan[i]=DivPlatformArcade::Channel(); + chan[i].std.setEngine(parent); chan[i].vol=0x7f; chan[i].outVol=0x7f; } diff --git a/src/engine/platform/ay.cpp b/src/engine/platform/ay.cpp index f096b1e6c..e8a61b9ae 100644 --- a/src/engine/platform/ay.cpp +++ b/src/engine/platform/ay.cpp @@ -513,6 +513,7 @@ void DivPlatformAY8910::reset() { memset(regPool,0,16); for (int i=0; i<3; i++) { chan[i]=DivPlatformAY8910::Channel(); + chan[i].std.setEngine(parent); chan[i].vol=0x0f; } if (dumpWrites) { diff --git a/src/engine/platform/ay8930.cpp b/src/engine/platform/ay8930.cpp index 8237876b9..9f684a048 100644 --- a/src/engine/platform/ay8930.cpp +++ b/src/engine/platform/ay8930.cpp @@ -528,6 +528,7 @@ void DivPlatformAY8930::reset() { memset(regPool,0,32); for (int i=0; i<3; i++) { chan[i]=DivPlatformAY8930::Channel(); + chan[i].std.setEngine(parent); chan[i].vol=31; ayEnvPeriod[i]=0; ayEnvMode[i]=0; diff --git a/src/engine/platform/bubsyswsg.cpp b/src/engine/platform/bubsyswsg.cpp index 2c2bebbd3..4a80bf414 100644 --- a/src/engine/platform/bubsyswsg.cpp +++ b/src/engine/platform/bubsyswsg.cpp @@ -274,6 +274,7 @@ void DivPlatformBubSysWSG::reset() { memset(regPool,0,4*2); for (int i=0; i<2; i++) { chan[i]=DivPlatformBubSysWSG::Channel(); + chan[i].std.setEngine(parent); chan[i].ws.setEngine(parent); chan[i].ws.init(NULL,32,15,false); } diff --git a/src/engine/platform/c64.cpp b/src/engine/platform/c64.cpp index d230c1402..db5ab46a6 100644 --- a/src/engine/platform/c64.cpp +++ b/src/engine/platform/c64.cpp @@ -482,6 +482,7 @@ bool DivPlatformC64::getDCOffRequired() { void DivPlatformC64::reset() { for (int i=0; i<3; i++) { chan[i]=DivPlatformC64::Channel(); + chan[i].std.setEngine(parent); } sid.reset(); diff --git a/src/engine/platform/fds.cpp b/src/engine/platform/fds.cpp index e039e38a6..aefa37541 100644 --- a/src/engine/platform/fds.cpp +++ b/src/engine/platform/fds.cpp @@ -408,6 +408,7 @@ int DivPlatformFDS::getRegisterPoolSize() { void DivPlatformFDS::reset() { for (int i=0; i<1; i++) { chan[i]=DivPlatformFDS::Channel(); + chan[i].std.setEngine(parent); } ws.setEngine(parent); ws.init(NULL,64,63,false); diff --git a/src/engine/platform/gb.cpp b/src/engine/platform/gb.cpp index a84173189..8791e6d4c 100644 --- a/src/engine/platform/gb.cpp +++ b/src/engine/platform/gb.cpp @@ -424,6 +424,7 @@ int DivPlatformGB::getRegisterPoolSize() { void DivPlatformGB::reset() { for (int i=0; i<4; i++) { chan[i]=DivPlatformGB::Channel(); + chan[i].std.setEngine(parent); } ws.setEngine(parent); ws.init(NULL,32,15,false); diff --git a/src/engine/platform/genesis.cpp b/src/engine/platform/genesis.cpp index aacc31243..7a9a1d51a 100644 --- a/src/engine/platform/genesis.cpp +++ b/src/engine/platform/genesis.cpp @@ -827,6 +827,7 @@ void DivPlatformGenesis::reset() { } for (int i=0; i<10; i++) { chan[i]=DivPlatformGenesis::Channel(); + chan[i].std.setEngine(parent); chan[i].vol=0x7f; chan[i].outVol=0x7f; } diff --git a/src/engine/platform/lynx.cpp b/src/engine/platform/lynx.cpp index 9ef121594..6120233af 100644 --- a/src/engine/platform/lynx.cpp +++ b/src/engine/platform/lynx.cpp @@ -329,11 +329,11 @@ int DivPlatformLynx::getRegisterPoolSize() } void DivPlatformLynx::reset() { - - mikey = std::make_unique( rate ); + mikey=std::make_unique(rate); for (int i=0; i<4; i++) { - chan[i]= DivPlatformLynx::Channel(); + chan[i]=DivPlatformLynx::Channel(); + chan[i].std.setEngine(parent); } if (dumpWrites) { addWrite(0xffffffff,0); diff --git a/src/engine/platform/mmc5.cpp b/src/engine/platform/mmc5.cpp index e23e242bd..a9f9bb796 100644 --- a/src/engine/platform/mmc5.cpp +++ b/src/engine/platform/mmc5.cpp @@ -348,6 +348,7 @@ float DivPlatformMMC5::getPostAmp() { void DivPlatformMMC5::reset() { for (int i=0; i<3; i++) { chan[i]=DivPlatformMMC5::Channel(); + chan[i].std.setEngine(parent); } if (dumpWrites) { addWrite(0xffffffff,0); diff --git a/src/engine/platform/n163.cpp b/src/engine/platform/n163.cpp index 5de55ef0b..a66157d9e 100644 --- a/src/engine/platform/n163.cpp +++ b/src/engine/platform/n163.cpp @@ -625,6 +625,7 @@ void DivPlatformN163::reset() { while (!writes.empty()) writes.pop(); for (int i=0; i<8; i++) { chan[i]=DivPlatformN163::Channel(); + chan[i].std.setEngine(parent); chan[i].ws.setEngine(parent); chan[i].ws.init(NULL,32,15,false); } diff --git a/src/engine/platform/nes.cpp b/src/engine/platform/nes.cpp index 532f9f23c..4c65ce4f2 100644 --- a/src/engine/platform/nes.cpp +++ b/src/engine/platform/nes.cpp @@ -474,6 +474,7 @@ float DivPlatformNES::getPostAmp() { void DivPlatformNES::reset() { for (int i=0; i<5; i++) { chan[i]=DivPlatformNES::Channel(); + chan[i].std.setEngine(parent); } if (dumpWrites) { addWrite(0xffffffff,0); diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index 006afc472..add3815c5 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -934,6 +934,7 @@ void DivPlatformOPL::reset() { for (int i=0; ireset(); for (int i=0; i<14; i++) { chan[i]=DivPlatformYM2610::Channel(); + chan[i].std.setEngine(parent); } for (int i=0; i<4; i++) { chan[i].vol=0x7f; diff --git a/src/engine/platform/ym2610b.cpp b/src/engine/platform/ym2610b.cpp index ade5a9d80..130eb29a2 100644 --- a/src/engine/platform/ym2610b.cpp +++ b/src/engine/platform/ym2610b.cpp @@ -1131,6 +1131,7 @@ void DivPlatformYM2610B::reset() { fm->reset(); for (int i=0; i<16; i++) { chan[i]=DivPlatformYM2610B::Channel(); + chan[i].std.setEngine(parent); } for (int i=0; i<6; i++) { chan[i].vol=0x7f; diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 240209943..ea2213305 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -1472,8 +1472,13 @@ void DivEngine::nextRow() { bool DivEngine::nextTick(bool noAccum) { bool ret=false; if (divider<10) divider=10; + + if (lowLatency) { + tickMult=1000/divider; + if (tickMult<1) tickMult=1; + } - cycles=got.rate*pow(2,MASTER_CLOCK_PREC)/divider; + cycles=got.rate*pow(2,MASTER_CLOCK_PREC)/(divider*tickMult); clockDrift+=fmod(got.rate*pow(2,MASTER_CLOCK_PREC),(double)divider); if (clockDrift>=divider) { clockDrift-=divider; @@ -1499,130 +1504,133 @@ bool DivEngine::nextTick(bool noAccum) { } if (!freelance) { - if (stepPlay!=1) if (--ticks<=0) { - ret=endOfSong; - if (endOfSong) { - if (song.loopModality!=2) { - playSub(true); + if (--subticks<=0) { + subticks=tickMult; + if (stepPlay!=1) if (--ticks<=0) { + ret=endOfSong; + if (endOfSong) { + if (song.loopModality!=2) { + playSub(true); + } } + endOfSong=false; + if (stepPlay==2) stepPlay=1; + nextRow(); } - endOfSong=false; - if (stepPlay==2) stepPlay=1; - nextRow(); - } - // process stuff - for (int i=0; i0) { - if (--chan[i].rowDelay==0) { - processRow(i,true); + // process stuff + for (int i=0; i0) { + if (--chan[i].rowDelay==0) { + processRow(i,true); + } } - } - if (chan[i].retrigSpeed) { - if (--chan[i].retrigTick<0) { - chan[i].retrigTick=chan[i].retrigSpeed-1; - dispatchCmd(DivCommand(DIV_CMD_NOTE_ON,i,DIV_NOTE_NULL)); - keyHit[i]=true; + if (chan[i].retrigSpeed) { + if (--chan[i].retrigTick<0) { + chan[i].retrigTick=chan[i].retrigSpeed-1; + dispatchCmd(DivCommand(DIV_CMD_NOTE_ON,i,DIV_NOTE_NULL)); + keyHit[i]=true; + } } - } - if (!song.noSlidesOnFirstTick || !firstTick) { - if (chan[i].volSpeed!=0) { - chan[i].volume=(chan[i].volume&0xff)|(dispatchCmd(DivCommand(DIV_CMD_GET_VOLUME,i))<<8); - chan[i].volume+=chan[i].volSpeed; - if (chan[i].volume>chan[i].volMax) { - chan[i].volume=chan[i].volMax; - chan[i].volSpeed=0; - dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8)); - } else if (chan[i].volume<0) { - chan[i].volSpeed=0; - if (song.legacyVolumeSlides) { - chan[i].volume=chan[i].volMax+1; + if (!song.noSlidesOnFirstTick || !firstTick) { + if (chan[i].volSpeed!=0) { + chan[i].volume=(chan[i].volume&0xff)|(dispatchCmd(DivCommand(DIV_CMD_GET_VOLUME,i))<<8); + chan[i].volume+=chan[i].volSpeed; + if (chan[i].volume>chan[i].volMax) { + chan[i].volume=chan[i].volMax; + chan[i].volSpeed=0; + dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8)); + } else if (chan[i].volume<0) { + chan[i].volSpeed=0; + if (song.legacyVolumeSlides) { + chan[i].volume=chan[i].volMax+1; + } else { + chan[i].volume=0; + } + dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8)); } else { - chan[i].volume=0; + dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8)); } - dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8)); - } else { - dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8)); } } - } - if (chan[i].vibratoDepth>0) { - chan[i].vibratoPos+=chan[i].vibratoRate; - if (chan[i].vibratoPos>=64) chan[i].vibratoPos-=64; - switch (chan[i].vibratoDir) { - case 1: // up - dispatchCmd(DivCommand(DIV_CMD_PITCH,i,chan[i].pitch+(MAX(0,(chan[i].vibratoDepth*vibTable[chan[i].vibratoPos]*chan[i].vibratoFine)>>4)/15))); - break; - case 2: // down - dispatchCmd(DivCommand(DIV_CMD_PITCH,i,chan[i].pitch+(MIN(0,(chan[i].vibratoDepth*vibTable[chan[i].vibratoPos]*chan[i].vibratoFine)>>4)/15))); - break; - default: // both - dispatchCmd(DivCommand(DIV_CMD_PITCH,i,chan[i].pitch+(((chan[i].vibratoDepth*vibTable[chan[i].vibratoPos]*chan[i].vibratoFine)>>4)/15))); - break; - } - } - if (!song.noSlidesOnFirstTick || !firstTick) { - if ((chan[i].keyOn || chan[i].keyOff) && chan[i].portaSpeed>0) { - if (dispatchCmd(DivCommand(DIV_CMD_NOTE_PORTA,i,chan[i].portaSpeed,chan[i].portaNote))==2 && chan[i].portaStop && song.targetResetsSlides) { - chan[i].portaSpeed=0; - chan[i].oldNote=chan[i].note; - chan[i].note=chan[i].portaNote; - chan[i].inPorta=false; - dispatchCmd(DivCommand(DIV_CMD_LEGATO,i,chan[i].note)); + if (chan[i].vibratoDepth>0) { + chan[i].vibratoPos+=chan[i].vibratoRate; + if (chan[i].vibratoPos>=64) chan[i].vibratoPos-=64; + switch (chan[i].vibratoDir) { + case 1: // up + dispatchCmd(DivCommand(DIV_CMD_PITCH,i,chan[i].pitch+(MAX(0,(chan[i].vibratoDepth*vibTable[chan[i].vibratoPos]*chan[i].vibratoFine)>>4)/15))); + break; + case 2: // down + dispatchCmd(DivCommand(DIV_CMD_PITCH,i,chan[i].pitch+(MIN(0,(chan[i].vibratoDepth*vibTable[chan[i].vibratoPos]*chan[i].vibratoFine)>>4)/15))); + break; + default: // both + dispatchCmd(DivCommand(DIV_CMD_PITCH,i,chan[i].pitch+(((chan[i].vibratoDepth*vibTable[chan[i].vibratoPos]*chan[i].vibratoFine)>>4)/15))); + break; } } - } - if (chan[i].cut>0) { - if (--chan[i].cut<1) { - chan[i].oldNote=chan[i].note; - //chan[i].note=-1; - if (chan[i].inPorta && song.noteOffResetsSlides) { - chan[i].keyOff=true; - chan[i].keyOn=false; - if (chan[i].stopOnOff) { - chan[i].portaNote=-1; - chan[i].portaSpeed=-1; - chan[i].stopOnOff=false; - } - if (disCont[dispatchOfChan[i]].dispatch->keyOffAffectsPorta(dispatchChanOfChan[i])) { - chan[i].portaNote=-1; - chan[i].portaSpeed=-1; - /*if (i==2 && sysOfChan[i]==DIV_SYSTEM_SMS) { - chan[i+1].portaNote=-1; - chan[i+1].portaSpeed=-1; - }*/ - } - dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,false,0)); - chan[i].scheduledSlideReset=true; - } - dispatchCmd(DivCommand(DIV_CMD_NOTE_OFF,i)); - } - } - if (chan[i].resetArp) { - dispatchCmd(DivCommand(DIV_CMD_LEGATO,i,chan[i].note)); - chan[i].resetArp=false; - } - if (song.rowResetsArpPos && firstTick) { - chan[i].arpStage=-1; - } - if (chan[i].arp!=0 && !chan[i].arpYield && chan[i].portaSpeed<1) { - if (--chan[i].arpTicks<1) { - chan[i].arpTicks=song.arpLen; - chan[i].arpStage++; - if (chan[i].arpStage>2) chan[i].arpStage=0; - switch (chan[i].arpStage) { - case 0: + if (!song.noSlidesOnFirstTick || !firstTick) { + if ((chan[i].keyOn || chan[i].keyOff) && chan[i].portaSpeed>0) { + if (dispatchCmd(DivCommand(DIV_CMD_NOTE_PORTA,i,chan[i].portaSpeed,chan[i].portaNote))==2 && chan[i].portaStop && song.targetResetsSlides) { + chan[i].portaSpeed=0; + chan[i].oldNote=chan[i].note; + chan[i].note=chan[i].portaNote; + chan[i].inPorta=false; dispatchCmd(DivCommand(DIV_CMD_LEGATO,i,chan[i].note)); - break; - case 1: - dispatchCmd(DivCommand(DIV_CMD_LEGATO,i,chan[i].note+(chan[i].arp>>4))); - break; - case 2: - dispatchCmd(DivCommand(DIV_CMD_LEGATO,i,chan[i].note+(chan[i].arp&15))); - break; + } } } - } else { - chan[i].arpYield=false; + if (chan[i].cut>0) { + if (--chan[i].cut<1) { + chan[i].oldNote=chan[i].note; + //chan[i].note=-1; + if (chan[i].inPorta && song.noteOffResetsSlides) { + chan[i].keyOff=true; + chan[i].keyOn=false; + if (chan[i].stopOnOff) { + chan[i].portaNote=-1; + chan[i].portaSpeed=-1; + chan[i].stopOnOff=false; + } + if (disCont[dispatchOfChan[i]].dispatch->keyOffAffectsPorta(dispatchChanOfChan[i])) { + chan[i].portaNote=-1; + chan[i].portaSpeed=-1; + /*if (i==2 && sysOfChan[i]==DIV_SYSTEM_SMS) { + chan[i+1].portaNote=-1; + chan[i+1].portaSpeed=-1; + }*/ + } + dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,false,0)); + chan[i].scheduledSlideReset=true; + } + dispatchCmd(DivCommand(DIV_CMD_NOTE_OFF,i)); + } + } + if (chan[i].resetArp) { + dispatchCmd(DivCommand(DIV_CMD_LEGATO,i,chan[i].note)); + chan[i].resetArp=false; + } + if (song.rowResetsArpPos && firstTick) { + chan[i].arpStage=-1; + } + if (chan[i].arp!=0 && !chan[i].arpYield && chan[i].portaSpeed<1) { + if (--chan[i].arpTicks<1) { + chan[i].arpTicks=song.arpLen; + chan[i].arpStage++; + if (chan[i].arpStage>2) chan[i].arpStage=0; + switch (chan[i].arpStage) { + case 0: + dispatchCmd(DivCommand(DIV_CMD_LEGATO,i,chan[i].note)); + break; + case 1: + dispatchCmd(DivCommand(DIV_CMD_LEGATO,i,chan[i].note+(chan[i].arp>>4))); + break; + case 2: + dispatchCmd(DivCommand(DIV_CMD_LEGATO,i,chan[i].note+(chan[i].arp&15))); + break; + } + } + } else { + chan[i].arpYield=false; + } } } } @@ -1636,7 +1644,7 @@ bool DivEngine::nextTick(bool noAccum) { if (stepPlay!=1) { if (!noAccum) { totalTicksR++; - totalTicks+=1000000/divider; + totalTicks+=1000000/(divider*tickMult); } if (totalTicks>=1000000) { totalTicks-=1000000; diff --git a/src/gui/gui.h b/src/gui/gui.h index 40d0b9c44..67d084a47 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -818,6 +818,7 @@ class FurnaceGUI { int scrollChangesOrder; int oplStandardWaveNames; int cursorMoveNoScroll; + int lowLatency; unsigned int maxUndoSteps; String mainFontPath; String patFontPath; @@ -889,6 +890,7 @@ class FurnaceGUI { scrollChangesOrder(0), oplStandardWaveNames(0), cursorMoveNoScroll(0), + lowLatency(0), maxUndoSteps(100), mainFontPath(""), patFontPath(""), diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 666ba3b62..f4d62a81d 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -405,6 +405,14 @@ void FurnaceGUI::drawSettings() { e->setMetronomeVol(((float)settings.metroVol)/100.0f); } + bool lowLatencyB=settings.lowLatency; + if (ImGui::Checkbox("Low-latency mode (experimental!)",&lowLatencyB)) { + settings.lowLatency=lowLatencyB; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("reduces latency by running the engine faster than the tick rate.\nuseful for live playback/jam mode.\n\nwarning: experimental! may produce glitches.\nonly enable if your buffer size is small (10ms or less)."); + } + bool forceMonoB=settings.forceMono; if (ImGui::Checkbox("Force mono audio",&forceMonoB)) { settings.forceMono=forceMonoB; @@ -1600,6 +1608,7 @@ void FurnaceGUI::syncSettings() { settings.scrollChangesOrder=e->getConfInt("scrollChangesOrder",0); settings.oplStandardWaveNames=e->getConfInt("oplStandardWaveNames",0); settings.cursorMoveNoScroll=e->getConfInt("cursorMoveNoScroll",0); + settings.lowLatency=e->getConfInt("lowLatency",0); clampSetting(settings.mainFontSize,2,96); clampSetting(settings.patFontSize,2,96); @@ -1660,6 +1669,7 @@ void FurnaceGUI::syncSettings() { clampSetting(settings.scrollChangesOrder,0,1); clampSetting(settings.oplStandardWaveNames,0,1); clampSetting(settings.cursorMoveNoScroll,0,1); + clampSetting(settings.lowLatency,0,1); // keybinds for (int i=0; isetConf("scrollChangesOrder",settings.scrollChangesOrder); e->setConf("oplStandardWaveNames",settings.oplStandardWaveNames); e->setConf("cursorMoveNoScroll",settings.cursorMoveNoScroll); + e->setConf("lowLatency",settings.lowLatency); // colors for (int i=0; i