dev96 - add virtual tempo

This commit is contained in:
tildearrow 2022-05-18 00:05:25 -05:00
parent f1ca53561f
commit cfa05143ab
10 changed files with 90 additions and 21 deletions

View file

@ -1,5 +1,6 @@
# to-do for 0.6pre1 # to-do for 0.6pre1
- finish ExtCh on OPN/OPNA
- RF5C68 system - RF5C68 system
- ZX beeper system overlay percussion - ZX beeper system overlay percussion
- ADPCM chips - ADPCM chips

View file

@ -29,6 +29,7 @@ furthermore, an `or reserved` indicates this field is always present, but is res
the format versions are: the format versions are:
- 96: Furnace dev96
- 95: Furnace dev95 - 95: Furnace dev95
- 94: Furnace dev94 - 94: Furnace dev94
- 93: Furnace dev93 - 93: Furnace dev93
@ -296,7 +297,10 @@ size | description
1 | SN duty macro always resets phase (>=86) or reserved 1 | SN duty macro always resets phase (>=86) or reserved
1 | pitch macro is linear (>=90) or reserved 1 | pitch macro is linear (>=90) or reserved
1 | pitch slide speed in full linear pitch mode (>=94) 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) --- | **additional subsongs** (>=95)
STR | first subsong name STR | first subsong name
STR | first subsong comment STR | first subsong comment
@ -328,7 +332,8 @@ size | description
| - the limit is 256. | - the limit is 256.
1 | highlight A 1 | highlight A
1 | highlight B 1 | highlight B
4 | reserved 2 | virtual tempo numerator
2 | virtual tempo denominator
STR | subsong name STR | subsong name
STR | subsong comment STR | subsong comment
??? | orders ??? | orders

View file

@ -1068,6 +1068,7 @@ void DivEngine::playSub(bool preserveDrift, int goalRow) {
endOfSong=false; endOfSong=false;
} else { } else {
ticks=1; ticks=1;
tempoAccum=0;
totalTicks=0; totalTicks=0;
totalSeconds=0; totalSeconds=0;
totalTicksR=0; totalTicksR=0;
@ -2669,6 +2670,7 @@ void DivEngine::quitDispatch() {
speedAB=false; speedAB=false;
endOfSong=false; endOfSong=false;
ticks=0; ticks=0;
tempoAccum=0;
curRow=0; curRow=0;
curOrder=0; curOrder=0;
nextSpeed=3; nextSpeed=3;

View file

@ -45,8 +45,8 @@
#define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock(); #define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock();
#define BUSY_END isBusy.unlock(); softLocked=false; #define BUSY_END isBusy.unlock(); softLocked=false;
#define DIV_VERSION "dev95" #define DIV_VERSION "dev96"
#define DIV_ENGINE_VERSION 95 #define DIV_ENGINE_VERSION 96
// for imports // for imports
#define DIV_VERSION_MOD 0xff01 #define DIV_VERSION_MOD 0xff01
@ -312,6 +312,7 @@ class DivEngine {
int changeOrd, changePos, totalSeconds, totalTicks, totalTicksR, totalCmds, lastCmds, cmdsPerSecond, globalPitch; int changeOrd, changePos, totalSeconds, totalTicks, totalTicksR, totalCmds, lastCmds, cmdsPerSecond, globalPitch;
unsigned char extValue; unsigned char extValue;
unsigned char speed1, speed2; unsigned char speed1, speed2;
short tempoAccum;
DivStatusView view; DivStatusView view;
DivHaltPositions haltOn; DivHaltPositions haltOn;
DivChannelState chan[DIV_MAX_CHANS]; DivChannelState chan[DIV_MAX_CHANS];
@ -942,6 +943,7 @@ class DivEngine {
extValue(0), extValue(0),
speed1(3), speed1(3),
speed2(3), speed2(3),
tempoAccum(0),
view(DIV_STATUS_NOTHING), view(DIV_STATUS_NOTHING),
haltOn(DIV_HALT_NONE), haltOn(DIV_HALT_NONE),
audioEngine(DIV_AUDIO_NULL), audioEngine(DIV_AUDIO_NULL),

View file

@ -1404,11 +1404,19 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
} else { } else {
reader.readC(); reader.readC();
} }
for (int i=0; i<18; i++) { for (int i=0; i<14; i++) {
reader.readC(); reader.readC();
} }
} }
// first song virtual tempo
if (ds.version>=96) {
subSong->virtualTempoN=reader.readS();
subSong->virtualTempoD=reader.readS();
} else {
reader.readI();
}
// subsongs // subsongs
if (ds.version>=95) { if (ds.version>=95) {
subSong->name=reader.readString(); subSong->name=reader.readString();
@ -1457,7 +1465,12 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
subSong->hilightA=reader.readC(); subSong->hilightA=reader.readC();
subSong->hilightB=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->name=reader.readString();
subSong->notes=reader.readString(); subSong->notes=reader.readString();
@ -2858,9 +2871,13 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) {
w->writeC(song.snDutyReset); w->writeC(song.snDutyReset);
w->writeC(song.pitchMacroIsLinear); w->writeC(song.pitchMacroIsLinear);
w->writeC(song.pitchSlideSpeed); w->writeC(song.pitchSlideSpeed);
for (int i=0; i<18; i++) { for (int i=0; i<14; i++) {
w->writeC(0); w->writeC(0);
} }
// first subsong virtual tempo
w->writeS(subSong->virtualTempoN);
w->writeS(subSong->virtualTempoD);
// subsong list // subsong list
w->writeString(subSong->name,false); w->writeString(subSong->name,false);
@ -2891,7 +2908,8 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) {
w->writeS(subSong->ordersLen); w->writeS(subSong->ordersLen);
w->writeC(subSong->hilightA); w->writeC(subSong->hilightA);
w->writeC(subSong->hilightB); w->writeC(subSong->hilightB);
w->writeI(0); // reserved w->writeS(subSong->virtualTempoN);
w->writeS(subSong->virtualTempoD);
w->writeString(subSong->name,false); w->writeString(subSong->name,false);
w->writeString(subSong->notes,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"); 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) { if (sys==DIV_SYSTEM_C64_6581 || sys==DIV_SYSTEM_C64_8580) {
addWarning("absolute duty/cutoff macro not available in .dmf!"); addWarning("absolute duty/cutoff macro not available in .dmf!");
addWarning("duty precision will be lost"); addWarning("duty precision will be lost");

View file

@ -900,16 +900,23 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) {
if (!freelance) { if (!freelance) {
if (--subticks<=0) { if (--subticks<=0) {
subticks=tickMult; subticks=tickMult;
if (stepPlay!=1) if (--ticks<=0) { if (stepPlay!=1) {
ret=endOfSong; tempoAccum+=curSubSong->virtualTempoN;
if (endOfSong) { while (tempoAccum>=curSubSong->virtualTempoD) {
if (song.loopModality!=2) { tempoAccum-=curSubSong->virtualTempoD;
playSub(true); 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 // process stuff
for (int i=0; i<chans; i++) { for (int i=0; i<chans; i++) {

View file

@ -114,6 +114,7 @@ struct DivSubSong {
String name, notes; String name, notes;
unsigned char hilightA, hilightB; unsigned char hilightA, hilightB;
unsigned char timeBase, speed1, speed2, arpLen; unsigned char timeBase, speed1, speed2, arpLen;
short virtualTempoN, virtualTempoD;
bool pal; bool pal;
bool customTempo; bool customTempo;
float hz; float hz;
@ -136,6 +137,8 @@ struct DivSubSong {
speed1(6), speed1(6),
speed2(6), speed2(6),
arpLen(1), arpLen(1),
virtualTempoN(150),
virtualTempoD(150),
pal(true), pal(true),
customTempo(false), customTempo(false),
hz(60.0), hz(60.0),

View file

@ -792,14 +792,15 @@ void FurnaceGUI::prepareLayout() {
fclose(check); fclose(check);
} }
float FurnaceGUI::calcBPM(int s1, int s2, float hz) { float FurnaceGUI::calcBPM(int s1, int s2, float hz, int vN, int vD) {
float hl=e->curSubSong->hilightA; float hl=e->curSubSong->hilightA;
if (hl<=0.0f) hl=4.0f; if (hl<=0.0f) hl=4.0f;
float timeBase=e->curSubSong->timeBase+1; float timeBase=e->curSubSong->timeBase+1;
float speedSum=s1+s2; float speedSum=s1+s2;
if (timeBase<1.0f) timeBase=1.0f; if (timeBase<1.0f) timeBase=1.0f;
if (speedSum<1.0f) speedSum=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) { void FurnaceGUI::play(int row) {
@ -2929,7 +2930,7 @@ bool FurnaceGUI::loop() {
if (e->isPlaying()) { if (e->isPlaying()) {
int totalTicks=e->getTotalTicks(); int totalTicks=e->getTotalTicks();
int totalSeconds=e->getTotalSeconds(); 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 { } else {
bool hasInfo=false; bool hasInfo=false;
String info; String info;

View file

@ -1255,7 +1255,7 @@ class FurnaceGUI {
void pushAccentColors(const ImVec4& one, const ImVec4& two, const ImVec4& border, const ImVec4& borderShadow); void pushAccentColors(const ImVec4& one, const ImVec4& two, const ImVec4& border, const ImVec4& borderShadow);
void popAccentColors(); 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); void patternRow(int i, bool isPlaying, float lineHeight, int chans, int ord, const DivPattern** patCache, bool inhibitSel);

View file

@ -82,7 +82,7 @@ void FurnaceGUI::drawSongInfo() {
e->curSubSong->timeBase=realTB-1; e->curSubSong->timeBase=realTB-1;
} }
ImGui::TableNextColumn(); 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::TableNextRow();
ImGui::TableNextColumn(); ImGui::TableNextColumn();
@ -100,6 +100,28 @@ void FurnaceGUI::drawSongInfo() {
if (e->isPlaying()) play(); 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::TableNextRow();
ImGui::TableNextColumn(); ImGui::TableNextColumn();
ImGui::Text("Highlight"); ImGui::Text("Highlight");