diff --git a/src/lib/time.h b/src/lib/time.h index a6fe2aa43..e23747a01 100644 --- a/src/lib/time.h +++ b/src/lib/time.h @@ -28,9 +28,9 @@ ** ** 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 \em quantised (aligned to a frame grid), + ** 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 \em timecode formats. + ** 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 diff --git a/src/lib/time/formats.hpp b/src/lib/time/formats.hpp index 1476d6c21..82d08b02b 100644 --- a/src/lib/time/formats.hpp +++ b/src/lib/time/formats.hpp @@ -58,8 +58,8 @@ namespace time { class Quantiser; // API for grid aligning - typedef Quantiser const& QuantR; - typedef std::shared_ptr PQuant; + using QuantR = Quantiser const&; + using PQuant = std::shared_ptr; namespace format { @@ -147,25 +147,25 @@ namespace time { template<> struct Traits { - typedef FrameNr TimeCode; + using TimeCode = FrameNr; }; template<> struct Traits { - typedef SmpteTC TimeCode; + using TimeCode = SmpteTC; }; template<> struct Traits { - typedef HmsTC TimeCode; + using TimeCode = HmsTC; }; template<> struct Traits { - typedef Secs TimeCode; + using TimeCode = Secs; }; diff --git a/src/lib/time/time.cpp b/src/lib/time/time.cpp index 7e66d1600..daaef856e 100644 --- a/src/lib/time/time.cpp +++ b/src/lib/time/time.cpp @@ -47,6 +47,7 @@ #include "lib/time.h" #include "lib/time/timevalue.hpp" #include "lib/util-quant.hpp" +#include "lib/format-string.hpp" extern "C" { #include "lib/tmpbuf.h" @@ -66,13 +67,11 @@ using lib::time::FrameRate; using boost::rational_cast; using boost::lexical_cast; + + namespace error = lumiera::error; - - - - namespace lib { namespace meta { extern const std::string FAILURE_INDICATOR; @@ -92,11 +91,13 @@ namespace time { const Time Time::NEVER (Time::MAX); const Offset Offset::ZERO (Time::ZERO); + + Literal DIAGNOSTIC_FORMAT{"%s%01d:%02d:%02d.%03d"}; /** scale factor _used locally within this implementation header_. - * GAVL_TIME_SCALE rsp. TimeValue::SCALE is the correct factor or dividend when using gavl_time_t - * for units of whole seconds from gavl_time_t. Since we want to use milliseconds, + * GAVL_TIME_SCALE rsp. TimeValue::SCALE is the correct factor or dividend when using + * gavl_time_t for display on a scale with seconds. Since we want to use milliseconds, * we need to multiply or divide by 1000 to get correct results. */ #define TIME_SCALE_MS (lib::time::TimeValue::SCALE / 1000) @@ -112,7 +113,7 @@ namespace time { * any further adjustments. */ Time::Time ( long millis - , uint secs + , uint secs , uint mins , uint hours ) @@ -129,18 +130,6 @@ namespace time { { } - /** display an internal Lumiera Time value - * for diagnostic purposes or internal reporting. - * @warning internal Lumiera time values refer to an - * implementation dependent time origin/scale. - * @return string rendering of the actual, underlying - * implementation value, as `h:m:s:ms` - */ - Time::operator string() const - { - return string (lumiera_tmpbuf_print_time (t_)); - } - /** @note recommendation is to use TCode for external representation * @remarks this is the most prevalent internal diagnostics display * of any "time-like" value, it is meant to be compact. */ @@ -161,6 +150,41 @@ namespace time { ; } + /** display an internal Lumiera Time value + * for diagnostic purposes or internal reporting. + * Format is `-hh:mm:ss.mss` + * @warning internal Lumiera time values refer to an + * implementation dependent time origin/scale. + * @return string rendering of the actual, underlying + * implementation value, as `h:m:s:ms` + */ + Time::operator string() const + { + gavl_time_t time = t_; + int millis, seconds, minutes, hours; + bool negative = (time < 0); + + if (negative) + time = -time; + + time /= TIME_SCALE_MS; + millis = time % 1000; + time /= 1000; + seconds = time % 60; + time /= 60; + minutes = time % 60; + time /= 60; + hours = time; + + return util::_Fmt{string(DIAGNOSTIC_FORMAT)} + % (negative? "-":"") + % hours + % minutes + % seconds + % millis; + } + + Offset::operator string() const { return (t_< 0? "" : "∆") @@ -174,15 +198,15 @@ namespace time { TimeSpan::operator string() const { - return string (lumiera_tmpbuf_print_time (t_)) - + string (dur_); + return string (start()) + + string (duration()); } namespace { - template + template string - renderFraction (INT const& frac, Literal postfx) noexcept + renderFraction (RAT const& frac, Literal postfx) noexcept try { std::ostringstream buffer; if (1 == frac.denominator() or 0 == frac.numerator()) @@ -299,7 +323,7 @@ lumiera_tmpbuf_print_time (gavl_time_t time) time /= 60; hours = time; - char *buffer = lumiera_tmpbuf_snprintf(64, "%s%01d:%02d:%02d.%03d", + char *buffer = lumiera_tmpbuf_snprintf(64, lib::time::DIAGNOSTIC_FORMAT, negative ? "-" : "", hours, minutes, seconds, milliseconds); ENSURE(buffer != NULL); diff --git a/src/lib/time/timequant.hpp b/src/lib/time/timequant.hpp index 68ba6b614..09f69a444 100644 --- a/src/lib/time/timequant.hpp +++ b/src/lib/time/timequant.hpp @@ -32,6 +32,34 @@ ** And it offers a dedicated API to "materialise" this (still complete and ** precise) time value into an external representation. ** + ** # Collaborations + ** + ** lib::time::Time is the ubiquitous yet opaque internal time "instant" used + ** in Lumiera to designate time points (and by extension also a TimeSpan). + ** What such an _internal time instant_ actually means, depends on the context, + ** yet it is the implementation's (not the user's) responsibility to keep that + ** meaning straight. + ** + ** However, by creating a [Quantised Time](\ref lib::time::QuTime), the association + ** to some time scale or time grid is made explicit; the time value within QuTime + ** is stored in full precision though, without performing any "rounding" yet. + ** + ** Only by building or casting into a _time code,_ the actual quantisation is + ** performed, which means to align the time value to the next grid point and + ** discard excess timing information. There are several [time formats](\ref formats.hpp) + ** available to choose from, like Hour-Minute-Second, or SMPTE (h-m-s + frame) or + ** fractional seconds or just a frame count with implicit frame rate. + ** + ** How the _quantisation_ or _grid alignment_ is actually performed is up to the + ** discretion of the lib::time::Quantiser; an actually Quantiser instance also + ** represents a common notion of some specific time grid, together with a usage + ** context. Notably, a "25 fps grid" means to start timing when the playback + ** starts, and to proceed in steps of 1/25sec. Conceptually (not-yet-implemented + ** as of 11/2022) a frame grid could also imply to anchor the time at some global + ** time zone, allowing to integrate _live content_ into the timeline -- and the + ** actual link to wall-clock time would be made explicit by some _placement_ + ** somewhere on the actual timeline. + ** */ @@ -60,13 +88,13 @@ namespace time { * relative to the defined time scale. Usually this time scale * exists already in the Lumiera session and is referred simply * by symbolic ID, it will be fetched on demand through the - * \link advice.hpp advice system.\endlink + * [advice system](\ref advice.hpp). * * By creating a QuTime value, the relation to such a predefined * time scale is made explicit. This doesn't change the internal * time value, but the actual creation of a timecode formatted * value (#formatAs) usually implies to quantise or grid align - * the time to the frame grid specific to this time scale. + * the time to the frame grid specific to this time scale. */ class QuTime : public Time @@ -74,21 +102,21 @@ namespace time { PQuant quantiser_; public: - QuTime (TimeValue raw, Symbol gridID); ///< @note defined in common-services.cpp + QuTime (TimeValue raw, Symbol gridID); ///< @note defined in common-services.cpp QuTime (TimeValue raw, PQuant quantisation_to_use); - operator PQuant() const; - + operator PQuant() const; ///< @return `shared-ptr` to the associated time grid (or time scale) + template - bool supports() const; + bool supports() const; ///< does our implicit time grid support building that timecode format? template typename format::Traits::TimeCode - formatAs() const; + formatAs() const; ///< create new time code instance, then #castInto template void - castInto (TC& timecode) const; + castInto (TC& timecode) const; ///< quantise into implicit grid, then rebuild the timecode /** receive change message, which might cause re-quantisation */ @@ -118,16 +146,25 @@ namespace time { inline typename format::Traits::TimeCode QuTime::formatAs() const { - typedef typename format::Traits::TimeCode TC; + using TC = typename format::Traits::TimeCode; return TC(*this); } + /** + * @tparam TC the kind of time code to use as target + * @param timecode existing instance of that timecode, to be overwritten + * @remark this is the basic operation to convert an (internal) time value + * into a time code format. QuTime is already associated with some + * _time grid_ for quantisation, but the internal value is precise. + * This operation creates a quantised (frame aligned) transient copy, + * and uses this to adjust/modify the fields in the given timecode. + */ template inline void QuTime::castInto (TC& timecode) const { - typedef typename TC::Format Format; + using Format = typename TC::Format; REQUIRE (supports()); Format::rebuild (timecode, *quantiser_, TimeValue(*this)); diff --git a/src/lib/time/timevalue.hpp b/src/lib/time/timevalue.hpp index 942f8aec5..6a460dad0 100644 --- a/src/lib/time/timevalue.hpp +++ b/src/lib/time/timevalue.hpp @@ -35,8 +35,8 @@ ** ** The value types defined in this header represent time points and time intervals ** based on an internal time scale (µs ticks) and not related to any known fixed time - ** zone or time base; rather they are interpreted in usage context, and the only way - ** to retrieve such a value is by formatting it into a time code format. + ** zone or time base; rather they are interpreted in usage context, and the intended + ** way to retrieve such a value is by formatting it into a time code format. ** ** The lib::time::TimeValue serves as foundation for all further time calculations; ** in fact it is implemented as a single 64bit µ-tick value (`gavl_time_t`). The @@ -64,6 +64,34 @@ ** is possible to attach as listener to such a "controller" and be notified by any ** manipulation; this setup is the base for running time display, playback cursors etc. ** + ** # Quantised time + ** + ** While these _internal time values_ can be considered _sufficiently precise,_ in practice + ** any time specifications in the context of media handling will be aligned to some grid, + ** and expressed in a _time code format._ Typically, we want to know the number of frames + ** since playback started at the beginning of the timeline, and such a specification also + ** relies on some implicitly known _frame rate_ (24fps for film in US, 25fps for film and + ** TV in Europe, ...). By _deliberate choice,_ in Lumiera we *do not incorporate* such + ** implicit assumptions into the actual time values. Rather, they need to be made explicitly + ** in the relevant usage context. This is also the reason why the time entities defined in + ** this header _do not offer an API_ to get the "real" time (whatever this means). Rather, + ** the user of these time entities should get used to the concept that these abstract + ** opaque values are the real thing, and a concrete, human readable time code is only + ** a derivation, and any such derivation also incurs information loss. To reiterate that, + ** _any time quantisation is a lossy information;_ grid aligned values are not "cleaner", + ** they are just easier to handle for humans. + ** + ** \par how can I extract a time value? + ** Taking the aforementioned into account, it depends on the context what to expect and to get + ** - the standard path is to create a lib::time::QuTime by associating the internal time value + ** with a pre-defined _time grid._ From there you can call QuTime::formatAs() to build an + ** actual timecode instance, which can then be investigated or just printed. + ** - for debugging purpose, lib::time::Time defines an `operator string()`, which breaks down + ** the internal values into the format `-hh:mm:ss.mss` + ** - advanced calculations with the need to access the implementation data in full precision + ** should go through lib::time::TimeVar, which offers conversions to raw `int64_t` and the + ** even more fine grained `FSec`, which is a rational (fraction) `boost::rational` + ** ** @see time.h basic time calculation library functions ** @see timequant.hpp ** @see TimeValue_test @@ -178,15 +206,15 @@ namespace time { * grid and origin is obvious from the call context. * @warning do not mix up gavl_time_t and FrameCnt. * @warning use 64bit consistently. - * beware: \c long is 32bit on i386 + * beware: `long` is 32bit on i386 * @note any conversion to frame numbers should go through * time quantisation followed by conversion to FrameNr */ - typedef int64_t FrameCnt; + using FrameCnt = int64_t; /** rational representation of fractional seconds * @warning do not mix up gavl_time_t and FSecs */ - typedef boost::rational FSecs; + using FSecs = boost::rational; @@ -319,7 +347,7 @@ namespace time { * Similar to (basic) time values, offsets can be compared, * but are otherwise opaque and immutable. Yet they allow * to build derived values, including - * - the \em absolute (positive) distance for this offset + * - the _absolute (positive) distance_ for this offset: #abs * - a combined offset by chaining another offset */ class Offset @@ -500,7 +528,7 @@ namespace time { * * As an exception to the generally immutable Time * entities, a non constant TimeSpan may receive - * \em mutation messages, both for the start point + * _mutation messages_, both for the start point * and the duration. This allows for changing * position and length of objects in the timeline. * diff --git a/src/stage/model/diagnostics.hpp b/src/stage/model/diagnostics.hpp index 4ce6c5804..c862e2f49 100644 --- a/src/stage/model/diagnostics.hpp +++ b/src/stage/model/diagnostics.hpp @@ -26,10 +26,7 @@ ** This header defines the basics of... ** ** @note as of X/2014 this is complete bs - ** @todo WIP ///////////////////////TICKET # - ** - ** @see ////TODO_test usage example - ** @see diagnostics.cpp implementation + ** @todo planned as of 11/2022 ////////////////////////////////////////////////////////////////////////////TICKET #1257 : provide test-session for GUI ** */ @@ -78,43 +75,8 @@ namespace model { { } - - - /* == Adapter interface for == */ - - void - setSolution (string const& solution ="") - { - UNIMPLEMENTED ("tbw"); -#if false /////////////////////////////////////////////////////////////////////////////////////////////////////////////UNIMPLEMENTED :: TICKET #888 - if (isDeaf()) - this->transmogrify (solution); -#endif /////////////////////////////////////////////////////////////////////////////////////////////////////////////UNIMPLEMENTED :: TICKET #888 - } - - - protected: - void maybe () const; - - }; - - - - - /** @internal in case - */ - inline void - Diagnostics::maybe () const - { - UNIMPLEMENTED ("tbw"); - } - - - - - }} // namespace stage::model #endif /*STAGE_MODEL_DIAGNOSTICS_H*/ diff --git a/src/stage/model/zoom-window.hpp b/src/stage/model/zoom-window.hpp index 8d779b9b2..5d815a42a 100644 --- a/src/stage/model/zoom-window.hpp +++ b/src/stage/model/zoom-window.hpp @@ -128,9 +128,9 @@ namespace model { uint px_per_sec_; public: - ZoomWindow() - : startAll_{Time::ZERO} - , afterAll_{Time(0,23)} + ZoomWindow (TimeSpan timeline =TimeSpan{Time::ZERO, FSecs(23)}) + : startAll_{timeline.start()} + , afterAll_{nonEmpty(timeline.end())} , startWin_{startAll_} , afterWin_{afterAll_} , px_per_sec_{25} @@ -163,6 +163,16 @@ namespace model { UNIMPLEMENTED ("setMetric"); } + /** + * scale up or down on a 2-logarithmic scale. + * Each step either doubles or halves the zoom level, + * and the visible window is adjusted accordingly, using + * the current #anchorPoint as centre for scaling. + * @note the zoom factor is limited to be between + * 2px per µ-tick and 1px per second (~20min + * on a typical 1280 monitor) + * @todo support for overview mode ////////////////////////////////////////////////////////////////////TICKET #1255 : implement overview mode + */ void nudgeMetric (int steps) { @@ -201,7 +211,7 @@ namespace model { void setVisibleRange (TimeSpan newWindow) { - UNIMPLEMENTED ("setVisibleRange"); + mutateWindow (newWindow.start(), newWindow.end()); } void @@ -237,7 +247,9 @@ namespace model { void nudgeVisiblePos (int steps) { - UNIMPLEMENTED ("nudgeVisiblePos"); + FSecs dur{afterWin_-startWin_}; // navigate half window steps + setVisibleRange (TimeSpan{Time{startWin_ + dur*steps/2} + , dur}); } void @@ -249,12 +261,59 @@ namespace model { private: /* === adjust and coordinate === */ - void - mutateWindow (TimeValue start, TimeValue after) + TimeValue + nonEmpty (TimeValue endPoint) { - UNIMPLEMENTED ("change Window TimeSpan, validate and adjust all params"); + if (startAll_ < endPoint) + return endPoint; + if (startAll_ < Time::MAX) + return TimeValue{startAll_ + 1}; + startAll_ = Time::MAX - TimeValue(1); + return Time::MAX; } + + /** @internal change Window TimeSpan, validate and adjust all params */ + void + mutateWindow (TimeVar start, TimeVar after) + { + if (not (start < after)) + { + if (after == Time::MAX) + start = Time::MAX - TimeValue(1); + else + after = start + TimeValue(1); + } + + FSecs dur{after - start}; + if (dur > FSecs{afterAll_ - startAll_}) + { + start = startAll_; + after = afterAll_; + } + else + if (start < startAll_) + { + start = startAll_; + after = start + dur; + } + else + if (after > afterAll_) + { + after = afterAll_; + start = after - dur; + } + ASSERT (after-start <= afterAll_-startAll_); + + px_per_sec_ = adjustedScale (start,after, startWin_,afterWin_); + startWin_ = start; + afterWin_ = after; + fireChangeNotification(); + } + + /** + * @internal adjust Window to match given scale, + * validate and adjust all params */ void mutateScale (uint px_per_sec) { diff --git a/tests/25fundamental.tests b/tests/25fundamental.tests index 2de2bdb3e..c272874d1 100644 --- a/tests/25fundamental.tests +++ b/tests/25fundamental.tests @@ -206,7 +206,7 @@ return: 0 END -TEST "Suppport for specific timecode formats" FormatSupport_test < + + + + + @@ -16184,6 +16189,7 @@

+ @@ -16207,10 +16213,13 @@ - + - + + + + @@ -17118,7 +17127,10 @@ - + + + + @@ -38358,7 +38370,7 @@ - + @@ -38386,6 +38398,85 @@ + + + + + + + + + + + +

+ FSecs ≙ boost::rational<int64_t> +

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

+ Resultat: +

+
    +
  • + man hat einen Faktor 1e+6 im Ergebnis +
  • +
  • + ist mir tatsächlich passiert, und zwar ziemlich überraschend +
  • +
  • + Abhilfe: FSecs-ctor explizit anschreiben +
  • +
+ +
+ +
+
+ + + + + + + + + + + + +

+ und so ist es auch gedacht: TimeVar sollte nicht auf APIs auftauchen! +

+

+ Wenn man sich daran hält, tritt TimeVar immer nur in einem lokalen Universum auf, wie hier im ZoomWindow, und man weiß genau, wo und warum man dem eine reine Zahl zuweist +

+ +
+ +
+
+ + + + +