heuristic for song length determination

song length calculation of lorge.fur now takes 18ms (from ~1 hour)
This commit is contained in:
tildearrow 2025-11-09 20:42:38 -05:00
parent 61ac87d505
commit 9e8c97cc6f
6 changed files with 53 additions and 14 deletions

View file

@ -264,6 +264,19 @@ double DivEngine::benchmarkSeek() {
return tAvg;
}
double DivEngine::benchmarkWalk() {
std::chrono::high_resolution_clock::time_point timeStart=std::chrono::high_resolution_clock::now();
// benchmark
calcSongTimestamps();
std::chrono::high_resolution_clock::time_point timeEnd=std::chrono::high_resolution_clock::now();
double t=(double)(std::chrono::duration_cast<std::chrono::microseconds>(timeEnd-timeStart).count())/1000000.0;
printf("[RESULT] %fs\n",t);
return t;
}
void DivEngine::notifyInsChange(int ins) {
BUSY_BEGIN;
for (int i=0; i<song.systemLen; i++) {

View file

@ -821,6 +821,7 @@ class DivEngine {
// benchmark (returns time in seconds)
double benchmarkPlayback();
double benchmarkSeek();
double benchmarkWalk();
// returns the minimum VGM version which may carry the specified system, or 0 if none.
int minVGMVersion(DivSystem which);

View file

@ -19,6 +19,7 @@
#include "song.h"
#include "../ta-log.h"
#include <inttypes.h>
#include <chrono>
TimeMicros DivSongTimestamps::getTimes(int order, int row) {
@ -347,22 +348,40 @@ void DivSubSong::calcTimestamps(int chans, std::vector<DivGroovePattern>& groove
// MAKE IT WORK
while (!endOfSong) {
// heuristic
int advance=(curVirtualTempoD*ticks)/curVirtualTempoN;
for (int i=0; i<chans; i++) {
if (rowDelay[i]>0) {
if (rowDelay[i]<advance) advance=rowDelay[i];
}
}
if (advance<1) advance=1;
//logV("tick %" PRIu64 " advance: %d",ts.totalTicks,advance);
// cycle channels to find a tick rate/tempo change effect after delay
// (unfortunately Cxxx and F0xx are not pre-effects and obey EDxx)
for (int i=0; i<chans; i++) {
if (rowDelay[i]>0) {
if (--rowDelay[i]==0) {
rowDelay[i]-=advance;
if (rowDelay[i]==0) {
tinyProcessRow(i,true);
}
}
}
// run virtual tempo
tempoAccum+=curVirtualTempoN;
tempoAccum+=curVirtualTempoN*advance;
while (tempoAccum>=curVirtualTempoD) {
tempoAccum-=curVirtualTempoD;
int ticksToRun=tempoAccum/curVirtualTempoD;
tempoAccum%=curVirtualTempoD;
// tick counter
if (--ticks<=0) {
ticks-=ticksToRun;
if (ticks<0) {
// if ticks is negative, we must call ticks back
tempoAccum+=-ticks*curVirtualTempoD;
}
if (ticks<=0) {
if (shallStopSched) {
shallStop=true;
break;
@ -400,8 +419,8 @@ void DivSubSong::calcTimestamps(int chans, std::vector<DivGroovePattern>& groove
if (!endOfSong) {
// update playback time
double dt=divider;//*((double)virtualTempoN/(double)MAX(1,virtualTempoD));
ts.totalTicks++;
double dt=divider/(double)advance;//*((double)virtualTempoN/(double)MAX(1,virtualTempoD));
ts.totalTicks+=advance;
ts.totalTime.micros+=1000000/dt;
totalMicrosOff+=fmod(1000000.0,dt);
@ -410,9 +429,10 @@ void DivSubSong::calcTimestamps(int chans, std::vector<DivGroovePattern>& groove
ts.totalTime.micros++;
}
if (ts.totalTime.micros>=1000000) {
ts.totalTime.micros-=1000000;
// who's gonna play a song for 68 years?
if (ts.totalTime.seconds<0x7fffffff) ts.totalTime.seconds++;
ts.totalTime.seconds+=ts.totalTime.micros/1000000;
if (ts.totalTime.seconds<0) ts.totalTime.seconds=INT_MAX;
ts.totalTime.micros%=1000000;
}
}
if (ts.maxRow[curOrder]<curRow) ts.maxRow[curOrder]=curRow;
@ -425,6 +445,7 @@ void DivSubSong::calcTimestamps(int chans, std::vector<DivGroovePattern>& groove
std::chrono::high_resolution_clock::time_point timeEnd=std::chrono::high_resolution_clock::now();
logV("calcTimestamps() took %dµs",std::chrono::duration_cast<std::chrono::microseconds>(timeEnd-timeStart).count());
logV("song length: %s; %" PRIu64 " ticks",ts.totalTime.toString(6,TA_TIME_FORMAT_AUTO),ts.totalTicks);
}
void DivSubSong::clearData() {

View file

@ -171,7 +171,7 @@ struct DivGroovePattern {
struct DivSongTimestamps {
// song duration (in seconds and microseconds)
TimeMicros totalTime;
int totalTicks;
uint64_t totalTicks;
int totalRows;
// loop region (order/row positions)

View file

@ -21,7 +21,7 @@
#include "guiConst.h"
#include "debug.h"
#include "IconsFontAwesome4.h"
#include <SDL_timer.h>
#include <inttypes.h>
#include <fmt/printf.h>
#include "imgui.h"
#include "imgui_internal.h"
@ -208,7 +208,7 @@ void FurnaceGUI::drawDebug() {
DivSongTimestamps& ts=e->curSubSong->ts;
String timeFormatted=ts.totalTime.toString(-1,TA_TIME_FORMAT_AUTO);
ImGui::Text("song duration: %s (%d ticks; %d rows)",timeFormatted.c_str(),ts.totalTicks,ts.totalRows);
ImGui::Text("song duration: %s (%" PRIu64 " ticks; %d rows)",timeFormatted.c_str(),ts.totalTicks,ts.totalRows);
if (ts.isLoopDefined) {
ImGui::Text("loop region is defined");
} else {

View file

@ -539,8 +539,10 @@ TAParamResult pBenchmark(String val) {
benchMode=1;
} else if (val=="seek") {
benchMode=2;
} else if (val=="walk") {
benchMode=3;
} else {
logE("invalid value for benchmark! valid values are: render and seek.");
logE("invalid value for benchmark! valid values are: render, seek and walk.");
return TA_PARAM_ERROR;
}
e.setAudio(DIV_AUDIO_DUMMY);
@ -653,7 +655,7 @@ void initParams() {
params.push_back(TAParam("S","safemode",false,pSafeMode,"","enable safe mode (software rendering and no audio)"));
params.push_back(TAParam("A","safeaudio",false,pSafeModeAudio,"","enable safe mode (with audio"));
params.push_back(TAParam("B","benchmark",true,pBenchmark,"render|seek","run performance test"));
params.push_back(TAParam("B","benchmark",true,pBenchmark,"render|seek|walk","run performance test"));
params.push_back(TAParam("V","version",false,pVersion,"","view information about Furnace."));
params.push_back(TAParam("W","warranty",false,pWarranty,"","view warranty disclaimer."));
@ -1031,7 +1033,9 @@ int main(int argc, char** argv) {
if (benchMode) {
logI("starting benchmark!");
if (benchMode==2) {
if (benchMode==3) {
e.benchmarkWalk();
} else if (benchMode==2) {
e.benchmarkSeek();
} else {
e.benchmarkPlayback();