diff --git a/README.md b/README.md index f8ea1aec..1eac106d 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,8 @@ this is a multi-system chiptune tracker. check out the [Releases](https://github.com/tildearrow/furnace/releases) page. available for Windows, macOS and Linux (AppImage). +[see here](https://nightly.link/tildearrow/furnace/workflows/build/master) for unstable developer builds. + ## features - supports the following systems: @@ -64,6 +66,8 @@ some people have provided packages for Unix/Unix-like distributions. here's a li [![Build furnace](https://github.com/tildearrow/furnace/actions/workflows/build.yml/badge.svg)](https://github.com/tildearrow/furnace/actions/workflows/build.yml) +if you can't download these artifacts (because GitHub requires you to be logged in), [go here](https://nightly.link/tildearrow/furnace/workflows/build/master) instead. + **NOTE: do not download the project's source as a .zip or .tar.gz as these do not include the project's submodules which are necessary to proceed with building. please instead use Git as shown below.** ## dependencies diff --git a/TODO.md b/TODO.md index d9817937..04b724e0 100644 --- a/TODO.md +++ b/TODO.md @@ -1,7 +1,11 @@ # to-do for 0.6pre1 - panning macro + - single macro for hard-panned chips + - two macros for soft-panned ones - pitch macro + - relative mode + - test - piano/input pad - note input via piano - input pad @@ -43,3 +47,4 @@ - settings: OK/Cancel buttons should be always visible - Apply button in settings - better FM chip names (number and codename) +- find and replace diff --git a/papers/format.md b/papers/format.md index 2f4037a0..7775e9ea 100644 --- a/papers/format.md +++ b/papers/format.md @@ -274,6 +274,19 @@ size | description # instrument +notes: + +- the entire instrument is stored, regardless of instrument type. +- the macro range varies depending on the instrument type. +- "macro open" indicates whether the macro is collapsed or not in the instrument editor. +- FM operator order is: + - 1/3/2/4 (internal order) for OPN, OPM, OPZ and OPL 4-op + - 1/2/?/? (? = unused) for OPL 2-op and OPLL +- meaning of extended macros varies depending on instrument type. +- meaning of panning macros varies depending on instrument type: + - for hard-panned chips (e.g. FM and Game Boy): left panning is 2-bit panning macro (left/right) + - otherwise both left and right panning macros are used + ``` size | description -----|------------------------------------ @@ -282,17 +295,39 @@ size | description 2 | format version (see header) 1 | instrument type | - 0: standard - | - 1: FM + | - 1: FM (OPM/OPN) | - 2: Game Boy | - 3: C64 | - 4: Amiga/sample + | - 5: PC Engine + | - 6: AY-3-8910 + | - 7: AY8930 + | - 8: TIA + | - 9: SAA1099 + | - 10: VIC + | - 11: PET + | - 12: VRC6 + | - 13: OPLL + | - 14: OPL + | - 15: FDS + | - 16: Virtual Boy + | - 17: Namco 163 + | - 18: SCC + | - 19: OPZ + | - 20: POKEY + | - 21: PC Speaker + | - 22: WonderSwan + | - 23: Lynx + | - 24: VERA + | - 25: X1-010 + | - 26: VRC6 (saw) 1 | reserved STR | instrument name --- | **FM instrument data** - 1 | alg + 1 | alg (SUS on OPLL) 1 | feedback - 1 | fms - 1 | ams + 1 | fms (DC on OPLL) + 1 | ams (DM on OPLL) 1 | operator count | - this is either 2 or 4, and is ignored on non-OPL systems. | - always read 4 ops regardless of this value. @@ -314,10 +349,12 @@ size | description 1 | dt 1 | d2r 1 | ssgEnv - 1 | dam - 1 | dvb - 1 | egt - 1 | ksl + | - bit 4: on (EG-S on OPLL) + | - bit 0-3: envelope type + 1 | dam (for YMU759 compat; REV on OPZ) + 1 | dvb (for YMU759 compat; FINE on OPZ) + 1 | egt (for YMU759 compat; FixedFreq on OPZ) + 1 | ksl (EGShift on OPZ) 1 | sus 1 | vib 1 | ws @@ -690,15 +727,18 @@ size | description | - 10: A# | - 11: B | - 12: C (of next octave) + | - this is actually a leftover of the .dmf format. | - 100: note off | - 100: note release | - 100: macro release | - octave | - this is an signed char stored in a short. | - therefore octave value 255 is actually octave -1. + | - yep, another leftover of the .dmf format... | - instrument | - volume - | - effect and effect data... + | - effect and effect data (× effect columns) + | - for note/octave, if both values are 0 then it means empty. | - for instrument, volume, effect and effect data, a value of -1 means empty. STR | pattern name (>=51) ``` diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 6257ff53..6d0d4df0 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -853,6 +853,7 @@ void DivEngine::playSub(bool preserveDrift, int goalRow) { } if (!preserveDrift) { ticks=1; + subticks=1; } skipping=false; cmdStream.clear(); @@ -1970,7 +1971,7 @@ void DivEngine::autoNoteOn(int ch, int ins, int note, int vol) { } do { - if ((ins==-1 || getPreferInsType(finalChan)==getIns(ins)->type) && chan[finalChan].midiNote==-1) { + if ((ins==-1 || getPreferInsType(finalChan)==getIns(ins)->type || getIns(ins)->type==DIV_INS_AMIGA) && chan[finalChan].midiNote==-1) { chan[finalChan].midiNote=note; pendingNotes.push(DivNoteEvent(finalChan,ins,note,vol,true)); break; @@ -1996,6 +1997,20 @@ void DivEngine::autoNoteOff(int ch, int note, int vol) { } } +void DivEngine::autoNoteOffAll() { + if (!playing) { + reset(); + freelance=true; + playing=true; + } + for (int i=0; i>1; + chan[i].chVolR=chan[i].std.panL.val&1; + if (isMuted[i]) { + rWrite(chanOffs[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); + } else { + rWrite(chanOffs[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)|((chan[i].chVolL&1)<<6)|((chan[i].chVolR&1)<<7)); + } + } + + if (chan[i].std.pitch.had) { + chan[i].freqChanged=true; + } + if (chan[i].std.phaseReset.had) { if (chan[i].std.phaseReset.val==1) { chan[i].keyOn=true; diff --git a/src/engine/platform/ay.cpp b/src/engine/platform/ay.cpp index d3db4ba8..25f49e95 100644 --- a/src/engine/platform/ay.cpp +++ b/src/engine/platform/ay.cpp @@ -215,6 +215,9 @@ void DivPlatformAY8910::tick(bool sysTick) { rWrite(0x08+i,(chan[i].outVol&15)|((chan[i].psgMode&4)<<2)); } } + if (chan[i].std.pitch.had) { + chan[i].freqChanged=true; + } if (chan[i].std.phaseReset.had) { if (chan[i].std.phaseReset.val==1) { oldWrites[0x08+i]=-1; diff --git a/src/engine/platform/ay8930.cpp b/src/engine/platform/ay8930.cpp index 9fa39131..e4fa30dc 100644 --- a/src/engine/platform/ay8930.cpp +++ b/src/engine/platform/ay8930.cpp @@ -226,6 +226,9 @@ void DivPlatformAY8930::tick(bool sysTick) { rWrite(0x08+i,(chan[i].outVol&31)|((chan[i].psgMode&4)<<3)); } } + if (chan[i].std.pitch.had) { + chan[i].freqChanged=true; + } if (chan[i].std.phaseReset.had) { if (chan[i].std.phaseReset.val==1) { oldWrites[0x08+i]=-1; diff --git a/src/engine/platform/bubsyswsg.cpp b/src/engine/platform/bubsyswsg.cpp index 91f50d46..569e82f1 100644 --- a/src/engine/platform/bubsyswsg.cpp +++ b/src/engine/platform/bubsyswsg.cpp @@ -110,6 +110,9 @@ void DivPlatformBubSysWSG::tick(bool sysTick) { if (!chan[i].keyOff) chan[i].keyOn=true; } } + if (chan[i].std.pitch.had) { + chan[i].freqChanged=true; + } if (chan[i].active) { if (chan[i].ws.tick()) { updateWave(i); diff --git a/src/engine/platform/c64.cpp b/src/engine/platform/c64.cpp index 4c3cc32e..12ec3f5f 100644 --- a/src/engine/platform/c64.cpp +++ b/src/engine/platform/c64.cpp @@ -181,6 +181,9 @@ void DivPlatformC64::tick(bool sysTick) { chan[i].wave=chan[i].std.wave.val; rWrite(i*7+4,(chan[i].wave<<4)|(chan[i].ring<<2)|(chan[i].sync<<1)|(int)(chan[i].active)); } + if (chan[i].std.pitch.had) { + chan[i].freqChanged=true; + } if (chan[i].std.ex1.had) { filtControl=chan[i].std.ex1.val&15; updateFilter(); diff --git a/src/engine/platform/fds.cpp b/src/engine/platform/fds.cpp index 16e24f13..c581c516 100644 --- a/src/engine/platform/fds.cpp +++ b/src/engine/platform/fds.cpp @@ -107,21 +107,11 @@ void DivPlatformFDS::tick(bool sysTick) { rWrite(0x4080,0x80|chan[i].outVol); } if (chan[i].std.arp.had) { - if (i==3) { // noise + if (!chan[i].inPorta) { if (chan[i].std.arp.mode) { - chan[i].baseFreq=chan[i].std.arp.val; + chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp.val); } else { - chan[i].baseFreq=chan[i].note+chan[i].std.arp.val; - } - if (chan[i].baseFreq>255) chan[i].baseFreq=255; - if (chan[i].baseFreq<0) chan[i].baseFreq=0; - } else { - if (!chan[i].inPorta) { - if (chan[i].std.arp.mode) { - chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp.val); - } else { - chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+chan[i].std.arp.val); - } + chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+chan[i].std.arp.val); } } chan[i].freqChanged=true; @@ -155,6 +145,9 @@ void DivPlatformFDS::tick(bool sysTick) { //if (!chan[i].keyOff) chan[i].keyOn=true; } } + if (chan[i].std.pitch.had) { + chan[i].freqChanged=true; + } if (chan[i].active) { if (ws.tick()) { updateWave(); diff --git a/src/engine/platform/gb.cpp b/src/engine/platform/gb.cpp index c28478a3..d3bb28e5 100644 --- a/src/engine/platform/gb.cpp +++ b/src/engine/platform/gb.cpp @@ -192,6 +192,9 @@ void DivPlatformGB::tick(bool sysTick) { if (!chan[i].keyOff) chan[i].keyOn=true; } } + if (chan[i].std.pitch.had) { + chan[i].freqChanged=true; + } if (chan[i].std.phaseReset.had) { if (chan[i].std.phaseReset.val==1) { chan[i].keyOn=true; diff --git a/src/engine/platform/genesis.cpp b/src/engine/platform/genesis.cpp index bcca663e..1385c62c 100644 --- a/src/engine/platform/genesis.cpp +++ b/src/engine/platform/genesis.cpp @@ -258,6 +258,10 @@ void DivPlatformGenesis::tick(bool sysTick) { } } + if (chan[i].std.pitch.had) { + chan[i].freqChanged=true; + } + if (chan[i].std.phaseReset.had) { if (chan[i].std.phaseReset.val==1) { chan[i].keyOn=true; diff --git a/src/engine/platform/lynx.cpp b/src/engine/platform/lynx.cpp index 307d2059..16d3f15e 100644 --- a/src/engine/platform/lynx.cpp +++ b/src/engine/platform/lynx.cpp @@ -171,6 +171,10 @@ void DivPlatformLynx::tick(bool sysTick) { } } + if (chan[i].std.pitch.had) { + chan[i].freqChanged=true; + } + if (chan[i].freqChanged) { if (chan[i].lfsr >= 0) { WRITE_LFSR(i, (chan[i].lfsr&0xff)); @@ -184,8 +188,8 @@ void DivPlatformLynx::tick(bool sysTick) { } WRITE_CONTROL(i, (chan[i].fd.clockDivider|0x18|chan[i].duty.int_feedback7)); WRITE_BACKUP( i, chan[i].fd.backup ); - } - else if (chan[i].std.duty.had) { + chan[i].freqChanged=false; + } else if (chan[i].std.duty.had) { chan[i].duty = chan[i].std.duty.val; WRITE_FEEDBACK(i, chan[i].duty.feedback); WRITE_CONTROL(i, (chan[i].fd.clockDivider|0x18|chan[i].duty.int_feedback7)); diff --git a/src/engine/platform/mmc5.cpp b/src/engine/platform/mmc5.cpp index 3c7f3dca..696ae6d2 100644 --- a/src/engine/platform/mmc5.cpp +++ b/src/engine/platform/mmc5.cpp @@ -123,6 +123,9 @@ void DivPlatformMMC5::tick(bool sysTick) { chan[i].duty=chan[i].std.duty.val; rWrite(0x5000+i*4,0x30|chan[i].outVol|((chan[i].duty&3)<<6)); } + if (chan[i].std.pitch.had) { + chan[i].freqChanged=true; + } if (chan[i].std.phaseReset.had) { if (chan[i].std.phaseReset.val==1) { chan[i].freqChanged=true; diff --git a/src/engine/platform/n163.cpp b/src/engine/platform/n163.cpp index 6a1eb60e..a9634fa9 100644 --- a/src/engine/platform/n163.cpp +++ b/src/engine/platform/n163.cpp @@ -261,6 +261,9 @@ void DivPlatformN163::tick(bool sysTick) { } } } + if (chan[i].std.pitch.had) { + chan[i].freqChanged=true; + } if (chan[i].std.ex1.had) { if (chan[i].waveLen!=(chan[i].std.ex1.val&0xfc)) { chan[i].waveLen=chan[i].std.ex1.val&0xfc; diff --git a/src/engine/platform/nes.cpp b/src/engine/platform/nes.cpp index 7e291446..e4bfbca9 100644 --- a/src/engine/platform/nes.cpp +++ b/src/engine/platform/nes.cpp @@ -195,6 +195,9 @@ void DivPlatformNES::tick(bool sysTick) { chan[i].freqChanged=true; } } + if (chan[i].std.pitch.had) { + chan[i].freqChanged=true; + } if (chan[i].sweepChanged) { chan[i].sweepChanged=false; if (i==0) { diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index e73ca41e..7050e000 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -269,6 +269,10 @@ void DivPlatformOPL::tick(bool sysTick) { } } + if (chan[i].std.pitch.had) { + chan[i].freqChanged=true; + } + if (chan[i].std.phaseReset.had) { if (chan[i].std.phaseReset.val==1) { chan[i].keyOn=true; diff --git a/src/engine/platform/opll.cpp b/src/engine/platform/opll.cpp index cb6ceba8..d94d1e26 100644 --- a/src/engine/platform/opll.cpp +++ b/src/engine/platform/opll.cpp @@ -145,6 +145,10 @@ void DivPlatformOPLL::tick(bool sysTick) { } } + if (chan[i].std.pitch.had) { + chan[i].freqChanged=true; + } + if (chan[i].std.phaseReset.had) { if (chan[i].std.phaseReset.val==1) { chan[i].keyOn=true; diff --git a/src/engine/platform/pce.cpp b/src/engine/platform/pce.cpp index cff5e534..2c248db0 100644 --- a/src/engine/platform/pce.cpp +++ b/src/engine/platform/pce.cpp @@ -196,6 +196,9 @@ void DivPlatformPCE::tick(bool sysTick) { if (!chan[i].keyOff) chan[i].keyOn=true; } } + if (chan[i].std.pitch.had) { + chan[i].freqChanged=true; + } if (chan[i].active) { if (chan[i].ws.tick() || (chan[i].std.phaseReset.had && chan[i].std.phaseReset.val==1)) { updateWave(i); diff --git a/src/engine/platform/pcspkr.cpp b/src/engine/platform/pcspkr.cpp index 8e1705f5..e7062d21 100644 --- a/src/engine/platform/pcspkr.cpp +++ b/src/engine/platform/pcspkr.cpp @@ -186,6 +186,9 @@ void DivPlatformPCSpeaker::tick(bool sysTick) { chan[i].freqChanged=true; } } + if (chan[i].std.pitch.had) { + chan[i].freqChanged=true; + } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true)-1+chan[i].std.pitch.val; if (chan[i].freq<0) chan[i].freq=0; diff --git a/src/engine/platform/pet.cpp b/src/engine/platform/pet.cpp index 0fe4523d..47bb2e3d 100644 --- a/src/engine/platform/pet.cpp +++ b/src/engine/platform/pet.cpp @@ -112,6 +112,9 @@ void DivPlatformPET::tick(bool sysTick) { rWrite(10,chan.wave); } } + if (chan.std.pitch.had) { + chan.freqChanged=true; + } if (chan.freqChanged || chan.keyOn || chan.keyOff) { chan.freq=parent->calcFreq(chan.baseFreq,chan.pitch,true)+chan.std.pitch.val; if (chan.freq>257) chan.freq=257; diff --git a/src/engine/platform/qsound.cpp b/src/engine/platform/qsound.cpp index 601ea0ea..33887010 100644 --- a/src/engine/platform/qsound.cpp +++ b/src/engine/platform/qsound.cpp @@ -326,6 +326,9 @@ void DivPlatformQSound::tick(bool sysTick) { chan[i].freqChanged=true; } } + if (chan[i].std.pitch.had) { + chan[i].freqChanged=true; + } 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,false)+chan[i].std.pitch.val; diff --git a/src/engine/platform/saa.cpp b/src/engine/platform/saa.cpp index ca91800c..0e06c338 100644 --- a/src/engine/platform/saa.cpp +++ b/src/engine/platform/saa.cpp @@ -166,6 +166,9 @@ void DivPlatformSAA1099::tick(bool sysTick) { if (chan[i].std.wave.had) { chan[i].psgMode=chan[i].std.wave.val&3; } + if (chan[i].std.pitch.had) { + chan[i].freqChanged=true; + } if (chan[i].std.ex1.had) { saaEnv[i/3]=chan[i].std.ex1.val; rWrite(0x18+(i/3),saaEnv[i/3]); diff --git a/src/engine/platform/segapcm.cpp b/src/engine/platform/segapcm.cpp index 97ff900a..79d7179a 100644 --- a/src/engine/platform/segapcm.cpp +++ b/src/engine/platform/segapcm.cpp @@ -99,6 +99,10 @@ void DivPlatformSegaPCM::tick(bool sysTick) { chan[i].freqChanged=true; } } + + if (chan[i].std.pitch.had) { + chan[i].freqChanged=true; + } /*if (chan[i].keyOn || chan[i].keyOff) { chan[i].keyOff=false; }*/ diff --git a/src/engine/platform/sms.cpp b/src/engine/platform/sms.cpp index acd2c072..d755e84f 100644 --- a/src/engine/platform/sms.cpp +++ b/src/engine/platform/sms.cpp @@ -98,6 +98,9 @@ void DivPlatformSMS::tick(bool sysTick) { } } } + if (chan[i].std.pitch.had) { + chan[i].freqChanged=true; + } } for (int i=0; i<3; i++) { if (chan[i].freqChanged) { diff --git a/src/engine/platform/swan.cpp b/src/engine/platform/swan.cpp index 99d819b1..3b4a955e 100644 --- a/src/engine/platform/swan.cpp +++ b/src/engine/platform/swan.cpp @@ -173,6 +173,9 @@ void DivPlatformSwan::tick(bool sysTick) { chan[i].ws.changeWave1(chan[i].wave); } } + if (chan[i].std.pitch.had) { + chan[i].freqChanged=true; + } if (chan[i].active) { sndCtrl|=(1<calcFreq(chan[i].baseFreq,chan[i].pitch,false,8)+chan[i].std.pitch.val; if (chan[i].freq>65535) chan[i].freq=65535; diff --git a/src/engine/platform/vic20.cpp b/src/engine/platform/vic20.cpp index 7817cd83..44aba967 100644 --- a/src/engine/platform/vic20.cpp +++ b/src/engine/platform/vic20.cpp @@ -119,6 +119,9 @@ void DivPlatformVIC20::tick(bool sysTick) { chan[i].keyOn=true; } } + if (chan[i].std.pitch.had) { + chan[i].freqChanged=true; + } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true)+chan[i].std.pitch.val; if (i<3) { diff --git a/src/engine/platform/vrc6.cpp b/src/engine/platform/vrc6.cpp index 048b42cd..d8b37d92 100644 --- a/src/engine/platform/vrc6.cpp +++ b/src/engine/platform/vrc6.cpp @@ -178,6 +178,9 @@ void DivPlatformVRC6::tick(bool sysTick) { chWrite(i,0,(chan[i].outVol&0xf)|((chan[i].duty&7)<<4)); } } + if (chan[i].std.pitch.had) { + chan[i].freqChanged=true; + } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { if (i==2) { // sawtooth chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true)-1+chan[i].std.pitch.val; diff --git a/src/engine/platform/x1_010.cpp b/src/engine/platform/x1_010.cpp index df6682ad..229d8eab 100644 --- a/src/engine/platform/x1_010.cpp +++ b/src/engine/platform/x1_010.cpp @@ -372,6 +372,9 @@ void DivPlatformX1_010::tick(bool sysTick) { } } } + if (chan[i].std.pitch.had) { + chan[i].freqChanged=true; + } if (chan[i].std.ex1.had) { bool nextEnable=(chan[i].std.ex1.val&1); if (nextEnable!=(chan[i].env.flag.envEnable)) { diff --git a/src/engine/platform/ym2610.cpp b/src/engine/platform/ym2610.cpp index 3d6c8ed2..6f4a1e46 100644 --- a/src/engine/platform/ym2610.cpp +++ b/src/engine/platform/ym2610.cpp @@ -408,6 +408,10 @@ void DivPlatformYM2610::tick(bool sysTick) { } } + if (chan[i].std.pitch.had) { + chan[i].freqChanged=true; + } + if (chan[i].std.phaseReset.had) { if (chan[i].std.phaseReset.val==1) { chan[i].keyOn=true; diff --git a/src/engine/platform/ym2610b.cpp b/src/engine/platform/ym2610b.cpp index aeadeae4..04bc096a 100644 --- a/src/engine/platform/ym2610b.cpp +++ b/src/engine/platform/ym2610b.cpp @@ -472,6 +472,10 @@ void DivPlatformYM2610B::tick(bool sysTick) { } } + if (chan[i].std.pitch.had) { + chan[i].freqChanged=true; + } + if (chan[i].std.phaseReset.had) { if (chan[i].std.phaseReset.val==1) { chan[i].keyOn=true; diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 2b22abd2..38c7309d 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -1472,19 +1472,21 @@ void DivEngine::nextRow() { firstTick=true; } -bool DivEngine::nextTick(bool noAccum) { +bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { bool ret=false; if (divider<10) divider=10; - if (lowLatency) { + if (lowLatency && !skipping && !inhibitLowLat) { tickMult=1000/divider; if (tickMult<1) tickMult=1; + } else { + tickMult=1; } 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; + clockDrift+=fmod(got.rate*pow(2,MASTER_CLOCK_PREC),(double)(divider*tickMult)); + if (clockDrift>=(divider*tickMult)) { + clockDrift-=(divider*tickMult); cycles++; } diff --git a/src/engine/vgmOps.cpp b/src/engine/vgmOps.cpp index b2081348..efac0e48 100644 --- a/src/engine/vgmOps.cpp +++ b/src/engine/vgmOps.cpp @@ -1384,7 +1384,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) { writeLoop=true; } } - if (nextTick() || !playing) { + if (nextTick(false,true) || !playing) { done=true; if (!loop) { for (int i=0; i7) { curOctave=7; } else { - for (size_t i=0; inoteOff(activeNotes[i].chan); - } - activeNotes.clear(); + e->autoNoteOffAll(); } break; case GUI_ACTION_OCTAVE_DOWN: if (--curOctave<-5) { curOctave=-5; } else { - for (size_t i=0; inoteOff(activeNotes[i].chan); - } - activeNotes.clear(); + e->autoNoteOffAll(); } break; case GUI_ACTION_INS_UP: @@ -565,12 +559,14 @@ void FurnaceGUI::doAction(int what) { case GUI_ACTION_SAMPLE_LIST_ADD: curSample=e->addSample(); + updateSampleTex=true; MARK_MODIFIED; break; case GUI_ACTION_SAMPLE_LIST_DUPLICATE: if (curSample>=0 && curSample<(int)e->song.sample.size()) { DivSample* prevSample=e->getSample(curSample); curSample=e->addSample(); + updateSampleTex=true; e->lockEngine([this,prevSample]() { DivSample* sample=e->getSample(curSample); if (sample!=NULL) { @@ -597,16 +593,23 @@ void FurnaceGUI::doAction(int what) { if (curSample>=0 && curSample<(int)e->song.sample.size()) openFileDialog(GUI_FILE_SAMPLE_SAVE); break; case GUI_ACTION_SAMPLE_LIST_MOVE_UP: - if (e->moveSampleUp(curSample)) curSample--; + if (e->moveSampleUp(curSample)) { + curSample--; + updateSampleTex=true; + } break; case GUI_ACTION_SAMPLE_LIST_MOVE_DOWN: - if (e->moveSampleDown(curSample)) curSample++; + if (e->moveSampleDown(curSample)) { + curSample++; + updateSampleTex=true; + } break; case GUI_ACTION_SAMPLE_LIST_DELETE: e->delSample(curSample); MARK_MODIFIED; if (curSample>=(int)e->song.sample.size()) { curSample--; + updateSampleTex=true; } break; case GUI_ACTION_SAMPLE_LIST_EDIT: @@ -614,9 +617,11 @@ void FurnaceGUI::doAction(int what) { break; case GUI_ACTION_SAMPLE_LIST_UP: if (--curSample<0) curSample=0; + updateSampleTex=true; break; case GUI_ACTION_SAMPLE_LIST_DOWN: if (++curSample>=(int)e->song.sample.size()) curSample=((int)e->song.sample.size())-1; + updateSampleTex=true; break; case GUI_ACTION_SAMPLE_LIST_PREVIEW: e->previewSample(curSample); diff --git a/src/gui/editControls.cpp b/src/gui/editControls.cpp index 5098a73d..722c38f2 100644 --- a/src/gui/editControls.cpp +++ b/src/gui/editControls.cpp @@ -35,10 +35,7 @@ void FurnaceGUI::drawEditControls() { if (ImGui::InputInt("##Octave",&curOctave,1,1)) { if (curOctave>7) curOctave=7; if (curOctave<-5) curOctave=-5; - for (size_t i=0; inoteOff(activeNotes[i].chan); - } - activeNotes.clear(); + e->autoNoteOffAll(); if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { nextWindow=GUI_WINDOW_PATTERN; @@ -139,10 +136,7 @@ void FurnaceGUI::drawEditControls() { if (ImGui::InputInt("##Octave",&curOctave,1,1)) { if (curOctave>7) curOctave=7; if (curOctave<-5) curOctave=-5; - for (size_t i=0; inoteOff(activeNotes[i].chan); - } - activeNotes.clear(); + e->autoNoteOffAll(); if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { nextWindow=GUI_WINDOW_PATTERN; @@ -213,10 +207,7 @@ void FurnaceGUI::drawEditControls() { if (ImGui::InputInt("##Octave",&curOctave,0,0)) { if (curOctave>7) curOctave=7; if (curOctave<-5) curOctave=-5; - for (size_t i=0; inoteOff(activeNotes[i].chan); - } - activeNotes.clear(); + e->autoNoteOffAll(); if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { nextWindow=GUI_WINDOW_PATTERN; @@ -314,10 +305,7 @@ void FurnaceGUI::drawEditControls() { if (ImGui::InputInt("##Octave",&curOctave,1,1)) { if (curOctave>7) curOctave=7; if (curOctave<-5) curOctave=-5; - for (size_t i=0; inoteOff(activeNotes[i].chan); - } - activeNotes.clear(); + e->autoNoteOffAll(); if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { nextWindow=GUI_WINDOW_PATTERN; diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index bdee5656..7f8f0d72 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1078,9 +1078,6 @@ void FurnaceGUI::keyDown(SDL_Event& ev) { if (edit) { noteInput(num,key); } - if (key!=100 && key!=101 && key!=102) { - previewNote(cursor.xCoarse,num); - } } catch (std::out_of_range& e) { } } else if (edit) { // value @@ -1182,75 +1179,10 @@ void FurnaceGUI::keyDown(SDL_Event& ev) { } } catch (std::out_of_range& e) { } - - // PER-WINDOW PREVIEW KEYS - switch (curWindow) { - case GUI_WINDOW_INS_EDIT: - case GUI_WINDOW_INS_LIST: - case GUI_WINDOW_EDIT_CONTROLS: - case GUI_WINDOW_SONG_INFO: - if (!ev.key.repeat) { - try { - int key=noteKeys.at(ev.key.keysym.scancode); - int num=12*curOctave+key; - if (key!=100 && key!=101 && key!=102) { - previewNote(cursor.xCoarse,num); - } - } catch (std::out_of_range& e) { - } - } - break; - case GUI_WINDOW_SAMPLE_EDIT: - case GUI_WINDOW_SAMPLE_LIST: - if (!ev.key.repeat) { - try { - int key=noteKeys.at(ev.key.keysym.scancode); - int num=12*curOctave+key; - if (key!=100 && key!=101 && key!=102) { - e->previewSample(curSample,num); - samplePreviewOn=true; - samplePreviewKey=ev.key.keysym.scancode; - samplePreviewNote=num; - } - } catch (std::out_of_range& e) { - } - } - break; - case GUI_WINDOW_WAVE_LIST: - case GUI_WINDOW_WAVE_EDIT: - if (!ev.key.repeat) { - try { - int key=noteKeys.at(ev.key.keysym.scancode); - int num=12*curOctave+key; - if (key!=100 && key!=101 && key!=102) { - e->previewWave(curWave,num); - wavePreviewOn=true; - wavePreviewKey=ev.key.keysym.scancode; - wavePreviewNote=num; - } - } catch (std::out_of_range& e) { - } - } - break; - default: - break; - } } void FurnaceGUI::keyUp(SDL_Event& ev) { - stopPreviewNote(ev.key.keysym.scancode,true); - if (wavePreviewOn) { - if (ev.key.keysym.scancode==wavePreviewKey) { - wavePreviewOn=false; - e->stopWavePreview(); - } - } - if (samplePreviewOn) { - if (ev.key.keysym.scancode==samplePreviewKey) { - samplePreviewOn=false; - e->stopSamplePreview(); - } - } + // nothing for now } bool dirExists(String what) { @@ -2021,10 +1953,100 @@ void FurnaceGUI::editOptions(bool topMenu) { } } +int _processEvent(void* instance, SDL_Event* event) { + return ((FurnaceGUI*)instance)->processEvent(event); +} + +int FurnaceGUI::processEvent(SDL_Event* ev) { + if (ev->type==SDL_KEYDOWN) { + if (!ev->key.repeat && !wantCaptureKeyboard && (ev->key.keysym.mod&(~(KMOD_NUM|KMOD_CAPS|KMOD_SCROLL)))==0) { + if (settings.notePreviewBehavior==0) return 1; + switch (curWindow) { + case GUI_WINDOW_SAMPLE_EDIT: + case GUI_WINDOW_SAMPLE_LIST: + try { + int key=noteKeys.at(ev->key.keysym.scancode); + int num=12*curOctave+key; + if (key!=100 && key!=101 && key!=102) { + e->previewSample(curSample,num); + samplePreviewOn=true; + samplePreviewKey=ev->key.keysym.scancode; + samplePreviewNote=num; + } + } catch (std::out_of_range& e) { + } + break; + case GUI_WINDOW_WAVE_LIST: + case GUI_WINDOW_WAVE_EDIT: + try { + int key=noteKeys.at(ev->key.keysym.scancode); + int num=12*curOctave+key; + if (key!=100 && key!=101 && key!=102) { + e->previewWave(curWave,num); + wavePreviewOn=true; + wavePreviewKey=ev->key.keysym.scancode; + wavePreviewNote=num; + } + } catch (std::out_of_range& e) { + } + break; + case GUI_WINDOW_ORDERS: // ignore here + break; + case GUI_WINDOW_PATTERN: + if (settings.notePreviewBehavior==1) { + if (cursor.xFine!=0) break; + } else if (settings.notePreviewBehavior==2) { + if (edit && cursor.xFine!=0) break; + } + // fall-through + default: + try { + int key=noteKeys.at(ev->key.keysym.scancode); + int num=12*curOctave+key; + + if (num<-60) num=-60; // C-(-5) + if (num>119) num=119; // B-9 + + if (key!=100 && key!=101 && key!=102) { + previewNote(cursor.xCoarse,num); + } + } catch (std::out_of_range& e) { + } + break; + } + } + } else if (ev->type==SDL_KEYUP) { + stopPreviewNote(ev->key.keysym.scancode,true); + if (wavePreviewOn) { + if (ev->key.keysym.scancode==wavePreviewKey) { + wavePreviewOn=false; + e->stopWavePreview(); + } + } + if (samplePreviewOn) { + if (ev->key.keysym.scancode==samplePreviewKey) { + samplePreviewOn=false; + e->stopSamplePreview(); + } + } + } + return 1; +} + bool FurnaceGUI::loop() { + SDL_SetEventFilter(_processEvent,this); + while (!quit) { SDL_Event ev; + if (e->isPlaying()) { + WAKE_UP; + } + if (--drawHalt<=0) { + drawHalt=0; + if (settings.powerSave) SDL_WaitEventTimeout(NULL,500); + } while (SDL_PollEvent(&ev)) { + WAKE_UP; ImGui_ImplSDL2_ProcessEvent(&ev); switch (ev.type) { case SDL_MOUSEMOTION: { @@ -2131,23 +2153,7 @@ bool FurnaceGUI::loop() { } break; case SDL_KEYUP: - if (!ImGui::GetIO().WantCaptureKeyboard) { - keyUp(ev); - } else { - stopPreviewNote(ev.key.keysym.scancode,true); - if (wavePreviewOn) { - if (ev.key.keysym.scancode==wavePreviewKey) { - wavePreviewOn=false; - e->stopWavePreview(); - } - } - if (samplePreviewOn) { - if (ev.key.keysym.scancode==samplePreviewKey) { - samplePreviewOn=false; - e->stopSamplePreview(); - } - } - } + // for now break; case SDL_DROPFILE: if (ev.drop.file!=NULL) { @@ -2172,6 +2178,8 @@ bool FurnaceGUI::loop() { break; } } + + wantCaptureKeyboard=ImGui::GetIO().WantCaptureKeyboard; while (true) { midiLock.lock(); @@ -3422,8 +3430,10 @@ FurnaceGUI::FurnaceGUI(): displayError(false), displayExporting(false), vgmExportLoop(true), + wantCaptureKeyboard(false), displayNew(false), vgmExportVersion(0x171), + drawHalt(10), curFileDialog(GUI_FILE_OPEN), warnAction(GUI_WARN_OPEN), postWarnAction(GUI_WARN_GENERIC), diff --git a/src/gui/gui.h b/src/gui/gui.h index 67d084a4..b6613e9d 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -41,6 +41,7 @@ #define unimportant(x) if (x) {handleUnimportant} #define MARK_MODIFIED modified=true; +#define WAKE_UP drawHalt=16; #define TOGGLE_COLOR(x) ((x)?uiColors[GUI_COLOR_TOGGLE_ON]:uiColors[GUI_COLOR_TOGGLE_OFF]) @@ -711,10 +712,11 @@ class FurnaceGUI { String mmlString[17]; String mmlStringW; - bool quit, warnQuit, willCommit, edit, modified, displayError, displayExporting, vgmExportLoop; + bool quit, warnQuit, willCommit, edit, modified, displayError, displayExporting, vgmExportLoop, wantCaptureKeyboard; bool displayNew; bool willExport[32]; int vgmExportVersion; + int drawHalt; FurnaceGUIFileDialogs curFileDialog; FurnaceGUIWarnings warnAction; @@ -819,6 +821,8 @@ class FurnaceGUI { int oplStandardWaveNames; int cursorMoveNoScroll; int lowLatency; + int notePreviewBehavior; + int powerSave; unsigned int maxUndoSteps; String mainFontPath; String patFontPath; @@ -891,6 +895,8 @@ class FurnaceGUI { oplStandardWaveNames(0), cursorMoveNoScroll(0), lowLatency(0), + notePreviewBehavior(1), + powerSave(1), maxUndoSteps(100), mainFontPath(""), patFontPath(""), @@ -1215,6 +1221,7 @@ class FurnaceGUI { void addScroll(int amount); void setFileName(String name); void runBackupThread(); + int processEvent(SDL_Event* ev); bool loop(); bool finish(); bool init(); diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 50f93de2..b54ccd13 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -175,6 +175,10 @@ const char* n163UpdateBits[8]={ "now", "every waveform changed", NULL }; +const char* panBits[3]={ + "left", "right", NULL +}; + const char* oneBit[2]={ "on", NULL }; @@ -2717,8 +2721,10 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_SAA1099) ex1Max=8; int panMax=0; + bool panSingle=false; if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPL || ins->type==DIV_INS_GB || ins->type==DIV_INS_OPZ || ins->type==DIV_INS_VERA) { panMax=1; + panSingle=true; } if (ins->type==DIV_INS_AMIGA) { panMax=127; @@ -2746,8 +2752,12 @@ void FurnaceGUI::drawInsEdit() { NORMAL_MACRO(ins->std.waveMacro,0,waveMax,"wave",waveLabel,(bitMode && ins->type!=DIV_INS_PET)?64:160,ins->std.waveMacro.open,bitMode,waveNames,false,NULL,0,0,0,((ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930)?1:0),false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_WAVE],mmlString[3],0,waveMax,NULL,false); } if (panMax>0) { - NORMAL_MACRO(ins->std.panLMacro,0,panMax,"panL","Panning (left)",(31+panMax),ins->std.panLMacro.open,false,NULL,false,NULL,0,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[13],0,panMax,NULL,false); - NORMAL_MACRO(ins->std.panRMacro,0,panMax,"panR","Panning (right)",(31+panMax),ins->std.panRMacro.open,false,NULL,false,NULL,0,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[14],0,panMax,NULL,false); + if (panSingle) { + NORMAL_MACRO(ins->std.panLMacro,0,2,"panL","Panning",32,ins->std.panLMacro.open,true,panBits,false,NULL,0,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[13],0,panMax,NULL,false); + } else { + NORMAL_MACRO(ins->std.panLMacro,0,panMax,"panL","Panning (left)",(31+panMax),ins->std.panLMacro.open,false,NULL,false,NULL,0,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[13],0,panMax,NULL,false); + NORMAL_MACRO(ins->std.panRMacro,0,panMax,"panR","Panning (right)",(31+panMax),ins->std.panRMacro.open,false,NULL,false,NULL,0,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[14],0,panMax,NULL,false); + } } NORMAL_MACRO(ins->std.pitchMacro,pitchMacroScroll,pitchMacroScroll+160,"pitch","Pitch",160,ins->std.pitchMacro.open,false,NULL,true,&pitchMacroScroll,-2048,2047,0,0,true,1,macroAbsoluteMode,uiColors[GUI_COLOR_MACRO_PITCH],mmlString[15],-2048,2047,NULL,!ins->std.pitchMacro.mode); if (ins->type==DIV_INS_FM || diff --git a/src/gui/osc.cpp b/src/gui/osc.cpp index 266dfa3b..030a84e9 100644 --- a/src/gui/osc.cpp +++ b/src/gui/osc.cpp @@ -49,12 +49,19 @@ void FurnaceGUI::readOsc() { for (int i=0; i<512; i++) { int pos=(readPos+(i*total/512))&0x7fff; oscValues[i]=(e->oscBuf[0][pos]+e->oscBuf[1][pos])*0.5f; + if (oscValues[i]>0.001f || oscValues[i]<-0.001f) { + WAKE_UP; + } } float peakDecay=0.05f*60.0f*ImGui::GetIO().DeltaTime; for (int i=0; i<2; i++) { peak[i]*=1.0-peakDecay; - if (peak[i]<0.0001) peak[i]=0.0; + if (peak[i]<0.0001) { + peak[i]=0.0; + } else { + WAKE_UP; + } float newPeak=peak[i]; for (int j=0; jgetConfInt("oplStandardWaveNames",0); settings.cursorMoveNoScroll=e->getConfInt("cursorMoveNoScroll",0); settings.lowLatency=e->getConfInt("lowLatency",0); + settings.notePreviewBehavior=e->getConfInt("notePreviewBehavior",1); + settings.powerSave=e->getConfInt("powerSave",POWER_SAVE_DEFAULT); clampSetting(settings.mainFontSize,2,96); clampSetting(settings.patFontSize,2,96); @@ -1670,6 +1701,8 @@ void FurnaceGUI::syncSettings() { clampSetting(settings.oplStandardWaveNames,0,1); clampSetting(settings.cursorMoveNoScroll,0,1); clampSetting(settings.lowLatency,0,1); + clampSetting(settings.notePreviewBehavior,0,3); + clampSetting(settings.powerSave,0,1); // keybinds for (int i=0; isetConf("oplStandardWaveNames",settings.oplStandardWaveNames); e->setConf("cursorMoveNoScroll",settings.cursorMoveNoScroll); e->setConf("lowLatency",settings.lowLatency); + e->setConf("notePreviewBehavior",settings.notePreviewBehavior); + e->setConf("powerSave",settings.powerSave); // colors for (int i=0; i