diff --git a/CMakeLists.txt b/CMakeLists.txt index eff3bd2f1..d37334bdd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -554,6 +554,7 @@ set(ENGINE_SOURCES src/log.cpp src/baseutils.cpp src/fileutils.cpp +src/timeutils.cpp src/utfutils.cpp extern/itcompress/compression.c diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index e7804fc83..1c94ad275 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -2171,7 +2171,7 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { playPosLock.unlock(); // also set the playback position and sync file player if necessary - DivSongTimestamps::Timestamp rowTS=curSubSong->ts.getTimes(curOrder,curRow); + TimeMicros rowTS=curSubSong->ts.getTimes(curOrder,curRow); totalSeconds=rowTS.seconds; totalTicks=rowTS.micros; if (curFilePlayer && filePlayerSync) { @@ -3109,7 +3109,7 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi // if file player is synchronized then set its position to that of the loop row if (curFilePlayer && filePlayerSync) { if (curFilePlayer->isPlaying()) { - DivSongTimestamps::Timestamp rowTS=curSubSong->ts.loopStartTime; + TimeMicros rowTS=curSubSong->ts.loopStartTime; int finalSeconds=rowTS.seconds+filePlayerCueSeconds; int finalMicros=rowTS.micros+filePlayerCueMicros; diff --git a/src/engine/song.cpp b/src/engine/song.cpp index 0da55424a..9d70c5283 100644 --- a/src/engine/song.cpp +++ b/src/engine/song.cpp @@ -21,11 +21,11 @@ #include "../ta-log.h" #include -DivSongTimestamps::Timestamp DivSongTimestamps::getTimes(int order, int row) { - if (order<0 || order>=DIV_MAX_PATTERNS) return Timestamp(-1,0); - if (row<0 || row>=DIV_MAX_ROWS) return Timestamp(-1,0); - Timestamp* t=orders[order]; - if (t==NULL) return Timestamp(-1,0); +TimeMicros DivSongTimestamps::getTimes(int order, int row) { + if (order<0 || order>=DIV_MAX_PATTERNS) return TimeMicros(-1,0); + if (row<0 || row>=DIV_MAX_ROWS) return TimeMicros(-1,0); + TimeMicros* t=orders[order]; + if (t==NULL) return TimeMicros(-1,0); return t[row]; } @@ -391,12 +391,12 @@ void DivSubSong::calcTimestamps(int chans, std::vector& groove // log row time here if (rowChanged && !endOfSong) { if (ts.orders[prevOrder]==NULL) { - ts.orders[prevOrder]=new DivSongTimestamps::Timestamp[DIV_MAX_ROWS]; + ts.orders[prevOrder]=new TimeMicros[DIV_MAX_ROWS]; for (int i=0; igetFilePlayer(); logV("cursor moved to %d:%d",cursor.order,cursor.y); if (!fp->isPlaying()) { - DivSongTimestamps::Timestamp rowTS=e->curSubSong->ts.getTimes(cursor.order,cursor.y); + TimeMicros rowTS=e->curSubSong->ts.getTimes(cursor.order,cursor.y); if (rowTS.seconds!=-1) { int cueSeconds=0; int cueMicros=0; diff --git a/src/gui/pattern.cpp b/src/gui/pattern.cpp index bd19d5f01..aeea36b63 100644 --- a/src/gui/pattern.cpp +++ b/src/gui/pattern.cpp @@ -408,7 +408,7 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int } if (debugRowTimestamps) { - DivSongTimestamps::Timestamp rowTS=e->curSubSong->ts.getTimes(ord,i); + TimeMicros rowTS=e->curSubSong->ts.getTimes(ord,i); if (rowTS.seconds==-1) { ImGui::Text("---"); } else { diff --git a/src/gui/refPlayer.cpp b/src/gui/refPlayer.cpp index aec5a0a3d..de0884078 100644 --- a/src/gui/refPlayer.cpp +++ b/src/gui/refPlayer.cpp @@ -106,7 +106,7 @@ void FurnaceGUI::drawRefPlayer() { ssize_t curSeconds=0; unsigned int curMicros=0; fp->getPosSeconds(curSeconds,curMicros); - DivSongTimestamps::Timestamp rowTS=e->curSubSong->ts.getTimes(curOrder,0); + TimeMicros rowTS=e->curSubSong->ts.getTimes(curOrder,0); if (rowTS.seconds==-1) { showError(_("the first row of this order isn't going to play.")); } else { @@ -168,7 +168,7 @@ void FurnaceGUI::drawRefPlayer() { ssize_t curSeconds=0; unsigned int curMicros=0; fp->getPosSeconds(curSeconds,curMicros); - DivSongTimestamps::Timestamp rowTS=e->curSubSong->ts.getTimes(curOrder,0); + TimeMicros rowTS=e->curSubSong->ts.getTimes(curOrder,0); if (rowTS.seconds==-1) { showError(_("the first row of this order isn't going to play.")); } else { @@ -197,7 +197,7 @@ void FurnaceGUI::drawRefPlayer() { // handled outside } if (ImGui::IsItemClicked(ImGuiMouseButton_Left) || ImGui::IsItemClicked(ImGuiMouseButton_Right)) { - DivSongTimestamps::Timestamp rowTS; + TimeMicros rowTS; if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { rowTS=e->curSubSong->ts.getTimes(cursor.order,cursor.y); } else { diff --git a/src/timeutils.cpp b/src/timeutils.cpp new file mode 100644 index 000000000..0c9c058d8 --- /dev/null +++ b/src/timeutils.cpp @@ -0,0 +1,107 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2025 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "timeutils.h" +#include + +String TimeMicros::toString(signed char prec, unsigned char hms) { + String ret; + int actualSeconds=seconds; + int actualMicros=micros; + bool negative=(seconds<0); + if (seconds<0 && micros!=0) { + actualSeconds++; + actualMicros=1000000-micros; + } + if (negative) actualSeconds=-actualSeconds; + + switch (hms) { + case 2: { // h:mm:ss + int h=actualSeconds/3600; + int m=(actualSeconds/60)%60; + int s=actualSeconds%60; + + if (negative) { + ret=fmt::sprintf("-%d:%02d:%02d",h,m,s); + } else { + ret=fmt::sprintf("%d:%02d:%02d",h,m,s); + } + break; + } + case 1: { // m:ss + int m=actualSeconds/60; + int s=actualSeconds%60; + + if (negative) { + ret=fmt::sprintf("-%d:%02d",m,s); + } else { + ret=fmt::sprintf("%d:%02d",m,s); + } + break; + } + default: { // seconds + if (negative) { + ret=fmt::sprintf("-%d",actualSeconds); + } else { + ret=fmt::sprintf("%d",actualSeconds); + } + break; + } + } + + if (prec<0) { + // automatic precision + prec=6; + int precMicros=actualMicros; + while ((precMicros%10)==0 && prec>1) { + prec--; + precMicros/=10; + } + } + + if (prec>0) { + if (prec>6) prec=6; + switch (prec) { + case 1: + ret+=fmt::sprintf(".%01d",actualMicros/100000); + break; + case 2: + ret+=fmt::sprintf(".%02d",actualMicros/10000); + break; + case 3: + ret+=fmt::sprintf(".%03d",actualMicros/1000); + break; + case 4: + ret+=fmt::sprintf(".%04d",actualMicros/100); + break; + case 5: + ret+=fmt::sprintf(".%05d",actualMicros/10); + break; + case 6: + ret+=fmt::sprintf(".%06d",actualMicros); + break; + } + } + + return ret; +} + +TimeMicros TimeMicros::fromString(String s) { + return TimeMicros(0,0); +} diff --git a/src/timeutils.h b/src/timeutils.h new file mode 100644 index 000000000..0bf07ccf5 --- /dev/null +++ b/src/timeutils.h @@ -0,0 +1,61 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2025 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef _TIMEUTILS_H +#define _TIMEUTILS_H + +#ifdef __unix__ +#include +#include +#endif + +#include "ta-utils.h" + +struct TimeMicros { + int seconds, micros; + + inline float toFloat() { + return seconds+(float)micros/1000000.0f; + } + inline double toDouble() { + return seconds+(double)micros/1000000.0; + } + + /** + * convert this TimeMicros to a String. + * @param prec maximum digit precision (0-6). use -1 for automatic precision. + * @param hms how many denominations to include (0: seconds, 1: minutes, 2: hours). + * @return formatted TimeMicros. + */ + String toString(signed char prec=-1, unsigned char hms=0); + static TimeMicros fromString(String s); + + TimeMicros(int s, int u): + seconds(s), micros(u) {} +#ifdef __unix__ + TimeMicros(struct timeval tv): + seconds(tv.tv_sec), micros(tv.tv_usec) {} + TimeMicros(struct timespec ts): + seconds(ts.tv_sec), micros(ts.tv_nsec/1000) {} +#endif + TimeMicros(): + seconds(0), micros(0) {} +}; + +#endif