From e8aeb45a12d4a42c70aa5b256d872e5e51e0a5c9 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 31 Oct 2025 03:42:43 -0500 Subject: [PATCH] TimeMicros::fromString() and improve the cue position editor a bit --- src/gui/debugWindow.cpp | 20 ++++ src/gui/gui.cpp | 3 + src/gui/gui.h | 5 + src/gui/refPlayer.cpp | 29 +++--- src/timeutils.cpp | 214 +++++++++++++++++++++++++++++++++++++++- src/timeutils.h | 2 +- 6 files changed, 259 insertions(+), 14 deletions(-) diff --git a/src/gui/debugWindow.cpp b/src/gui/debugWindow.cpp index 63fc23973..1b3ba9259 100644 --- a/src/gui/debugWindow.cpp +++ b/src/gui/debugWindow.cpp @@ -25,6 +25,7 @@ #include #include "imgui.h" #include "imgui_internal.h" +#include "misc/cpp/imgui_stdlib.h" PendingDrawOsc _debugDo; static float oscDebugData[2048]; @@ -369,6 +370,25 @@ void FurnaceGUI::drawDebug() { ImGui::Unindent(); ImGui::TreePop(); } + if (ImGui::TreeNode("TimeMicros Test")) { + static TimeMicros testTS; + static String testTSIn; + String testTSFormatted=testTS.toString(); + ImGui::Text("Current Value: %s",testTSFormatted.c_str()); + + if (ImGui::InputText("fromString",&testTSIn)) { + try { + testTS=TimeMicros::fromString(testTSIn); + } catch (std::invalid_argument& e) { + ImGui::Text("COULD NOT! (%s)",e.what()); + } + } + + ImGui::InputInt("seconds",&testTS.seconds); + ImGui::InputInt("micros",&testTS.micros); + + ImGui::TreePop(); + } if (ImGui::TreeNode("New File Picker Test")) { static bool check0, check1, check2, check3, check4, check5; diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index a4081350f..8cf76bfa7 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -9069,6 +9069,9 @@ FurnaceGUI::FurnaceGUI(): xyOscDecayTime(10.0f), xyOscIntensity(2.0f), xyOscThickness(2.0f), + fpCueInput(""), + fpCueInputFailed(false), + fpCueInputFailReason(""), followLog(true), #ifdef IS_MOBILE pianoOctaves(7), diff --git a/src/gui/gui.h b/src/gui/gui.h index c606c07fd..77df3318c 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -2734,6 +2734,11 @@ class FurnaceGUI { float keyHit1[DIV_MAX_CHANS]; int lastIns[DIV_MAX_CHANS]; + // file player temp variables + String fpCueInput; + bool fpCueInputFailed; + String fpCueInputFailReason; + // log window bool followLog; diff --git a/src/gui/refPlayer.cpp b/src/gui/refPlayer.cpp index be2f12067..f7f46cd2c 100644 --- a/src/gui/refPlayer.cpp +++ b/src/gui/refPlayer.cpp @@ -21,6 +21,7 @@ #include #include "imgui.h" #include "IconsFontAwesome4.h" +#include "misc/cpp/imgui_stdlib.h" void FurnaceGUI::drawRefPlayer() { DivFilePlayer* fp=e->getFilePlayer(); @@ -112,19 +113,25 @@ void FurnaceGUI::drawRefPlayer() { ImGui::TextUnformatted(_("Set cue position at first order:")); TimeMicros cueTime=e->getFilePlayerCue(); bool altered=false; - // TODO: improve this... - ImGui::SetNextItemWidth(240.0f*dpiScale); - if (ImGui::InputInt(_("Seconds##CuePosS"),&cueTime.seconds)) { - if (cueTime.seconds<-3600) cueTime.seconds=-3600; - if (cueTime.seconds>3600) cueTime.seconds=3600; - altered=true; + fpCueInput=cueTime.toString(-1,TA_TIME_FORMAT_AUTO); + pushWarningColor(false,fpCueInputFailed); + if (ImGui::InputText("##CuePos",&fpCueInput)) { + try { + cueTime=TimeMicros::fromString(fpCueInput); + altered=true; + fpCueInputFailed=false; + } catch (std::invalid_argument& e) { + fpCueInputFailed=true; + fpCueInputFailReason=e.what(); + } } - ImGui::SetNextItemWidth(240.0f*dpiScale); - if (ImGui::InputInt(_("Microseconds##CuePosM"),&cueTime.micros,1000,10000)) { - if (cueTime.micros<0) cueTime.micros=0; - if (cueTime.micros>999999) cueTime.micros=999999; - altered=true; + if (!ImGui::IsItemActive()) { + fpCueInputFailed=false; } + if (ImGui::IsItemHovered() && fpCueInputFailed) { + ImGui::SetTooltip("%s",fpCueInputFailReason.c_str()); + } + popWarningColor(); if (altered) { e->setFilePlayerCue(cueTime); } diff --git a/src/timeutils.cpp b/src/timeutils.cpp index 158fdd42c..8a0e27649 100644 --- a/src/timeutils.cpp +++ b/src/timeutils.cpp @@ -19,6 +19,7 @@ #include "timeutils.h" #include +#include String TimeMicros::toString(signed char prec, TATimeFormats hms) { String ret; @@ -189,6 +190,215 @@ String TimeMicros::toString(signed char prec, TATimeFormats hms) { return ret; } -TimeMicros TimeMicros::fromString(String s) { - return TimeMicros(0,0); +TimeMicros TimeMicros::fromString(const String& s) { + bool seenYears=false; + bool seenMonths=false; + bool seenDays=false; + bool seenDecimal=false; + bool needSomething=true; + bool canNegative=true; + bool isNegative=false; + unsigned int element[4]; + unsigned int elementDigits[4]; + int elementCount=0; + int curElement=0; + int curElementDigits=0; + + int years=0; + int months=0; + int days=0; + + element[0]=0; + element[1]=0; + element[2]=0; + element[3]=0; + elementDigits[0]=0; + elementDigits[1]=0; + elementDigits[2]=0; + elementDigits[3]=0; + + for (char i: s) { + switch (i) { + // numbers + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + if (curElementDigits<9) { + curElement*=10; + curElement+=(i-'0'); + curElementDigits++; + needSomething=false; + } else { + throw std::invalid_argument("value out of range"); + } + break; + + // negative indicator + case '-': + if (!canNegative) { + throw std::invalid_argument("negative symbol shall be at the beginning"); + } + isNegative=true; + break; + + // element separator + case ':': + if (needSomething) { + throw std::invalid_argument("missing value"); + } + if (seenDecimal) { + throw std::invalid_argument("after decimal"); + } + if (elementCount>=3) { + throw std::invalid_argument("too many elements"); + } + element[elementCount]=curElement; + elementDigits[elementCount++]=curElementDigits; + curElement=0; + curElementDigits=0; + break; + + // decimal separator + case '.': case ',': + if (needSomething && elementCount!=0) { + throw std::invalid_argument("wrong decimal separator"); + } + if (elementCount>=3) { + throw std::invalid_argument("too many elements"); + } + element[elementCount]=curElement; + elementDigits[elementCount++]=curElementDigits; + curElement=0; + curElementDigits=0; + needSomething=true; + seenDecimal=true; + break; + + // year/month/day marker + case 'Y': case 'y': + if (seenYears) { + throw std::invalid_argument("already have years"); + } + if (elementCount!=0) { + throw std::invalid_argument("years must be before time"); + } + years=curElement; + curElement=0; + curElementDigits=0; + needSomething=true; + seenYears=true; + break; + case 'M': case 'm': + if (seenMonths) { + throw std::invalid_argument("already have months"); + } + if (elementCount!=0) { + throw std::invalid_argument("months must be before time"); + } + months=curElement; + curElement=0; + curElementDigits=0; + needSomething=true; + seenMonths=true; + break; + case 'D': case 'd': + if (seenDays) { + throw std::invalid_argument("already have days"); + } + if (elementCount!=0) { + throw std::invalid_argument("days must be before time"); + } + days=curElement; + curElement=0; + curElementDigits=0; + needSomething=true; + seenDays=true; + break; + + // ignore spaces + case ' ': + break; + + // fail if any other character is seen + default: + throw std::invalid_argument("invalid character"); + break; + } + canNegative=false; + } + + // fail if you didn't provide an element + if (needSomething) { + throw std::invalid_argument("missing value at the end"); + } + + element[elementCount]=curElement; + elementDigits[elementCount]=curElementDigits; + + // safety checks (yeah I know these are a bit off but whatever) + if (years>68 || months>828 || (years*365+months*30+days)>24855) { + throw std::invalid_argument("years/months/days out of range"); + } + + // now combine elements + TimeMicros ret; + ret.seconds=86400*(years*365+months*30+days); + if (seenDecimal || elementCount==3) { + if (elementDigits[elementCount]<6) { + for (int i=elementDigits[elementCount]; i<6; i++) { + element[elementCount]*=10; + } + } else if (elementDigits[elementCount]>6) { + for (int i=elementDigits[elementCount]; i>6; i--) { + element[elementCount]/=10; + } + } + ret.micros=element[elementCount]; + + elementCount--; + } + switch (elementCount) { + case 0: // seconds only + ret.seconds+=element[0]; + break; + case 1: // minutes and seconds + if (element[0]>=35791394) { + throw std::invalid_argument("minutes out of range"); + } + if (element[1]>=60) { + throw std::invalid_argument("seconds out of range"); + } + ret.seconds+=(element[0]*60)+element[1]; + break; + case 2: // hours, minutes and seconds + if (seenYears || seenMonths || seenDays) { + if (element[0]>=24) { + throw std::invalid_argument("hours out of range"); + } + } else { + if (element[0]>=596523) { + throw std::invalid_argument("hours out of range"); + } + } + if (element[1]>=60) { + throw std::invalid_argument("minutes out of range"); + } + if (element[2]>=60) { + throw std::invalid_argument("seconds out of range"); + } + ret.seconds+=(element[0]*3600)+(element[1]*60)+element[2]; + break; + default: + throw std::invalid_argument("shouldn't happen"); + break; + } + + if (isNegative) { + ret.seconds=-ret.seconds; + if (ret.micros!=0) { + ret.micros=1000000-ret.micros; + ret.seconds--; + } + } + + return ret; } diff --git a/src/timeutils.h b/src/timeutils.h index 7da4f92d8..48b1d3cbe 100644 --- a/src/timeutils.h +++ b/src/timeutils.h @@ -110,7 +110,7 @@ struct TimeMicros { * @return formatted TimeMicros. */ String toString(signed char prec=6, TATimeFormats hms=TA_TIME_FORMAT_SECONDS); - static TimeMicros fromString(String s); + static TimeMicros fromString(const String& s); TimeMicros(int s, int u): seconds(s), micros(u) {}