diff --git a/src/lib/time.h b/src/lib/time.h index 93c84893a..abb9d3a74 100644 --- a/src/lib/time.h +++ b/src/lib/time.h @@ -78,15 +78,6 @@ 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); - - @@ -94,42 +85,6 @@ 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 diff --git a/src/lib/time/grid.hpp b/src/lib/time/grid.hpp index 5fd81430c..59ff50a2b 100644 --- a/src/lib/time/grid.hpp +++ b/src/lib/time/grid.hpp @@ -62,7 +62,7 @@ namespace time { virtual FrameCnt gridPoint (TimeValue const& raw) const =0; - virtual TimeValue gridLocal (TimeValue const& raw) const =0; + virtual Offset gridLocal (TimeValue const& raw) const =0; virtual TimeValue timeOf (FrameCnt gridPoint) const =0; virtual TimeValue timeOf (FSecs gridTime, int=0) const =0; }; diff --git a/src/lib/time/quantiser.cpp b/src/lib/time/quantiser.cpp index 1787605cd..d84be41e2 100644 --- a/src/lib/time/quantiser.cpp +++ b/src/lib/time/quantiser.cpp @@ -2,7 +2,8 @@ Quantiser - aligning time values to a time grid Copyright (C) - 2010, Hermann Vosseler + 2010, Stefan Kangas + 2010-2025, 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 @@ -20,11 +21,9 @@ #include "lib/time/quantiser.hpp" #include "lib/time/timevalue.hpp" #include "lib/time/timequant.hpp" -#include "lib/time.h" +#include "lib/util-quant.hpp" +#include "lib/rational.hpp" -#include - -using boost::rational_cast; using std::string; @@ -33,6 +32,63 @@ namespace error = lumiera::error; namespace lib { namespace time { + namespace { // implementation: basic frame quantisation.... + + /** + * Grid-align the raw time to a frame grid, which can be fractional. + * @note for extreme values of framerate and framerate_divisor, the + * computation is approximative and will be µs-aligned. + */ + inline int64_t + calculate_quantisation (raw_time_64 time, uint64_t framerate, uint64_t framerate_divisor=1) + { + REQUIRE (framerate); + REQUIRE (framerate_divisor); + + const uint64_t MAXRANGE = std::numeric_limits::max(); + const int64_t limit_num = MAXRANGE / framerate; + const int64_t limit_den = MAXRANGE / framerate_divisor; + const int64_t microScale {lib::time::TimeValue::SCALE}; + + // protect against numeric overflow + if (abs(time) < limit_num and microScale < limit_den) + { + // safe to calculate "time * framerate" + return util::floordiv (time*int64_t(framerate), microScale*int64_t(framerate_divisor)); + } + else + { + // direct calculation will overflow; + // thus approximate the grid spacing by re-quantisation into µ-tick scale + // (since in the end we will alias to the microScale anyway)... + const uint64_t LIM{MAXRANGE/microScale}; + if (framerate > LIM or framerate_divisor > LIM) + { + framerate = util::reQuant (framerate,framerate_divisor, LIM); + framerate_divisor = LIM; + } + // Note : frameDuration ≡ microScale / (framerate/framerate_divisor) + raw_time_64 frameDuration = util::reQuant (framerate_divisor,framerate, microScale); + return util::floordiv (time,frameDuration); + } + } + } + + /** + * 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. + */ + int64_t + FixedFrameQuantiser::grid_aligned (TimeValue const& time, TimeValue const& grid) + { + return util::floordiv (_raw(time), _raw(grid)); + } + + PQuant getDefaultGridFallback() @@ -51,7 +107,7 @@ namespace time { /* Note: the ctor QuTime(TimeValue, Symbol) and the static function * PQuant Quantiser::retrieve (Symbol) are defined in common-services.cpp * To use this special convenience shortcuts, you need to link against liblumieracore.so - * This allows to use the Advice-system to retrieve grid definitions directly from the session + * This allows to use the Advice-system to retrieve grid definitions directly from the session */ @@ -86,7 +142,7 @@ namespace time { /** convenience shortcut: \em materialise a raw time value - * based on this grid or time axis, but returning a raw time value. + * based on this grid or time axis, but returning a raw time value. * Implemented as combination of the #gridPoint and #timeOf operations, * i.e. we quantise into this scale, but transform the result back onto * the global raw time value scale. @@ -111,50 +167,52 @@ namespace time { * origin and framerate stored within this quantiser. * @warning returned frame count might exceed the valid * range when converting back into a TimeValue. - * @see #lumiera_quantise_frames + * @see FixedFrameQuantiser::grid_aligned */ FrameCnt FixedFrameQuantiser::gridPoint (TimeValue const& rawTime) const { - return lumiera_quantise_frames (_raw(rawTime), _raw(origin_), _raw(raster_)); + return grid_aligned (Offset{origin_, rawTime}, raster_); } /** transform into the local time scale grid aligned. - * The actual calculation first determines the number - * of the grid interval containing the given rawTime, - * then followed by multiplying this interval number + * The actual calculation first determines the number of the grid interval + * containing the given rawTime, followed by multiplying this interval number * with the grid spacing. - * @return time of the start point of the grid interval - * containing the rawTime, _relative to the origin_ - * of the time scale used by this quantiser. - * @warning returned time values are limited by the - * valid range of lumiera::Time - * @see #lumiera_quantise_time + * @return time of the start point of the grid interval containing the rawTime, + * _relative to the origin_ of the time scale used by this quantiser. + * @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 + * @see FixedFrameQuantiser::grid_aligned */ - TimeValue + Offset FixedFrameQuantiser::gridLocal (TimeValue const& rawTime) const { - return TimeValue (lumiera_quantise_time (_raw(rawTime), _raw(origin_), _raw(raster_))); + return raster_ * grid_aligned (Offset{origin_, rawTime} + ,raster_); } /** calculate time value of a grid interval (frame) start point - * @return time point measured in Lumiera internal time + * @param gridPoint index number of the grid point (0 is at origin) + * @return time point measured in Lumiera internal time * @warning returned time values are limited by the * valid range of lumiera::Time */ TimeValue FixedFrameQuantiser::timeOf (FrameCnt gridPoint) const { - return TimeValue (lumiera_time_of_gridpoint (gridPoint, _raw(origin_), _raw(raster_))); + return origin_ + gridPoint * raster_; } /** calculate time coordinates of a time spec relative to this quantised time scale * @param gridTime seconds relative to the origin of this scale * @param gridOffset additional offset in grid intervals (frames) - * @return time point measured in Lumiera internal time + * @return time point measured in Lumiera internal time * @warning returned time values are limited by the * valid range of lumiera::Time */ @@ -174,5 +232,66 @@ namespace time { -}} // lib::time + + + + /* ===== NTSC drop-frame conversions ===== */ + namespace { // conversion parameters + + const uint FRAMES_PER_10min = 10*60 * 30000/1001; + const uint FRAMES_PER_1min = 1*60 * 30000/1001; + const uint DISCREPANCY = (1*60 * 30) - FRAMES_PER_1min; + } + + + /** + * @remark This function reverses building the drop-frame timecode, + * and thus maps a time into consecutive frame numbers + * at NTSC framerate (i.e. without gaps) + * @param timecode represented as time value in µ-ticks + * @return the absolute frame number as addressed by NTSC drop-frame + * @todo 2011 I doubt this works correct for negative times!! + */ + int64_t + calculate_ntsc_drop_frame_number (raw_time_64 time) + { + int64_t frameNr = calculate_quantisation (time, 30000, 1001); + + // partition into 10 minute segments + lldiv_t tenMinFrames = lldiv (frameNr, FRAMES_PER_10min); + + // ensure the drop-frame incidents happen at full minutes; + // at start of each 10-minute segment *no* drop incident happens, + // thus we need to correct discrepancy between nominal/real framerate once: + int64_t remainingMinutes = (tenMinFrames.rem - DISCREPANCY) / FRAMES_PER_1min; + + int64_t dropIncidents = (10-1) * tenMinFrames.quot + remainingMinutes; + return frameNr + 2*dropIncidents; + } + + + /** + * @remark This is the mapping function to translate NTSC drop frame + * timecode specification into an actual time, with the necessary + * skip events every 1.-9. minute, thereby allocating 108 frames + * less per hour, than would be required for full 30 fps. + * @return raw time value on a µ-tick scale + */ + raw_time_64 + build_time_from_ntsc_drop_frame (uint frames, uint secs, uint mins, uint hours) + { + int64_t total_mins = 60 * hours + mins; + int64_t total_frames = 30*60*60 * hours + + 30*60 * mins + + 30 * secs + + frames + - 2 * (total_mins - total_mins / 10); + raw_time_64 result = _raw (Offset{total_frames, FrameRate::NTSC}); + + if (0 != result) // compensate for truncating down on conversion + result += 1; // without this adjustment the frame number + return result; // would turn out off by -1 on back conversion + } + +}} // lib::time diff --git a/src/lib/time/quantiser.hpp b/src/lib/time/quantiser.hpp index f0df2926f..b2a2e4876 100644 --- a/src/lib/time/quantiser.hpp +++ b/src/lib/time/quantiser.hpp @@ -114,7 +114,7 @@ namespace time { //------Grid-API---------------------------------------------- virtual FrameCnt gridPoint (TimeValue const& raw) const =0; - virtual TimeValue gridLocal (TimeValue const& raw) const =0; + virtual Offset gridLocal (TimeValue const& raw) const =0; virtual TimeValue timeOf (FrameCnt gridPoint) const =0; virtual TimeValue timeOf (FSecs, int =0) const =0; }; @@ -142,11 +142,13 @@ namespace time { FixedFrameQuantiser (FrameRate const& frames_per_second, TimeValue referencePoint =TimeValue(0)); FixedFrameQuantiser (Duration const& frame_duration, TimeValue referencePoint =TimeValue(0)); - FrameCnt gridPoint (TimeValue const&) const; - TimeValue gridLocal (TimeValue const&) const; - TimeValue timeOf (FrameCnt gridPoint) const; - TimeValue timeOf (FSecs, int =0) const; + FrameCnt gridPoint (TimeValue const&) const override; + Offset gridLocal (TimeValue const&) const override; + TimeValue timeOf (FrameCnt gridPoint) const override; + TimeValue timeOf (FSecs, int =0) const override; + private: + static int64_t grid_aligned (TimeValue const&, TimeValue const&); }; diff --git a/src/lib/time/time.cpp b/src/lib/time/time.cpp index f819f26f7..320b25e21 100644 --- a/src/lib/time/time.cpp +++ b/src/lib/time/time.cpp @@ -402,81 +402,8 @@ lumiera_framecount_to_time (uint64_t frameCount, FrameRate const& fps) return rational_cast (lib::time::TimeValue::SCALE * frameCount / framerate); } -raw_time_64 -lumiera_frame_duration (FrameRate const& fps) -{ - if (!fps) - throw error::Logic ("Impossible to quantise to an zero spaced frame grid" - , error::LUMIERA_ERROR_BOTTOM_VALUE); - - FSecs duration = 1 / fps; - return lumiera_rational_to_time (duration); -} -namespace { // implementation: basic frame quantisation.... - - inline int64_t - calculate_quantisation (raw_time_64 time, raw_time_64 origin, raw_time_64 grid) - { - time -= origin; - return floordiv (time,grid); - } - - inline int64_t - calculate_quantisation (raw_time_64 time, raw_time_64 origin, uint framerate, uint framerate_divisor=1) - { - REQUIRE (framerate); - REQUIRE (framerate_divisor); - - const int64_t limit_num = std::numeric_limits::max() / framerate; - const int64_t limit_den = std::numeric_limits::max() / framerate_divisor; - const int64_t microScale {lib::time::TimeValue::SCALE}; - - // protect against numeric overflow - if (abs(time) < limit_num and microScale < limit_den) - { - // safe to calculate "time * framerate" - time -= origin; - return floordiv (time*framerate, microScale*framerate_divisor); - } - else - { - // direct calculation will overflow. - // use the less precise method instead... - raw_time_64 frameDuration = microScale / framerate; // truncated to µs - return calculate_quantisation (time,origin, frameDuration); - } - } -} - - -int64_t -lumiera_quantise_frames (raw_time_64 time, raw_time_64 origin, raw_time_64 grid) -{ - return calculate_quantisation (time, origin, grid); -} - -int64_t -lumiera_quantise_frames_fps (raw_time_64 time, raw_time_64 origin, uint framerate) -{ - return calculate_quantisation (time, origin, framerate); -} - -raw_time_64 -lumiera_quantise_time (raw_time_64 time, raw_time_64 origin, raw_time_64 grid) -{ - int64_t count = calculate_quantisation (time, origin, grid); - raw_time_64 alignedTime = count * grid; - return alignedTime; -} - -raw_time_64 -lumiera_time_of_gridpoint (int64_t nr, raw_time_64 origin, raw_time_64 grid) -{ - raw_time_64 offset = nr * grid; - return origin + offset; -} raw_time_64 @@ -536,66 +463,6 @@ lumiera_time_frames (raw_time_64 time, uint fps) namespace lib { namespace time { ////////////////////////////////////////////////////////////////////////////////////////////TICKET #1259 : move all calculation functions into a C++ namespace - - - /* ===== NTSC drop-frame conversions ===== */ - - namespace { // conversion parameters - - const uint FRAMES_PER_10min = 10*60 * 30000/1001; - const uint FRAMES_PER_1min = 1*60 * 30000/1001; - const uint DISCREPANCY = (1*60 * 30) - FRAMES_PER_1min; - } - - - /** - * @remark This function reverses building the drop-frame timecode, - * and thus maps a time into consecutive frame numbers - * at NTSC framerate (i.e. without gaps) - * @param timecode represented as time value in µ-ticks - * @return the absolute frame number as addressed by NTSC drop-frame - * @todo 2011 I doubt this works correct for negative times!! - */ - int64_t - calculate_ntsc_drop_frame_number (raw_time_64 time) - { - int64_t frameNr = calculate_quantisation (time, 0, 30000, 1001); - - // partition into 10 minute segments - lldiv_t tenMinFrames = lldiv (frameNr, FRAMES_PER_10min); - - // ensure the drop-frame incidents happen at full minutes; - // at start of each 10-minute segment *no* drop incident happens, - // thus we need to correct discrepancy between nominal/real framerate once: - int64_t remainingMinutes = (tenMinFrames.rem - DISCREPANCY) / FRAMES_PER_1min; - - int64_t dropIncidents = (10-1) * tenMinFrames.quot + remainingMinutes; - return frameNr + 2*dropIncidents; - } - - - /** - * @remark This is the mapping function to translate NTSC drop frame - * timecode specification into an actual time, with the necessary - * skip events every 1.-9. minute, thereby allocating 108 frames - * less per hour, than would be required for full 30 fps. - * @return raw time value on a µ-tick scale - */ - raw_time_64 - build_time_from_ntsc_drop_frame (uint frames, uint secs, uint mins, uint hours) - { - uint64_t total_mins = 60 * hours + mins; - uint64_t total_frames = 30*60*60 * hours - + 30*60 * mins - + 30 * secs - + frames - - 2 * (total_mins - total_mins / 10); - raw_time_64 result = lumiera_framecount_to_time (total_frames, FrameRate::NTSC); - - if (0 != result) // compensate for truncating down on conversion - result += 1; // without this adjustment the frame number - return result; // would turn out off by -1 on back conversion - } }} // lib::time diff --git a/tests/basics/time/quantiser-basics-test.cpp b/tests/basics/time/quantiser-basics-test.cpp index dc4189826..355f3b07c 100644 --- a/tests/basics/time/quantiser-basics-test.cpp +++ b/tests/basics/time/quantiser-basics-test.cpp @@ -21,6 +21,7 @@ #include "lib/time/quantiser.hpp" #include "lib/random.hpp" #include "lib/util.hpp" +#include "lib/test/diagnostic-output.hpp" using lumiera::error::LUMIERA_ERROR_BOTTOM_VALUE; using util::isnil; @@ -152,91 +153,80 @@ namespace test{ void coverQuantisationCornerCases() { + // For this test we exploit the limits of the time system + Time SUB_MIN{-Duration::MAX}; + Time SUP_MAX{ Duration::MAX}; + // origin at lower end of the time range - FixedFrameQuantiser case1 (1, Time::MIN); - CHECK (secs(0) == case1.gridLocal(Time::MIN )); - CHECK (secs(0) == case1.gridLocal(Time::MIN +TimeValue(1) )); - CHECK (secs(1) == case1.gridLocal(Time::MIN +secs(1) )); - CHECK (Time::MAX -secs(1) > case1.gridLocal( secs(-1) )); - CHECK (Time::MAX -secs(1) <= case1.gridLocal( secs (0) )); - CHECK (Time::MAX > case1.gridLocal( secs (0) )); - CHECK (Time::MAX == case1.gridLocal( secs(+1) )); - CHECK (Time::MAX == case1.gridLocal( secs(+2) )); + FixedFrameQuantiser case1 (1, SUB_MIN); + CHECK (secs(0) == case1.gridLocal(SUB_MIN )); + CHECK (secs(0) == case1.gridLocal(SUB_MIN +TimeValue(1) )); + CHECK (secs(1) == case1.gridLocal(SUB_MIN +secs(1) )); + CHECK (SUP_MAX -secs(1) > case1.gridLocal( secs(-1) )); + CHECK (SUP_MAX -secs(1) <= case1.gridLocal( secs (0) )); + CHECK (SUP_MAX > case1.gridLocal( secs (0) )); + CHECK (SUP_MAX > case1.gridLocal( secs(+1) )); + CHECK (SUP_MAX > case1.gridLocal( secs(+2) )); + + TimeValue largestPoint = case1.gridLocal(secs (0)); + CHECK (largestPoint == case1.gridLocal( secs(+1) )); + CHECK (largestPoint == case1.gridLocal( secs(+2) )); + CHECK (largestPoint < SUP_MAX); + CHECK (largestPoint == Offset{secs(1)} * case1.gridPoint(secs(0))); // origin at upper end of the time range - FixedFrameQuantiser case2 (1, Time::MAX); - CHECK (secs( 0) == case2.gridLocal(Time::MAX )); - CHECK (secs(-1) == case2.gridLocal(Time::MAX -TimeValue(1) )); // note: next lower frame - CHECK (secs(-1) == case2.gridLocal(Time::MAX -secs(1) )); // i.e. the same as a whole frame down - CHECK (Time::MIN +secs(1) < case2.gridLocal( secs(+2) )); - CHECK (Time::MIN +secs(1) >= case2.gridLocal( secs(+1) )); - CHECK (Time::MIN < case2.gridLocal( secs(+1) )); - CHECK (Time::MIN == case2.gridLocal( secs( 0) )); // note: because of downward truncating, - CHECK (Time::MIN == case2.gridLocal( secs(-1) )); // resulting values will already exceed - CHECK (Time::MIN == case2.gridLocal( secs(-2) )); // allowed range and thus will be clipped + FixedFrameQuantiser case2 (1, SUP_MAX); + CHECK (secs( 0) == case2.gridLocal(SUP_MAX )); + CHECK (secs(-1) == case2.gridLocal(SUP_MAX -TimeValue(1) )); // note: next lower frame + CHECK (secs(-1) == case2.gridLocal(SUP_MAX -secs(1) )); // i.e. the same as a whole frame down + CHECK (SUB_MIN +secs(1) < case2.gridLocal( secs(+2) )); + CHECK (SUB_MIN +secs(1) >= case2.gridLocal( secs(+1) )); + CHECK (SUB_MIN < case2.gridLocal( secs(+1) )); + CHECK (SUB_MIN == case2.gridLocal( secs( 0) )); // note: because of downward truncating, + CHECK (SUB_MIN == case2.gridLocal( secs(-1) )); // resulting values will already exceed + CHECK (SUB_MIN == case2.gridLocal( secs(-2) )); // allowed range and thus will be clipped // use very large frame with size of half the time range - Duration hugeFrame(Time::MAX); + Duration hugeFrame(SUP_MAX); FixedFrameQuantiser case3 (hugeFrame); - CHECK (Time::MIN == case3.gridLocal(Time::MIN )); - CHECK (Time::MIN == case3.gridLocal(Time::MIN +TimeValue(1) )); - CHECK (Time::MIN == case3.gridLocal( secs(-1) )); - CHECK (TimeValue(0) == case3.gridLocal( secs( 0) )); - CHECK (TimeValue(0) == case3.gridLocal( secs(+1) )); - CHECK (TimeValue(0) == case3.gridLocal(Time::MAX -TimeValue(1) )); - CHECK (Time::MAX == case3.gridLocal(Time::MAX )); + CHECK (SUB_MIN == case3.gridLocal(SUB_MIN )); + CHECK (SUB_MIN == case3.gridLocal(SUB_MIN +TimeValue(1) )); + CHECK (SUB_MIN == case3.gridLocal( secs(-1) )); + CHECK (TimeValue(0) == case3.gridLocal( secs( 0) )); + CHECK (TimeValue(0) == case3.gridLocal( secs(+1) )); + CHECK (TimeValue(0) == case3.gridLocal(SUP_MAX -TimeValue(1) )); + CHECK (SUP_MAX == case3.gridLocal(SUP_MAX )); // now displacing this grid by +1sec.... FixedFrameQuantiser case4 (hugeFrame, secs(1)); - CHECK (Time::MIN == case4.gridLocal(Time::MIN )); - CHECK (Time::MIN == case4.gridLocal(Time::MIN +TimeValue(1) )); // clipped... - CHECK (Time::MIN == case4.gridLocal(Time::MIN +secs(1) )); // but now exact (unclipped) - CHECK (Time::MIN == case4.gridLocal( secs(-1) )); - CHECK (Time::MIN == case4.gridLocal( secs( 0) )); - CHECK (TimeValue(0) == case4.gridLocal( secs(+1) )); //.....now exactly the frame number zero - CHECK (TimeValue(0) == case4.gridLocal(Time::MAX -TimeValue(1) )); - CHECK (TimeValue(0) == case4.gridLocal(Time::MAX )); //.......still truncated down to frame #0 + CHECK (SUB_MIN == case4.gridLocal(SUB_MIN )); + CHECK (SUB_MIN == case4.gridLocal(SUB_MIN +TimeValue(1) )); // clipped... + CHECK (SUB_MIN == case4.gridLocal(SUB_MIN +secs(1) )); // but now exact (unclipped) + CHECK (SUB_MIN == case4.gridLocal( secs(-1) )); + CHECK (SUB_MIN == case4.gridLocal( secs( 0) )); + CHECK (TimeValue(0) == case4.gridLocal( secs(+1) )); //.....now exactly the frame number zero + CHECK (TimeValue(0) == case4.gridLocal(SUP_MAX -TimeValue(1) )); + // think big... - Duration superHuge{secs(12345) + hugeFrame}; - Duration extraHuge{2*hugeFrame}; - CHECK (extraHuge == Duration::MAX); + TimeVar excess{SUP_MAX +secs(1)}; // this is a *loophole* to slide by the limitation of Time values + CHECK (SUP_MAX < excess); + CHECK (Duration{excess} < excess); // ...yet as soon as we construct another entity, the limitation applies + CHECK (Duration{excess} == SUP_MAX); - // Time::MAX < superHuge < Duration::Max is possible, but we can accommodate only one - FixedFrameQuantiser case5 (superHuge); - CHECK (TimeValue(0) == case5.gridLocal(Time::MAX )); - CHECK (TimeValue(0) == case5.gridLocal(Time::MAX -TimeValue(1) )); - CHECK (TimeValue(0) == case5.gridLocal( secs( 1) )); - CHECK (TimeValue(0) == case5.gridLocal( secs( 0) )); - CHECK (Time::MIN == case5.gridLocal( secs(-1) )); - CHECK (Time::MIN == case5.gridLocal(Time::MIN +TimeValue(1) )); - CHECK (Time::MIN == case5.gridLocal(Time::MIN )); - - // now with offset - FixedFrameQuantiser case6 (superHuge, Time::MAX-secs(1)); - CHECK (TimeValue(0) == case6.gridLocal(Time::MAX )); - CHECK (TimeValue(0) == case6.gridLocal(Time::MAX -TimeValue(1) )); - CHECK (TimeValue(0) == case6.gridLocal(Time::MAX -secs(1) )); - CHECK (Time::MIN == case6.gridLocal(Time::MAX -secs(2) )); - CHECK (Time::MIN == case6.gridLocal( secs( 1) )); - CHECK (Time::MIN == case6.gridLocal( secs(-12345) )); - CHECK (Time::MIN == case6.gridLocal( secs(-12345-1) )); - CHECK (Time::MIN == case6.gridLocal( secs(-12345-2) )); // this would be one frame lower, but is clipped - CHECK (Time::MIN == case6.gridLocal(Time::MIN +TimeValue(1) )); - CHECK (Time::MIN == case6.gridLocal(Time::MIN )); // same... unable to represent time points before Time::MIN + CHECK (SUP_MAX == case4.gridLocal(excess )); // Thus, more by accident, the next higher grid point can be computed - // maximum frame size is spanning the full time range - FixedFrameQuantiser case7 (extraHuge, Time::MIN+secs(1)); - CHECK (TimeValue(0) == case7.gridLocal(Time::MAX )); // rounded down one frame, i.e. to origin - CHECK (TimeValue(0) == case7.gridLocal( secs( 0) )); - CHECK (TimeValue(0) == case7.gridLocal(Time::MIN+secs(2) )); - CHECK (TimeValue(0) == case7.gridLocal(Time::MIN+secs(1) )); // exactly at origin - CHECK (Time::MIN == case7.gridLocal(Time::MIN )); // one frame further down, but clipped to Time::MIN - - // even larger frames aren't possible - Duration not_really_larger(secs(10000) + extraHuge); - CHECK (extraHuge == not_really_larger); + CHECK (secs(1) == case4.timeOf(0)); + CHECK (excess == case4.timeOf(1)); // The same loophole also allows to generate this next higher grid point + CHECK (excess == case4.timeOf(2)); // ...while the next after next will limited in computation + FixedFrameQuantiser broken (Duration::MAX, SUP_MAX); // Can drive this loophole to the extreme... + CHECK (secs(0) == broken.timeOf(-1)); // since there is leeway by one order of magnitude + CHECK (SUP_MAX == broken.timeOf(0)); + CHECK (SUP_MAX+SUP_MAX > Duration::MAX); + CHECK (SUP_MAX+SUP_MAX == broken.timeOf(1)); + CHECK (SUP_MAX+SUP_MAX == broken.timeOf(2)); + // frame sizes below the time micro grid get trapped long subAtomic = 2*TimeValue::SCALE; // too small for this universe... VERIFY_ERROR (BOTTOM_VALUE, FixedFrameQuantiser quark(subAtomic) ); diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 3820781ed..1a604aa9d 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -162938,7 +162938,8 @@ Since then others have made contributions, see the log for the history. - + + @@ -162966,8 +162967,8 @@ Since then others have made contributions, see the log for the history. - - + + @@ -162980,7 +162981,9 @@ Since then others have made contributions, see the log for the history. - + + + @@ -163100,15 +163103,27 @@ Since then others have made contributions, see the log for the history. - - - - - - + + + + + + + + + + + + + + + + - + + + @@ -163199,7 +163214,10 @@ Since then others have made contributions, see the log for the history. - + + + + @@ -163231,6 +163249,165 @@ Since then others have made contributions, see the log for the history. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ Zeiten sind bereits µ-Tick-quantisiert, d.h. eine einfache Division über ein Grid ist stets im Integer-Value range und ohne Fehler ausführbar +

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

+ und zwar wegen der Gefahr numerischer Overflows; die eingebaute Limitierung der Lumiera-Time ist nicht ausreichend: denn die Quantisierung muß die Framerate durch das µ-Grid dividieren, also den Zähler mal 10^6 nehmen +

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

+ ...und das macht durchaus Sinn, grade wegen der starken Limitierung, die ich bisher in diesem Fall demonstriert habe; außerdem ist diese grid-local-Time ohnehin etwas sonderbar, und auch daher ist es sinnvoll, diese als Offset (gegenüber dem Origin) zu modellieren. Sofern der Benutzer in eine normale Lumiera-Time speichert, sind wir wieder zurück bei den alten Limitierungen, aber man kann eben mit diesem Offset auch weiterrechnen, und ihn z.B. zum Origin dazuaddieren (und würde dann im Beispiel wieder bei ganz kleinen Zeiten ankommen) +

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

+ ...und die Grenzen ausreizen, die das Zeit-Framework (bewußt) erlaubt, wenn man die Typisierung geschickt ausnutzt: denn ein TimeValue wird aus einer anderen Zeit-Enität (absichtlich) ohne weiteren Bounds-Check übernommen. Es sind mithin durchaus TimeValue möglich, die größer sind als Time::MAX +

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