From cfa05143abe002d84506d8f5e9b48a04b33cf343 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 18 May 2022 00:05:25 -0500 Subject: [PATCH] dev96 - add virtual tempo --- TODO.md | 1 + papers/format.md | 9 +++++++-- src/engine/engine.cpp | 2 ++ src/engine/engine.h | 6 ++++-- src/engine/fileOps.cpp | 34 ++++++++++++++++++++++++++++++---- src/engine/playback.cpp | 23 +++++++++++++++-------- src/engine/song.h | 3 +++ src/gui/gui.cpp | 7 ++++--- src/gui/gui.h | 2 +- src/gui/songInfo.cpp | 24 +++++++++++++++++++++++- 10 files changed, 90 insertions(+), 21 deletions(-) diff --git a/TODO.md b/TODO.md index 8c36f2161..3002ac7ce 100644 --- a/TODO.md +++ b/TODO.md @@ -1,5 +1,6 @@ # to-do for 0.6pre1 +- finish ExtCh on OPN/OPNA - RF5C68 system - ZX beeper system overlay percussion - ADPCM chips diff --git a/papers/format.md b/papers/format.md index c7672c29e..ffb904e9e 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: +- 96: Furnace dev96 - 95: Furnace dev95 - 94: Furnace dev94 - 93: Furnace dev93 @@ -296,7 +297,10 @@ size | description 1 | SN duty macro always resets phase (>=86) or reserved 1 | pitch macro is linear (>=90) or reserved 1 | pitch slide speed in full linear pitch mode (>=94) or reserved - 18 | reserved + 14 | reserved + --- | **virtual tempo data** + 2 | virtual tempo numerator of first song (>=96) or reserved + 2 | virtual tempo denominator of first song (>=96) or reserved --- | **additional subsongs** (>=95) STR | first subsong name STR | first subsong comment @@ -328,7 +332,8 @@ size | description | - the limit is 256. 1 | highlight A 1 | highlight B - 4 | reserved + 2 | virtual tempo numerator + 2 | virtual tempo denominator STR | subsong name STR | subsong comment ??? | orders diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index dd5473f38..2307d198e 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -1068,6 +1068,7 @@ void DivEngine::playSub(bool preserveDrift, int goalRow) { endOfSong=false; } else { ticks=1; + tempoAccum=0; totalTicks=0; totalSeconds=0; totalTicksR=0; @@ -2669,6 +2670,7 @@ void DivEngine::quitDispatch() { speedAB=false; endOfSong=false; ticks=0; + tempoAccum=0; curRow=0; curOrder=0; nextSpeed=3; diff --git a/src/engine/engine.h b/src/engine/engine.h index 895d288e4..5537dcc0d 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -45,8 +45,8 @@ #define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock(); #define BUSY_END isBusy.unlock(); softLocked=false; -#define DIV_VERSION "dev95" -#define DIV_ENGINE_VERSION 95 +#define DIV_VERSION "dev96" +#define DIV_ENGINE_VERSION 96 // for imports #define DIV_VERSION_MOD 0xff01 @@ -312,6 +312,7 @@ class DivEngine { int changeOrd, changePos, totalSeconds, totalTicks, totalTicksR, totalCmds, lastCmds, cmdsPerSecond, globalPitch; unsigned char extValue; unsigned char speed1, speed2; + short tempoAccum; DivStatusView view; DivHaltPositions haltOn; DivChannelState chan[DIV_MAX_CHANS]; @@ -942,6 +943,7 @@ class DivEngine { extValue(0), speed1(3), speed2(3), + tempoAccum(0), view(DIV_STATUS_NOTHING), haltOn(DIV_HALT_NONE), audioEngine(DIV_AUDIO_NULL), diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index feae353d3..7fee6f827 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -1404,11 +1404,19 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { } else { reader.readC(); } - for (int i=0; i<18; i++) { + for (int i=0; i<14; i++) { reader.readC(); } } + // first song virtual tempo + if (ds.version>=96) { + subSong->virtualTempoN=reader.readS(); + subSong->virtualTempoD=reader.readS(); + } else { + reader.readI(); + } + // subsongs if (ds.version>=95) { subSong->name=reader.readString(); @@ -1457,7 +1465,12 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { subSong->hilightA=reader.readC(); subSong->hilightB=reader.readC(); - reader.readI(); // reserved + if (ds.version>=96) { + subSong->virtualTempoN=reader.readS(); + subSong->virtualTempoD=reader.readS(); + } else { + reader.readI(); + } subSong->name=reader.readString(); subSong->notes=reader.readString(); @@ -2858,9 +2871,13 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) { w->writeC(song.snDutyReset); w->writeC(song.pitchMacroIsLinear); w->writeC(song.pitchSlideSpeed); - for (int i=0; i<18; i++) { + for (int i=0; i<14; i++) { w->writeC(0); } + + // first subsong virtual tempo + w->writeS(subSong->virtualTempoN); + w->writeS(subSong->virtualTempoD); // subsong list w->writeString(subSong->name,false); @@ -2891,7 +2908,8 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) { w->writeS(subSong->ordersLen); w->writeC(subSong->hilightA); w->writeC(subSong->hilightB); - w->writeI(0); // reserved + w->writeS(subSong->virtualTempoN); + w->writeS(subSong->virtualTempoD); w->writeString(subSong->name,false); w->writeString(subSong->notes,false); @@ -3171,6 +3189,14 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) { addWarning("only the currently selected subsong will be saved"); } + if (curSubSong->virtualTempoD!=curSubSong->virtualTempoN) { + addWarning(".dmf format does not support virtual tempo"); + } + + if (song.tuning<439.99 && song.tuning>440.01) { + addWarning(".dmf format does not support tuning"); + } + if (sys==DIV_SYSTEM_C64_6581 || sys==DIV_SYSTEM_C64_8580) { addWarning("absolute duty/cutoff macro not available in .dmf!"); addWarning("duty precision will be lost"); diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 7a2356a7e..0667522bb 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -900,16 +900,23 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { if (!freelance) { if (--subticks<=0) { subticks=tickMult; - if (stepPlay!=1) if (--ticks<=0) { - ret=endOfSong; - if (endOfSong) { - if (song.loopModality!=2) { - playSub(true); + if (stepPlay!=1) { + tempoAccum+=curSubSong->virtualTempoN; + while (tempoAccum>=curSubSong->virtualTempoD) { + tempoAccum-=curSubSong->virtualTempoD; + if (--ticks<=0) { + ret=endOfSong; + if (endOfSong) { + if (song.loopModality!=2) { + playSub(true); + } + } + endOfSong=false; + if (stepPlay==2) stepPlay=1; + nextRow(); + break; } } - endOfSong=false; - if (stepPlay==2) stepPlay=1; - nextRow(); } // process stuff for (int i=0; icurSubSong->hilightA; if (hl<=0.0f) hl=4.0f; float timeBase=e->curSubSong->timeBase+1; float speedSum=s1+s2; if (timeBase<1.0f) timeBase=1.0f; if (speedSum<1.0f) speedSum=1.0f; - return 120.0f*hz/(timeBase*hl*speedSum); + if (vD<1) vD=1; + return (120.0f*hz/(timeBase*hl*speedSum))*(float)vN/(float)vD; } void FurnaceGUI::play(int row) { @@ -2929,7 +2930,7 @@ bool FurnaceGUI::loop() { if (e->isPlaying()) { int totalTicks=e->getTotalTicks(); int totalSeconds=e->getTotalSeconds(); - ImGui::Text("| Speed %d:%d @ %gHz (%g BPM) | Order %d/%d | Row %d/%d | %d:%.2d:%.2d.%.2d",e->getSpeed1(),e->getSpeed2(),e->getCurHz(),calcBPM(e->getSpeed1(),e->getSpeed2(),e->getCurHz()),e->getOrder(),e->curSubSong->ordersLen,e->getRow(),e->curSubSong->patLen,totalSeconds/3600,(totalSeconds/60)%60,totalSeconds%60,totalTicks/10000); + ImGui::Text("| Speed %d:%d @ %gHz (%g BPM) | Order %d/%d | Row %d/%d | %d:%.2d:%.2d.%.2d",e->getSpeed1(),e->getSpeed2(),e->getCurHz(),calcBPM(e->getSpeed1(),e->getSpeed2(),e->getCurHz(),e->curSubSong->virtualTempoN,e->curSubSong->virtualTempoD),e->getOrder(),e->curSubSong->ordersLen,e->getRow(),e->curSubSong->patLen,totalSeconds/3600,(totalSeconds/60)%60,totalSeconds%60,totalTicks/10000); } else { bool hasInfo=false; String info; diff --git a/src/gui/gui.h b/src/gui/gui.h index 6805d63b9..a99ee90ab 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -1255,7 +1255,7 @@ class FurnaceGUI { void pushAccentColors(const ImVec4& one, const ImVec4& two, const ImVec4& border, const ImVec4& borderShadow); void popAccentColors(); - float calcBPM(int s1, int s2, float hz); + float calcBPM(int s1, int s2, float hz, int vN, int vD); void patternRow(int i, bool isPlaying, float lineHeight, int chans, int ord, const DivPattern** patCache, bool inhibitSel); diff --git a/src/gui/songInfo.cpp b/src/gui/songInfo.cpp index d62abef85..93fe14bc6 100644 --- a/src/gui/songInfo.cpp +++ b/src/gui/songInfo.cpp @@ -82,7 +82,7 @@ void FurnaceGUI::drawSongInfo() { e->curSubSong->timeBase=realTB-1; } ImGui::TableNextColumn(); - ImGui::Text("%.2f BPM",calcBPM(e->curSubSong->speed1,e->curSubSong->speed2,e->curSubSong->hz)); + ImGui::Text("%.2f BPM",calcBPM(e->curSubSong->speed1,e->curSubSong->speed2,e->curSubSong->hz,e->curSubSong->virtualTempoN,e->curSubSong->virtualTempoD)); ImGui::TableNextRow(); ImGui::TableNextColumn(); @@ -100,6 +100,28 @@ void FurnaceGUI::drawSongInfo() { if (e->isPlaying()) play(); } + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Virtual Tempo"); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(avail); + if (ImGui::InputScalar("##VTempoN",ImGuiDataType_S16,&e->curSubSong->virtualTempoN,&_ONE,&_THREE)) { MARK_MODIFIED + if (e->curSubSong->virtualTempoN<1) e->curSubSong->virtualTempoN=1; + if (e->curSubSong->virtualTempoN>255) e->curSubSong->virtualTempoN=255; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Numerator"); + } + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(avail); + if (ImGui::InputScalar("##VTempoD",ImGuiDataType_S16,&e->curSubSong->virtualTempoD,&_ONE,&_THREE)) { MARK_MODIFIED + if (e->curSubSong->virtualTempoD<1) e->curSubSong->virtualTempoD=1; + if (e->curSubSong->virtualTempoD>255) e->curSubSong->virtualTempoD=255; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Denominator (set to base tempo)"); + } + ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::Text("Highlight");