From 55b39ae592e70a185e76a787c2ba293010dcf4e0 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Tue, 27 May 2025 20:43:52 +0200 Subject: [PATCH] =?UTF-8?q?clean-up:=20retract=20usages=20of=20=C2=BBtime?= =?UTF-8?q?=20component=20access=C2=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit While these function may seem superficially plausible, I more and more come to the conclusion that offering such function as ''basic building blocks'' is in itself an ill-guided approach to handling of time entities. Time is neither „just a number“ — nor does it „contain“ hours, minutes and seconds. It is possible to ''represent'' it through a **time-code**, which incurs a quantisation step and implies a reference grid. Thus Lumiera ''should not offer'' a »basic time handling library«. Doing so would be just an invitation to bypass proper time handling and avoid the use of more demanding but also more adequate mental concepts. So the next step will be to remove functions not deemed adequate, and better directly inline the respective modulus based computations. Other functions can be integrated into the respective implementation translation units for time quantisation and timecode representation. --- src/lib/time.h | 1 + src/lib/time/diagnostics.hpp | 78 ----- src/lib/time/digxel.hpp | 10 +- src/lib/time/dropframe.hpp | 227 ++++++++++++++ src/lib/time/timecode.cpp | 28 +- src/lib/time/timecode.hpp | 16 +- src/stage/widget/timecode-widget.cpp | 23 +- tests/11time.tests | 10 - tests/25fundamental.tests | 6 +- tests/basics/time/time-basics-test.cpp | 93 +++--- tests/basics/time/time-dropframe-test.cpp | 185 ++++++++++++ tests/library/c-lib/test-time.c | 56 +--- wiki/thinkPad.ichthyo.mm | 347 ++++++++++++++++++++-- 13 files changed, 837 insertions(+), 243 deletions(-) delete mode 100644 src/lib/time/diagnostics.hpp create mode 100644 src/lib/time/dropframe.hpp create mode 100644 tests/basics/time/time-dropframe-test.cpp diff --git a/src/lib/time.h b/src/lib/time.h index 17fa1ee0f..8996aa3ce 100644 --- a/src/lib/time.h +++ b/src/lib/time.h @@ -3,6 +3,7 @@ Copyright (C) 2008, Christian Thaeter + 2010, Stefan Kangas   **Lumiera** is free software; you can redistribute it and/or modify it   under the terms of the GNU General Public License as published by the diff --git a/src/lib/time/diagnostics.hpp b/src/lib/time/diagnostics.hpp deleted file mode 100644 index 1a071e3c9..000000000 --- a/src/lib/time/diagnostics.hpp +++ /dev/null @@ -1,78 +0,0 @@ -/* - DIAGNOSTICS.hpp - diagnostics and output helpers for time(code) values - - Copyright (C) - 2011, Hermann Vosseler - -  **Lumiera** 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. See the file COPYING for further details. - -*/ - - -/** @file time/diagnostics.hpp - ** Extension to the lib::time::Time wrapper, adding output and further - ** diagnostic aids. This shouldn't be confused with formatting into - ** distinctive \em Timecode formats. There is an elaborate framework - ** for the latter: basically you'd need first to create a frame quantised - ** time value (QuTime) -- from there you can build various timecode - ** representations. - ** - ** To the contrary, the purpose of this header is to help with debugging, - ** writing unit tests and similar diagnostic activities. - ** - ** @see timevalue.hpp - ** @see lumitime-test.cpp - ** - */ - - - - - -#ifndef LIB_TIME_DIAGNOSTICS_H -#define LIB_TIME_DIAGNOSTICS_H - -#include "lib/time/timevalue.hpp" -#include "lib/time.h" - -#include - - -namespace lib { -namespace time { - - - - /* === H:M:S:mm component diagnostics === */ - - inline int - getHours (TimeValue const& t) - { - return lumiera_time_hours (_raw(t)); - } - - inline int - getMins (TimeValue const& t) - { - return lumiera_time_minutes (_raw(t)); - } - - inline int - getSecs (TimeValue const& t) - { - return lumiera_time_seconds (_raw(t)); - } - - inline int - getMillis (TimeValue t) - { - return lumiera_time_millis (_raw(t)); - } - - - -}} // lib::time -#endif diff --git a/src/lib/time/digxel.hpp b/src/lib/time/digxel.hpp index 9d76ae84d..9b70750dc 100644 --- a/src/lib/time/digxel.hpp +++ b/src/lib/time/digxel.hpp @@ -305,12 +305,12 @@ namespace time { /* == predefined Digxel configurations == */ - typedef Digxel< int, digxel::SexaFormatter> SexaDigit; ///< for displaying time components (sexagesimal) - typedef Digxel HexaDigit; ///< for displaying a hex byte - typedef Digxel< int, digxel::HourFormatter> HourDigit; ///< for displaying hours in H:M.S + using SexaDigit = Digxel< int, digxel::SexaFormatter>; ///< for displaying time components (sexagesimal) + using HexaDigit = Digxel; ///< for displaying a hex byte + using HourDigit = Digxel< int, digxel::HourFormatter>; ///< for displaying hours in H:M:S - typedef int64_t FrameCnt; - typedef Digxel CountVal; ///< for displaying a counter + using FrameCnt = int64_t; + using CountVal = Digxel; ///< for displaying a counter /** special Digxel to show a sign. diff --git a/src/lib/time/dropframe.hpp b/src/lib/time/dropframe.hpp new file mode 100644 index 000000000..1ef9e39e1 --- /dev/null +++ b/src/lib/time/dropframe.hpp @@ -0,0 +1,227 @@ +/* + DROPFRAME.hpp - Utilities for handling time + + Copyright (C) + 2010, Stefan Kangas + +  **Lumiera** 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. See the file COPYING for further details. +*/ + +/** @file dropframe.hpp + ** Common functions for handling of time values. + ** Working with time values in sexagesimal format, quantising time and converting + ** to/from common timecode formats can be tricky to get right. Thus the goal is + ** to concentrate the actual bits of math for these operations into a small number + ** of library functions, which are easy to test thoroughly in isolation. + ** + ** Built on top of that, the actual time handling in the GUI and within the Lumiera + ** session is mostly confined to use the opaque lib::time::Time wrapper objects. + ** When time values actually need to be _quantised_ (aligned to a frame grid), + ** this is expressed at the API through using the lib::time::QuTime type, which + ** then in turn can be materialised into a number of _timecode formats_. + ** These definitions ensure that whenever an actual quantisation (rounding) + ** operation is performed, the link to the appropriate time grid is available, + ** so that multiple output or rendering operations can use differing time origins + ** and frame rates simultaneously on the same model. + ** + ** The Lumiera Vault Layer functions mostly operate on raw frame counts, which in + ** this model are defined to be a special kind of timecode, and thus dependent on + ** a preceding time quantisation. + ** + ** @deprecated 2025 this should not be a "simple" C library set aside from the Lumiera + ** time handling framework, rather it should be clarified that these are + ** implementation helpers and must not be used by any application code. + ** It should be checked which of these functions actually need to be + ** exposed through an interface header, since these are typically + ** used to implement parts of the time handling framework. + ** + ** @see lib::time::Time + ** @see timequant.hpp + ** @see TimeValue_test + ** + */ + + +#ifndef LUMIERA_TIME_DROPFRAME_H +#define LUMIERA_TIME_DROPFRAME_H + +#include + +#ifdef __cplusplus /*=================== C++ facilities ===================== */ +#include "lib/time/timevalue.hpp" + +using lib::time::raw_time_64; + + +/** + * Converts a fraction of seconds to Lumiera's internal opaque time scale. + * @param fractionalSeconds given as rational number + * @note inconsistent with Lumiera's general quantisation behaviour, + * here negative fractional micro-ticks are truncated towards zero. + * This was deemed irrelevant in practice. + */ +raw_time_64 +lumiera_rational_to_time (lib::time::FSecs const& fractionalSeconds); + + +/** + * Converts a frame count into Lumiera's internal time scale. + * based on a framerate given as rational number (e.g. NTSC) + * @note handles only positive frame counts and assumes the + * origin to be at zero. + */ +raw_time_64 +lumiera_framecount_to_time (uint64_t frameCount, lib::time::FrameRate const& fps); + + +/** + * Calculates the duration of one frame in Lumiera time units. + * @param framerate underlying framerate as rational number + * @throw error::Logic on zero framerate + */ +raw_time_64 +lumiera_frame_duration (lib::time::FrameRate const& fps); + + + + + +extern "C" { /* ===================== C interface ======================== */ +#endif + + +/** + * Quantise the given time into a fixed grid, relative to the origin. + * The time grid used for quantisation is comprised of equally spaced intervals, + * rooted at the given origin. The interval starting with the origin is numbered + * as zero. Each interval includes its lower bound, but excludes its upper bound. + * @param grid spacing of the grid intervals, measured in TimeValue::Scale (µ-ticks) + * @return number of the grid interval containing the given time. + * @warning the resulting value is limited to (Time::Min, Time::MAX) + */ +int64_t +lumiera_quantise_frames (raw_time_64 time, raw_time_64 origin, raw_time_64 grid); + +int64_t +lumiera_quantise_frames_fps (raw_time_64 time, raw_time_64 origin, uint framerate); + +/** + * Similar to #lumiera_quantise_frames, but returns a grid aligned _relative time_. + * @return time of start of the grid interval containing the given time, + * but measured relative to the origin + * @warning because the resulting value needs to be limited to fit into a 64bit long, + * the addressable time range can be considerably reduced. For example, if + * origin = Time::MIN, then all original time values above zero will be + * clipped, because the result, relative to origin, needs to be <= Time::MAX + */ +raw_time_64 +lumiera_quantise_time (raw_time_64 time, raw_time_64 origin, raw_time_64 grid); + +/** + * Calculate time of a grid point (frame start) + * @param nr index number of the grid point (0 is at origin) + * @param grid spacing of the grid intervals, measured in TimeValue::Scale (µ-ticks) + * @return time point (frame start) on the Lumiera internal time scale + */ +raw_time_64 +lumiera_time_of_gridpoint (int64_t nr, raw_time_64 origin, raw_time_64 grid); + +/** + * Build a time value by summing up the given components. + * @param millis number of milliseconds + * @param secs number of seconds + * @param mins number of minutes + * @param hours number of hours + */ +raw_time_64 +lumiera_build_time (long millis, uint secs, uint mins, uint hours); + +/** + * Builds a time value by summing up the given components. + * @param fps framerate (frames per second) + * @param frames number of additional frames + * @param secs number of seconds + * @param mins number of minutes + * @param hours number of hours + */ +raw_time_64 +lumiera_build_time_fps (uint fps, uint frames, uint secs, uint mins, uint hours); + +/** + * Builds a time value by summing up the given components. + * The components are interpreted as a NTSC drop-frame timecode. + * @warning take care not to specify time codes that are illegal NTSC drop-frame times. + */ +raw_time_64 +lumiera_build_time_ntsc_drop (uint frames, uint secs, uint mins, uint hours); + + +/** Extract the hour part of given time. */ +int +lumiera_time_hours (raw_time_64 time); + + +/** Extract the minute part of given time. */ +int +lumiera_time_minutes (raw_time_64 time); + + +/** Extract the seconds part of given time. */ +int +lumiera_time_seconds (raw_time_64 time); + + +/** Extract the milliseconds part of given time. */ +int +lumiera_time_millis (raw_time_64 time); + +/** + * Extract the remaining frame part of given time. + * @param fps frame rate (frames per second) + */ +int +lumiera_time_frames (raw_time_64 time, uint fps); + +/** + * Extract the frame part of given time, using NTSC drop-frame timecode. + */ +int +lumiera_time_ntsc_drop_frames (raw_time_64 time); + +/** + * Extract the second part of given time, using NTSC drop-frame timecode. + */ +int +lumiera_time_ntsc_drop_seconds (raw_time_64 time); + +/** + * Extract the minute part of given time, using NTSC drop-frame timecode. + */ +int +lumiera_time_ntsc_drop_minutes (raw_time_64 time); + +/** + * Extract the hour part of given time, using NTSC drop-frame timecode. + */ +int +lumiera_time_ntsc_drop_hours (raw_time_64 time); + + +/** + * @internal Diagnostics helper: render time value in H:MM:SS.mmm format. + * @return `safeclib` temporary buffer containing formatted time string + * @note any time output for real should go through quantisation followed + * by rendering into a suitable timecode format. + */ +char* +lumiera_tmpbuf_print_time (raw_time_64 time); + + + +#ifdef __cplusplus +}//extern "C" +#endif +#endif /*LUMIERA_TIME_DROPFRAME_H*/ diff --git a/src/lib/time/timecode.cpp b/src/lib/time/timecode.cpp index e9a3fa794..cc12e24ea 100644 --- a/src/lib/time/timecode.cpp +++ b/src/lib/time/timecode.cpp @@ -443,33 +443,37 @@ namespace time { return *this; } - /** */ - int - HmsTC::getSecs() const - { - return lumiera_time_seconds (tpoint_); + namespace { + const auto TIME_SCALE_sec{lib::time::TimeValue::SCALE }; + const auto TIME_SCALE_ms {lib::time::TimeValue::SCALE / 1'000}; } - /** */ + /** @deprecated 5/25 : no numeric computations in this class! use Digxel instead! */ + int + HmsTC::getSecs() const + { /////////////////////////////////////////////////////////////////////////////////////////////////TICKET #750 we do not want numeric accessors her — rather we want Digxel members + return (raw_time_64(tpoint_) / TIME_SCALE_sec) % 60; + } + + /** @deprecated 5/25 : no numeric computations in this class! use Digxel instead! */ int HmsTC::getMins() const { - return lumiera_time_minutes (tpoint_); + return (raw_time_64(tpoint_) / TIME_SCALE_sec / 60) % 60; } - /** */ + /** @deprecated 5/25 : no numeric computations in this class! use Digxel instead! */ int HmsTC::getHours() const { - return lumiera_time_hours (tpoint_); + return raw_time_64(tpoint_) / TIME_SCALE_sec / 60 / 60; } - /** */ + /** @deprecated 5/25 : no numeric computations in this class! use Digxel instead! */ double HmsTC::getMillis() const { - TODO ("Frame-Quantisation"); - return lumiera_time_millis (tpoint_); + return (raw_time_64(tpoint_) / TIME_SCALE_ms) % 1000; } /** */ diff --git a/src/lib/time/timecode.hpp b/src/lib/time/timecode.hpp index b5f7a68fa..49eebd876 100644 --- a/src/lib/time/timecode.hpp +++ b/src/lib/time/timecode.hpp @@ -103,7 +103,7 @@ namespace time { TimeValue value() const { return Format::evaluate (*this, *quantiser_); } public: - typedef format::Frames Format; + using Format = format::Frames; FrameNr (QuTime const& quantisedTime); @@ -153,7 +153,7 @@ namespace time { public: - typedef format::Smpte Format; + using Format = format::Smpte; SmpteTC (QuTime const& quantisedTime); SmpteTC (SmpteTC const&); @@ -180,21 +180,27 @@ namespace time { /** * @warning missing implementation + * - not clear what we need and want + * - because also the use cases for H:M:S are not well defined + * - notable question: do we need/want a milliseconds part? + * - do we even want to go into fractional milliseconds, + * down to the µ-Grid? Or do we want that to be configurable? */ ////////////////////////////////////////////////////////////////////////////////////////////////TICKET #736 implement HMS format class HmsTC : public TCode { - TimeVar tpoint_; + TimeVar tpoint_; ///< @deprecated most definitively we do not want numeric computations here in this object virtual string show() const { return string(tpoint_); } virtual Literal tcID() const { return "Timecode"; } virtual TimeValue value() const { return tpoint_; } public: - typedef format::Hms Format; + using Format = format::Hms; HmsTC (QuTime const& quantisedTime); + ///////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #750 we do not want numeric accessors her — rather we want Digxel members double getMillis () const; int getSecs () const; int getMins () const; @@ -216,7 +222,7 @@ namespace time { virtual TimeValue value() const { return Time(sec_); } public: - typedef format::Seconds Format; + using Format = format::Seconds; Secs (QuTime const& quantisedTime); diff --git a/src/stage/widget/timecode-widget.cpp b/src/stage/widget/timecode-widget.cpp index fbf02f74e..e18a50f76 100644 --- a/src/stage/widget/timecode-widget.cpp +++ b/src/stage/widget/timecode-widget.cpp @@ -30,7 +30,6 @@ #include "stage/widget/timecode-widget.hpp" #include "lib/time/timevalue.hpp" -#include "lib/time/diagnostics.hpp" ////////////TODO: temporary solution to get H:M:S components. Use TimeCode instead! #include #include @@ -458,6 +457,26 @@ namespace widget { } + namespace { ///////////////////////////////////////////////////////////////////////////////////////////////TICKET #750 placeholder implementation: should integrate with the Digxel components of a given time code + inline int + getSecs (Time tpoint) + { + return (_raw(tpoint) / lib::time::TimeValue::SCALE) % 60; + } + + inline int + getMins (Time tpoint) + { + return (_raw(tpoint) / lib::time::TimeValue::SCALE / 60) % 60; + } + + inline int + getHours (Time tpoint) + { + return _raw(tpoint) / lib::time::TimeValue::SCALE / 60 / 60; + } + } + void TimeCode::set_minsec (Time when, bool force) { @@ -1275,7 +1294,7 @@ namespace widget { Time TimeCode::audio_time_from_display () const { - raw_time_64 parsedAudioFrames = lexical_cast(audio_frames_label.get_text()); + lib::time::raw_time_64 parsedAudioFrames = lexical_cast(audio_frames_label.get_text()); return Time(TimeValue(parsedAudioFrames)); } diff --git a/tests/11time.tests b/tests/11time.tests index b7aef47d3..087363243 100644 --- a/tests/11time.tests +++ b/tests/11time.tests @@ -1,15 +1,5 @@ TESTING "Time conversion" ./test-time -TEST "basic functionality" basic < ref) ); - raw_time_64 gat(var); - CHECK (!(gat == ref) ); - CHECK ( (gat != ref) ); - CHECK ( (gat >= ref) ); - CHECK (!(gat <= ref) ); - CHECK (!(gat < ref) ); - CHECK ( (gat > ref) ); + raw_time_64 rat(var); + CHECK (!(rat == ref) ); + CHECK ( (rat != ref) ); + CHECK ( (rat >= ref) ); + CHECK (!(rat <= ref) ); + CHECK (!(rat < ref) ); + CHECK ( (rat > ref) ); - CHECK ( (var == gat) ); - CHECK (!(var != gat) ); - CHECK ( (var >= gat) ); - CHECK ( (var <= gat) ); - CHECK (!(var < gat) ); - CHECK (!(var > gat) ); + CHECK ( (var == rat) ); + CHECK (!(var != rat) ); + CHECK ( (var >= rat) ); + CHECK ( (var <= rat) ); + CHECK (!(var < rat) ); + CHECK (!(var > rat) ); } void - checkComponentDiagnostics() + checkComponentBreakdown() { + Time t1(2008,0); + CHECK (t1 == "0:00:02.008"_expect); + + Time t2(2008,88); + CHECK (t2 == "0:01:30.008"_expect); + + Time t3(2008,118,58); + CHECK (t3 == "1:00:00.008"_expect); + + Time t4(t2 - t3); + CHECK (t4 == "-0:58:30.000"_expect); + + CHECK (Time::ZERO == "0:00:00.000"_expect); + CHECK (Time::MAX == "85401592:56:01.825"_expect); + CHECK (Time::MIN == "-85401592:56:01.825"_expect); + + seedRand(); - int millis = rani(1000); - int secs = rani (60); - int mins = rani (60); - int hours = rani (100); + int millis = 1 + rani (999); + int secs = rani (60); + int mins = rani (60); + int hours = rani (100); - Time time(millis,secs,mins,hours); - CHECK (Time() < time); - CHECK (millis == getMillis(time)); - CHECK (secs == getSecs (time)); - CHECK (mins == getMins (time)); - CHECK (hours == getHours (time)); - cout << time << endl; + Time randTime(millis,secs,mins,hours); + CHECK (Time() < randTime); - Time t2(2008,0); - cout << t2 << endl; - CHECK ( 8 == getMillis(t2)); - CHECK ( 2 == getSecs (t2)); - CHECK ( 0 == getMins (t2)); - CHECK ( 0 == getHours (t2)); + const auto TIME_SCALE_sec{lib::time::TimeValue::SCALE }; + const auto TIME_SCALE_ms {lib::time::TimeValue::SCALE / 1000}; - Time t3(2008,88); - cout << t3 << endl; - CHECK ( 8 == getMillis(t3)); - CHECK (30 == getSecs (t3)); - CHECK ( 1 == getMins (t3)); - CHECK ( 0 == getHours (t3)); - - Time t4(2008,118,58); - cout << t4 << endl; - CHECK ( 8 == getMillis(t4)); - CHECK ( 0 == getSecs (t4)); - CHECK ( 0 == getMins (t4)); - CHECK ( 1 == getHours (t4)); + CHECK (millis == (_raw(randTime) / TIME_SCALE_ms ) % 1000); + CHECK (secs == (_raw(randTime) / TIME_SCALE_sec) % 60); + CHECK (mins == (_raw(randTime) / TIME_SCALE_sec / 60) % 60); + CHECK (hours == _raw(randTime) / TIME_SCALE_sec / 60 / 60); } }; diff --git a/tests/basics/time/time-dropframe-test.cpp b/tests/basics/time/time-dropframe-test.cpp new file mode 100644 index 000000000..a229361e7 --- /dev/null +++ b/tests/basics/time/time-dropframe-test.cpp @@ -0,0 +1,185 @@ +/* + TimeDropframe(test) - document drop-frame calculation + + Copyright (C) + 2010 Stefan Kangas + +  **Lumiera** 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. See the file COPYING for further details. + +* *****************************************************************/ + +/** @file time-dropframe-test.cpp + ** unit test \ref TimeDropframe_test to document the + ** algorithm to compute the components of drop-frame timecode. + ** @see TimeFormats_test + ** @see TimeValue_test + ** @see timevalue.hpp + */ + + +#include "lib/test/test.h" + +#include +#include +#include + +typedef int64_t raw_time_64; //////////////////////////////////////////////////////////////////////////////TICKET #1259 turn time.h in to a C++ implementation header + +#include "lib/time.h" /////////////////////////////////////////////////////////////////////////////////////TICKET #1259 this should not be a general purpose library, but rather an implementation base + + +static int +calculate_framecount (raw_time_64 t, uint fps) +{ + return lumiera_quantise_frames_fps (t,0,fps); +} + + +TESTS_BEGIN + +const int FRAMES = 15; +const int MILLIS = 700; +const int SECONDS = 20; +const int MINUTES = 55; +const int HOURS = 3; +const int FPS = 24; + +/* + * 1. Basic functionality + */ + +TEST (basic) +{ + // Zero + raw_time_64 t = lumiera_build_time (0,0,0,0); + + CHECK ((raw_time_64) t == 0); + CHECK (lumiera_time_millis (t) == 0); + CHECK (lumiera_time_seconds (t) == 0); + CHECK (lumiera_time_minutes (t) == 0); + CHECK (lumiera_time_hours (t) == 0); + CHECK (lumiera_time_frames (t, FPS) == 0); + CHECK (lumiera_time_frames (t, FPS+5) == 0); + CHECK (calculate_framecount (t,FPS) == 0); + CHECK (calculate_framecount (t, FPS+5) == 0); + + ECHO ("%s", lumiera_tmpbuf_print_time (t)); + + // Non-zero + t = lumiera_build_time (MILLIS, SECONDS, MINUTES, HOURS); + + CHECK (lumiera_time_millis (t) == MILLIS); + CHECK (lumiera_time_seconds (t) == SECONDS); + CHECK (lumiera_time_minutes (t) == MINUTES); + CHECK (lumiera_time_hours (t) == HOURS); + CHECK (lumiera_time_frames (t, FPS) == FPS * MILLIS / 1000); + CHECK (lumiera_time_frames (t, FPS+5) == (FPS+5) * MILLIS / 1000); + CHECK (calculate_framecount (t, FPS) == 338896); + CHECK (calculate_framecount (t, FPS+5) == 409500); + + ECHO ("%s", lumiera_tmpbuf_print_time (t)); +} + +/* + * 2. Frame rate dependent calculations. + */ + +TEST (fps) +{ + raw_time_64 t = lumiera_build_time_fps (FPS, FRAMES, SECONDS, MINUTES, HOURS); + + CHECK (lumiera_time_millis (t) == FRAMES * 1000 / FPS); + CHECK (lumiera_time_seconds (t) == SECONDS); + CHECK (lumiera_time_minutes (t) == MINUTES); + CHECK (lumiera_time_hours (t) == HOURS); + CHECK (lumiera_time_frames (t, FPS) == FRAMES); + CHECK (lumiera_time_frames (t, FPS+5) == FRAMES * (FPS+5)/FPS); + CHECK (calculate_framecount (t, FPS) == 338895); + CHECK (calculate_framecount (t, FPS+5) == 409498); +} + +/* + * 3. NTSC drop-frame calculations. + */ + +TEST (ntsc_drop_frame) +{ + // Make sure frame 0 begins at 0 + raw_time_64 t = lumiera_build_time_ntsc_drop (0, 0, 0, 0); + + CHECK ((raw_time_64) t == 0); + CHECK (lumiera_time_millis (t) == 0); + CHECK (lumiera_time_seconds (t) == 0); + CHECK (lumiera_time_minutes (t) == 0); + CHECK (lumiera_time_hours (t) == 0); + CHECK (lumiera_time_frames (t, FPS) == 0); + CHECK (lumiera_time_frames (t, FPS+5) == 0); + CHECK (calculate_framecount (t, FPS) == 0); + CHECK (calculate_framecount (t, FPS+5) == 0); + + + + t = lumiera_build_time_ntsc_drop (FRAMES, SECONDS, MINUTES, HOURS); + + // Calculate manually what result to expect.... + int frames = FRAMES + 30*SECONDS + 30*60*MINUTES + 30*60*60*HOURS; // sum up using nominal 30fps + int minutes_to_drop_frames = (MINUTES - MINUTES/10) + (HOURS * 54); // but every minute, with the exception of every 10 minutes... + frames -= 2*minutes_to_drop_frames; // ...drop 2 frames + int64_t expectedMillis = 1000LL * frames * 1001/30000; // now convert frames to time, using the real framerate + + expectedMillis %= 1000; // look at the remainder.. + CHECK (lumiera_time_millis (t) == expectedMillis); + + CHECK (lumiera_time_seconds (t) == SECONDS); // while all other components should come out equal as set + CHECK (lumiera_time_minutes (t) == MINUTES); + CHECK (lumiera_time_hours (t) == HOURS); + + // Reverse calculate frames for NTSC drop +//CHECK (lumiera_quantise_frames (t, 0, dropFrameDuration) == frames); // the total nominal frames + CHECK (lumiera_time_ntsc_drop_frames (t) == FRAMES); // maximum one frame off due to rounding + + // Cover the whole value range; + // Manually construct a drop-frame timecode + // Make sure our library function returns the same times. + int min; + int sec; + int frame; + int hrs; + for (hrs = 0; hrs <= 24; hrs += 6) + for (min = 0; min <= 59; min += 1) + for (sec = 0; sec <= 59; sec += 10) + for (frame = 0; frame <= 29; frame++) + { + // Skip dropped frames + if (min % 10 && sec == 0 && frame < 2) + continue; + + t = lumiera_build_time_ntsc_drop(frame, sec, min, hrs); + /* + ECHO ("%02d:%02d:%02d;%02d" + , lumiera_time_ntsc_drop_hours (t) + , lumiera_time_ntsc_drop_minutes (t) + , lumiera_time_ntsc_drop_seconds (t) + , lumiera_time_ntsc_drop_frames (t) + ); + */ + CHECK (lumiera_time_ntsc_drop_frames (t) == frame); + CHECK (lumiera_time_ntsc_drop_seconds (t) == sec); + CHECK (lumiera_time_ntsc_drop_minutes (t) == min); + CHECK (lumiera_time_ntsc_drop_hours (t) == hrs % 24); + } + + // Make sure we do not get non-existent frames + int i; + for (i = 0; i < 59; i++) + { + int frame = (i % 10 == 0) ? 0 : 2; + t = lumiera_build_time_ntsc_drop (frame, 0, i, 0); + CHECK (lumiera_time_ntsc_drop_frames (t) == frame); + } +} + +TESTS_END diff --git a/tests/library/c-lib/test-time.c b/tests/library/c-lib/test-time.c index c491ef765..b808070ba 100644 --- a/tests/library/c-lib/test-time.c +++ b/tests/library/c-lib/test-time.c @@ -2,7 +2,7 @@ TEST-TIME - test the time conversion lib Copyright (C) - 2010 Stefan Kangas + 2010, Stefan Kangas   **Lumiera** is free software; you can redistribute it and/or modify it   under the terms of the GNU General Public License as published by the @@ -40,65 +40,11 @@ calculate_framecount (raw_time_64 t, uint fps) TESTS_BEGIN const int FRAMES = 15; -const int MILLIS = 700; const int SECONDS = 20; const int MINUTES = 55; const int HOURS = 3; const int FPS = 24; -/* - * 1. Basic functionality - */ - -TEST (basic) -{ - // Zero - raw_time_64 t = lumiera_build_time (0,0,0,0); - - CHECK ((raw_time_64) t == 0); - CHECK (lumiera_time_millis (t) == 0); - CHECK (lumiera_time_seconds (t) == 0); - CHECK (lumiera_time_minutes (t) == 0); - CHECK (lumiera_time_hours (t) == 0); - CHECK (lumiera_time_frames (t, FPS) == 0); - CHECK (lumiera_time_frames (t, FPS+5) == 0); - CHECK (calculate_framecount (t,FPS) == 0); - CHECK (calculate_framecount (t, FPS+5) == 0); - - ECHO ("%s", lumiera_tmpbuf_print_time (t)); - - // Non-zero - t = lumiera_build_time (MILLIS, SECONDS, MINUTES, HOURS); - - CHECK (lumiera_time_millis (t) == MILLIS); - CHECK (lumiera_time_seconds (t) == SECONDS); - CHECK (lumiera_time_minutes (t) == MINUTES); - CHECK (lumiera_time_hours (t) == HOURS); - CHECK (lumiera_time_frames (t, FPS) == FPS * MILLIS / 1000); - CHECK (lumiera_time_frames (t, FPS+5) == (FPS+5) * MILLIS / 1000); - CHECK (calculate_framecount (t, FPS) == 338896); - CHECK (calculate_framecount (t, FPS+5) == 409500); - - ECHO ("%s", lumiera_tmpbuf_print_time (t)); -} - -/* - * 2. Frame rate dependent calculations. - */ - -TEST (fps) -{ - raw_time_64 t = lumiera_build_time_fps (FPS, FRAMES, SECONDS, MINUTES, HOURS); - - CHECK (lumiera_time_millis (t) == FRAMES * 1000 / FPS); - CHECK (lumiera_time_seconds (t) == SECONDS); - CHECK (lumiera_time_minutes (t) == MINUTES); - CHECK (lumiera_time_hours (t) == HOURS); - CHECK (lumiera_time_frames (t, FPS) == FRAMES); - CHECK (lumiera_time_frames (t, FPS+5) == FRAMES * (FPS+5)/FPS); - CHECK (calculate_framecount (t, FPS) == 338895); - CHECK (calculate_framecount (t, FPS+5) == 409498); -} /* * 3. NTSC drop-frame calculations. diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index badba315c..4a6c6235a 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -92051,6 +92051,17 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + +

+ also das Thema »Quantisierung« war dann letztlichn doch nicht relevant für den Test, hat mich aber immerhin an den Sachverhalt erinnert, und dazu geführt, daß ich eine vertretbare Abkürzung in den FrameNo-Timecode eingabaut habe +

+ +
+ +
@@ -129307,7 +129318,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + @@ -159251,6 +159262,12 @@ unsigned int ThreadIdAsInt = *static_cast<unsigned int*>(static_cast<vo + + + + + + @@ -159484,16 +159501,13 @@ unsigned int ThreadIdAsInt = *static_cast<unsigned int*>(static_cast<vo - - - +

Christian kannte bereits Burkart Pflaum, und hat uns auf dessen Libraries aufmerksam gemacht. Die Libraries sind vorbildlich in iherer Struktur und in dem beschränkten Scope. Wahrscheinlich sind sie problemloser einzubinden als FFmpeg. Aber das Kernproblem bleibt: das ist eine one-Man-Show, und wir werden trotzdem auf FFmpeg nicht verzichten können....

- -
+ @@ -159915,7 +159929,7 @@ unsigned int ThreadIdAsInt = *static_cast<unsigned int*>(static_cast<vo - + @@ -162832,29 +162846,23 @@ Since then others have made contributions, see the log for the history.
- - - +

raw_time_64

- -
+
- - - +

schon seit langem will ich von der "C-Time-Library" weg, denn diese ist gradezu eine Einladung, Timecode-Operationen ad-hoc zu implementieren

- -
+
@@ -162862,22 +162870,27 @@ Since then others have made contributions, see the log for the history. - - - +

....weil eine solche Library gradezu dazu einläd, sich nicht mit den „mühsamen“ Abstraktionen des Time-Handling-Frameworks herumzuschlagen, sondern stattessen einfach mit Zahlen zu rechnen.

+
+
+ + + + +

+ ...nachdem gavl_time_t durch einen Typedef ersetzt ist, und ich das Thema durch #1261 bereits abgesteckt habe, könnte der Bestand einer eigenständigen Basis-Library durch wenige Umordnungen aufgehoben werden. Denn darum geht es mir: ich will in Lumiera eine Ordnung schaffen, in der gedankenloses einfach-mal-Machen keinen Raum findet. +

+
-
- + - - - +

...möglicherweise lassen sich diese Funktionen nämlich in Gruppen einteilen und dann direkt in einen anonymen namespace in die jeweilige Translation-Unit schieben.... @@ -162890,9 +162903,7 @@ Since then others have made contributions, see the log for the history. - - - +

Der Test täuscht: weil er in C geschrieben ist, sieht er so komplex aus. Dabei besteht die erste Hälfte lediglich draus, eine Konstruktor-Funktion zu testen. Der sinnvolle Kern daran ist die decimator-Sequenz zum Herunterbrechen von Zeiten. Die wird hier aber nur oberflächlich getestet. Da sollte man, wenn schon, wirklich auf die Grenzfälle losgehen ⟹ Test neu schreiben! @@ -162904,8 +162915,288 @@ Since then others have made contributions, see the log for the history.Antesten der drop-Frame-Umwandlung ⟹ auch das in eigenständigen neuen Test packen

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ zeigt besonders deutlich warum ich diesen Ansatz ablehne +

+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ und könnten vewendet werden, +

+

+ das Framework zu umgehen +

+ +
+ +
+ + + + + + + + + + + + + + +

+ diese dokumentieren einen gewissen Standard-Algorithmus, mit dem man einen SMPTE-drop-frame-Timecode aufbauen könnte +

+ +
+
+ + + + +

+ Bekanntlich war »drop-frame« vor langer Zeit in USA eingeführt worden, um ein Elektronik-Problem mit dem NTSC-Farbfernseh-Standard zu entschärfen. Klassischer Pragmatismus: was uns schadet, kann doch keine Wahrheit sein! +

+

+ +

+

+ Also handelt es sich im Kern darum, wie ein Frame-Count in ein klassisches Zeit-Schema gemappt wird. Das paßt nicht in den gedanklichen Rahmen, den ich ansonsten für Zeiten und Timecodes errichtet habe. Andererseits ist drop-Frame ein practical hack, und tritt meines Wissens nur auf im Zusammenhang mit SMPTE-Timecode — gehört also in eine vergangene Welt, in der die Verhältnisse einfach waren. +

+ +
+
+ + +
+ + + + + + + + + + + + + + +

+ es ist dadurch entstanden, +

+

+ daß man ausgehend von +

+

+ »Basis-Operationen« +

+

+ gedacht hat +

+ +
+ + + +

+ Also zeigt sich wieder einmal: die zugrundeliegende »pragmatische Denkweise« ist zutieft fehlgeleitet. Die Realität ist kein Baukasten-System. +

+

+ +

+

+ Hinter dem pragmatischen »hands-on« steckt ein Glaube an eine verborgene einfache Wahrheit. Nur unter dieser Annahme macht die Heuristik Sinn, daß man mal mit einem ausgedachten Exempel anfängt, dieses in Einzelschritte zerlegt, und dann behauptet, diese Einzelschritte wären Basis-Elemente. +

+

+ Und exakt so wurde bei dieser C-Library vorgegangen: Exempel-1: wir bauen uns mal eine Zeit. Einmal mit alles-auf-Null, dann einmal mit »random numbers« (return 47). Dabei stößt man dann auf die Dezimator-Schritte, und da diese irgendwie elementar aussehen, verpackt man sie in Funktionen, und behauptet, man habe das Atom gefunden. Das führt dazu, daß man eine Rechnung in Schein-Abstraktionen verpackt, welche man in einer Implementierung auch ohne Weiteres direkt anschreiben könnte. Tatsächlich ist die Modulus-Funktion bereits eine echte Abstraktion, d.h. sie ist ausdrucksstärker als eine lumiera_get_seconds()-Funktion. Und im Hintergrund macht sich auf diesem Weg wieder die geläufige Vorstellung breit, eine Zeit „bestehe“ aus Stunden, Minuten und Sekunden. Das ist bereits ein Irrtum, Time is fleeting.  +

+ +
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
  • + auch hier sollte vom tatsächlichen Nutzen ausgegangen werden... +
  • +
  • + Vorrausetzung wäre, das Verhältnis zu ISO8601 zu klären, d.h. verwenden wir H:M:S tatsächlich, um auch Millisekunden darzustellen? möglicherweise sogar konfigurierbar bis auf das µ-Grid hinunter +
  • +
+ +
+
+ + + + + + +

+ Solche Komponenten wären z.B. ein Editor für Marker-Positionen, für Clip-Längen, für Keyframes, für Effekte mit temporalem Aspekt. Aus diesem Kontext würde sich dann ergeben, wie mit der Format-Auswahl eines Timecode-Widget umgegangen werden soll, und daraus würde sich ergeben, wieein Timecode-Widget mit einem bestimmten Timecode verschaltet wird (und auf dessen Digxel zugreifen könnte). Erst von dort würde klar, welches API ein Timecode tatsächlich bieten muß. Im Moment wissen wir nur, daß ein Timecode auch als String gerendert werden kann.... +

+ +
+
+
+ + + + +

+ ...wenngleich auch durch die Hintertür — was aber egal ist, wenn man drin ist, is man drin +

+ +
+ + + + + +

+ und markiert als @deprecated +

+ +
+
+
+ + + +
+ + + + + +

+ gebrochen ist es ja ohnehin und sowiso, und weiter damit arbeiten möchte ich nicht — ganz ehrlich, das war ein »Griff ins Klo«, das ist kein Code, den man erhalten sollte. +

+
+ +
+ + + + + + + + + + + + +
+ + + + +