2008-06-19 00:57:47 +02:00
/*
2013-01-07 05:43:01 +01:00
Time - Lumiera time handling foundation
2008-06-19 00:57:47 +02:00
Copyright: clarify and simplify the file headers
* Lumiera source code always was copyrighted by individual contributors
* there is no entity "Lumiera.org" which holds any copyrights
* Lumiera source code is provided under the GPL Version 2+
== Explanations ==
Lumiera as a whole is distributed under Copyleft, GNU General Public License Version 2 or above.
For this to become legally effective, the ''File COPYING in the root directory is sufficient.''
The licensing header in each file is not strictly necessary, yet considered good practice;
attaching a licence notice increases the likeliness that this information is retained
in case someone extracts individual code files. However, it is not by the presence of some
text, that legally binding licensing terms become effective; rather the fact matters that a
given piece of code was provably copyrighted and published under a license. Even reformatting
the code, renaming some variables or deleting parts of the code will not alter this legal
situation, but rather creates a derivative work, which is likewise covered by the GPL!
The most relevant information in the file header is the notice regarding the
time of the first individual copyright claim. By virtue of this initial copyright,
the first author is entitled to choose the terms of licensing. All further
modifications are permitted and covered by the License. The specific wording
or format of the copyright header is not legally relevant, as long as the
intention to publish under the GPL remains clear. The extended wording was
based on a recommendation by the FSF. It can be shortened, because the full terms
of the license are provided alongside the distribution, in the file COPYING.
2024-11-17 23:42:55 +01:00
Copyright ( C )
2008 , Christian Thaeter < ct @ pipapo . org >
2010 , Stefan Kangas < skangas @ skangas . se >
2011 , Hermann Vosseler < Ichthyostega @ web . de >
2008-06-19 00:57:47 +02:00
Copyright: clarify and simplify the file headers
* Lumiera source code always was copyrighted by individual contributors
* there is no entity "Lumiera.org" which holds any copyrights
* Lumiera source code is provided under the GPL Version 2+
== Explanations ==
Lumiera as a whole is distributed under Copyleft, GNU General Public License Version 2 or above.
For this to become legally effective, the ''File COPYING in the root directory is sufficient.''
The licensing header in each file is not strictly necessary, yet considered good practice;
attaching a licence notice increases the likeliness that this information is retained
in case someone extracts individual code files. However, it is not by the presence of some
text, that legally binding licensing terms become effective; rather the fact matters that a
given piece of code was provably copyrighted and published under a license. Even reformatting
the code, renaming some variables or deleting parts of the code will not alter this legal
situation, but rather creates a derivative work, which is likewise covered by the GPL!
The most relevant information in the file header is the notice regarding the
time of the first individual copyright claim. By virtue of this initial copyright,
the first author is entitled to choose the terms of licensing. All further
modifications are permitted and covered by the License. The specific wording
or format of the copyright header is not legally relevant, as long as the
intention to publish under the GPL remains clear. The extended wording was
based on a recommendation by the FSF. It can be shortened, because the full terms
of the license are provided alongside the distribution, in the file COPYING.
2024-11-17 23:42:55 +01:00
* * 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 .
2008-06-19 00:57:47 +02:00
Copyright: clarify and simplify the file headers
* Lumiera source code always was copyrighted by individual contributors
* there is no entity "Lumiera.org" which holds any copyrights
* Lumiera source code is provided under the GPL Version 2+
== Explanations ==
Lumiera as a whole is distributed under Copyleft, GNU General Public License Version 2 or above.
For this to become legally effective, the ''File COPYING in the root directory is sufficient.''
The licensing header in each file is not strictly necessary, yet considered good practice;
attaching a licence notice increases the likeliness that this information is retained
in case someone extracts individual code files. However, it is not by the presence of some
text, that legally binding licensing terms become effective; rather the fact matters that a
given piece of code was provably copyrighted and published under a license. Even reformatting
the code, renaming some variables or deleting parts of the code will not alter this legal
situation, but rather creates a derivative work, which is likewise covered by the GPL!
The most relevant information in the file header is the notice regarding the
time of the first individual copyright claim. By virtue of this initial copyright,
the first author is entitled to choose the terms of licensing. All further
modifications are permitted and covered by the License. The specific wording
or format of the copyright header is not legally relevant, as long as the
intention to publish under the GPL remains clear. The extended wording was
based on a recommendation by the FSF. It can be shortened, because the full terms
of the license are provided alongside the distribution, in the file COPYING.
2024-11-17 23:42:55 +01:00
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2013-01-07 05:43:01 +01:00
2022-10-30 01:31:25 +02:00
/** @file time.cpp
2013-01-07 05:43:01 +01:00
* * Lumiera time handling core implementation unit .
* * This translation unit generates code for the Lumiera internal time wrapper ,
2025-05-17 23:12:47 +02:00
* * based on 64 bit integral µ - tick values , associated constants , marker classes
* * for the derived time entities ( TimeVar , Offset , Duration , TimeSpan , FrameRate )
* * and for the basic time and frame rate conversion functions .
2013-01-07 05:43:01 +01:00
* *
* * Client code includes either time . h ( for basics and conversion functions )
* * or timevalue . hpp ( for the time entities ) , timequant . hpp for grid aligned
2022-10-30 01:31:25 +02:00
* * time values or timecode . hpp
2013-01-07 05:43:01 +01:00
* *
* * @ see Time
* * @ see TimeValue
* * @ see Grid
* * @ see TimeValue_test
* * @ see QuantiserBasics_test
* *
*/
2011-01-09 02:13:58 +01:00
# include "lib/error.hpp"
2013-01-07 05:43:01 +01:00
# include "lib/time.h"
# include "lib/time/timevalue.hpp"
Timeline: safely calculate sum/difference of large fractional times
...in a similar vein as done for the product calculation.
In this case, we need to check the dimensions carefully and pick
the best calculation path, but as long as the overall result can
be represented, it should be possible to carry out the calculation
with fractional values, albeit introducing a small error.
As a follow-up, I have now also refactored the re-quantisation
functions, to be usable for general requantisation to another grid,
and I used these to replace the *naive* implementation of the
conversion FSecs -> µ-Grid, which caused a lot of integer-wrap-around
However, while the test now works basically without glitch or wrap,
the window position is still numerically of by 1e-6, which becomes
quite noticeably here due to the large overall span used for the test.
2022-12-01 23:23:50 +01:00
# include "lib/rational.hpp"
2012-03-04 01:34:18 +01:00
# include "lib/util-quant.hpp"
2022-10-30 23:12:34 +01:00
# include "lib/format-string.hpp"
2023-11-10 03:34:29 +01:00
# include "lib/util.hpp"
2011-01-09 04:56:46 +01:00
2011-01-09 02:13:58 +01:00
# include <math.h>
2013-01-07 05:43:01 +01:00
# include <limits>
# include <string>
Timehandling: choose safer representation for fractional seconds (closes #939)
When drafting the time handling framework some years ago,
I foresaw the possible danger of mixing up numbers relating
to fractional seconds, with other plain numbers intended as
frame counts or as micro ticks. Thus I deliberately picked
an incompatible integer type for FSecs = boost::rational<long>
However, using long is problematic in itself, since its actual
bit length is not fixed, and especially on 32bit platforms long
is quite surprisingly defined to be the same as int.
However, meanwhile, using the new C++ features, I have blocked
pretty much any possible implicit conversion path, requiring
explicit conversions in the relevant ctor invocations. So,
after weighting in the alternatives, FSecs is now defined
as boost::rational<int64_t>.
2020-02-17 02:36:54 +01:00
# include <sstream>
2013-01-07 05:43:01 +01:00
# include <boost/rational.hpp>
2013-09-01 17:36:05 +02:00
# include <boost/lexical_cast.hpp>
2011-01-06 13:30:27 +01:00
2013-01-07 05:43:01 +01:00
using std : : string ;
2023-11-10 03:34:29 +01:00
using util : : limited ;
2011-01-09 04:56:46 +01:00
using util : : floordiv ;
using lib : : time : : FSecs ;
using lib : : time : : FrameRate ;
using boost : : rational_cast ;
2013-09-01 17:36:05 +02:00
using boost : : lexical_cast ;
2011-01-09 04:56:46 +01:00
2023-11-10 03:34:29 +01:00
# undef MAX
# undef MIN
2011-01-09 04:56:46 +01:00
2011-01-06 13:30:27 +01:00
2022-10-30 23:12:34 +01:00
namespace error = lumiera : : error ;
2010-12-06 16:18:54 +01:00
2013-01-07 05:43:01 +01:00
namespace lib {
Timehandling: choose safer representation for fractional seconds (closes #939)
When drafting the time handling framework some years ago,
I foresaw the possible danger of mixing up numbers relating
to fractional seconds, with other plain numbers intended as
frame counts or as micro ticks. Thus I deliberately picked
an incompatible integer type for FSecs = boost::rational<long>
However, using long is problematic in itself, since its actual
bit length is not fixed, and especially on 32bit platforms long
is quite surprisingly defined to be the same as int.
However, meanwhile, using the new C++ features, I have blocked
pretty much any possible implicit conversion path, requiring
explicit conversions in the relevant ctor invocations. So,
after weighting in the alternatives, FSecs is now defined
as boost::rational<int64_t>.
2020-02-17 02:36:54 +01:00
namespace meta {
extern const std : : string FAILURE_INDICATOR ;
}
2013-01-07 05:43:01 +01:00
namespace time {
2025-05-17 23:12:47 +02:00
const raw_time_64 TimeValue : : SCALE = 1'000'000 ;
2018-11-17 18:00:39 +01:00
2013-01-07 05:43:01 +01:00
/** @note the allowed time range is explicitly limited to help overflow protection */
2025-05-17 23:12:47 +02:00
const Time Time : : MAX ( TimeValue : : buildRaw_ ( + std : : numeric_limits < raw_time_64 > : : max ( ) / 30 ) ) ;
2013-01-07 05:43:01 +01:00
const Time Time : : MIN ( TimeValue : : buildRaw_ ( - _raw ( Time : : MAX ) ) ) ;
const Time Time : : ZERO ;
2018-11-17 17:25:10 +01:00
const Time Time : : ANYTIME ( Time : : MIN ) ;
const Time Time : : NEVER ( Time : : MAX ) ;
2013-01-07 05:43:01 +01:00
const Offset Offset : : ZERO ( Time : : ZERO ) ;
2022-10-30 23:12:34 +01:00
2022-12-18 03:47:40 +01:00
const FSecs FSEC_MAX { std : : numeric_limits < int64_t > : : max ( ) / lib : : time : : TimeValue : : SCALE } ;
2022-10-30 23:12:34 +01:00
Literal DIAGNOSTIC_FORMAT { " %s%01d:%02d:%02d.%03d " } ;
2018-11-17 18:00:39 +01:00
2013-01-07 05:43:01 +01:00
2018-11-17 18:00:39 +01:00
/** scale factor _used locally within this implementation header_.
2025-05-17 23:12:47 +02:00
* TimeValue : : SCALE ( µ - ticks , i . e . 1e6 ) is the correct factor or dividend when using
* raw_time_64 for display on a scale with seconds . Since we want to use milliseconds ,
2018-11-17 18:00:39 +01:00
* we need to multiply or divide by 1000 to get correct results . */
# define TIME_SCALE_MS (lib::time::TimeValue::SCALE / 1000)
2013-01-07 05:43:01 +01:00
/** convenience constructor to build an
* internal Lumiera Time value from the usual parts
* of an sexagesimal time specification . Arbitrary integral
* values are acceptable and will be summed up accordingly .
* The minute and hour part can be omitted .
* @ warning internal Lumiera time values refer to an
* implementation dependent time origin / scale .
* The given value will be used as - is , without
* any further adjustments .
*/
Time : : Time ( long millis
2022-10-30 23:12:34 +01:00
, uint secs
2013-01-07 05:43:01 +01:00
, uint mins
, uint hours
)
: TimeValue ( lumiera_build_time ( millis , secs , mins , hours ) )
{ }
/** convenience constructor to build an Time value
* from a fraction of seconds , given as rational number .
* An example would be to the time unit of a framerate .
*/
Time : : Time ( FSecs const & fractionalSeconds )
: TimeValue ( lumiera_rational_to_time ( fractionalSeconds ) )
{ }
Library: rectify clipping of time::Duration (see #1263)
This is a deep refactoring to allow to represent the distance
between all valid time points as a time::Offset or time::Duration.
By design this is possible, since Time::MAX was defined as 1/30 of
the maximum value technically representable as int64_t. However,
introducing a different limiter for offsets and durations turns
out difficult, due to the inconsistencies in the exiting hierarchy
of temporal entities. Which in turn seems to stem from the unfortunate
decision to make time entities immutable, see #1261
Since the limiter is hard wired into the `time::TimeValue` constructor,
we are forced to create a "backdoor" of sorts, to pass up values
with different limiting from child classes. This would not be so
much of a problem if calculations weren't forced to go through `TimeVar`,
which does not distinguish between time points and time durations.
This solution rearranges all checks to be performed now by time::Offset,
while time::Duration will only take the absolute value at construction,
based on the fact that there is no valid construction path to yield
a duration which does not go through an offset first.
Later, when we're ready to sort out the implementation base of time values
(see #1258), this design issue should be revisited
- either we'll allow derived classes explicitly to invoke the limiter functions
- or we may be able to have an automatic conversion path from clearly
marked base implementation types, in which case we wouldn't use the
buildRaw_() and _raw() "backdoor" functions any more...
2022-12-05 00:58:32 +01:00
Offset : : Offset ( FSecs const & delta_in_secs )
: TimeValue { buildRaw_ ( symmetricLimit ( lumiera_rational_to_time ( delta_in_secs )
, Duration : : MAX ) ) }
{ }
2013-01-07 05:43:01 +01:00
2016-01-08 00:13:59 +01:00
/** @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 . */
2015-09-25 02:03:12 +02:00
TimeValue : : operator string ( ) const
2013-01-07 05:43:01 +01:00
{
2025-05-17 23:12:47 +02:00
raw_time_64 time = t_ ;
2016-01-08 00:13:59 +01:00
int64_t millis , seconds ;
2015-09-25 02:03:12 +02:00
bool negative = ( time < 0 ) ;
if ( negative ) time = - time ;
2018-11-17 18:00:39 +01:00
time / = TIME_SCALE_MS ;
2015-09-25 02:03:12 +02:00
millis = time % 1000 ;
seconds = time / 1000 ;
return string ( negative ? " - " : " " )
+ ( seconds > 0 or time = = 0 ? lexical_cast < string > ( seconds ) + " s " : " " )
+ ( millis > 0 ? lexical_cast < string > ( millis ) + " ms " : " " )
;
}
2022-10-30 23:12:34 +01:00
/** 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
{
2025-05-17 23:12:47 +02:00
raw_time_64 time = t_ ;
2022-10-30 23:12:34 +01:00
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 ;
}
2016-01-08 00:13:59 +01:00
Offset : : operator string ( ) const
{
return ( t_ < 0 ? " " : " ∆ " )
+ TimeValue : : operator string ( ) ;
}
Duration : : operator string ( ) const
{
return " ≺ " + TimeValue : : operator string ( ) + " ≻ " ;
}
2015-09-25 02:03:12 +02:00
TimeSpan : : operator string ( ) const
{
2022-10-30 23:12:34 +01:00
return string ( start ( ) )
+ string ( duration ( ) ) ;
2016-01-08 00:13:59 +01:00
}
Timehandling: choose safer representation for fractional seconds (closes #939)
When drafting the time handling framework some years ago,
I foresaw the possible danger of mixing up numbers relating
to fractional seconds, with other plain numbers intended as
frame counts or as micro ticks. Thus I deliberately picked
an incompatible integer type for FSecs = boost::rational<long>
However, using long is problematic in itself, since its actual
bit length is not fixed, and especially on 32bit platforms long
is quite surprisingly defined to be the same as int.
However, meanwhile, using the new C++ features, I have blocked
pretty much any possible implicit conversion path, requiring
explicit conversions in the relevant ctor invocations. So,
after weighting in the alternatives, FSecs is now defined
as boost::rational<int64_t>.
2020-02-17 02:36:54 +01:00
namespace {
2022-10-30 23:12:34 +01:00
template < typename RAT >
Timehandling: choose safer representation for fractional seconds (closes #939)
When drafting the time handling framework some years ago,
I foresaw the possible danger of mixing up numbers relating
to fractional seconds, with other plain numbers intended as
frame counts or as micro ticks. Thus I deliberately picked
an incompatible integer type for FSecs = boost::rational<long>
However, using long is problematic in itself, since its actual
bit length is not fixed, and especially on 32bit platforms long
is quite surprisingly defined to be the same as int.
However, meanwhile, using the new C++ features, I have blocked
pretty much any possible implicit conversion path, requiring
explicit conversions in the relevant ctor invocations. So,
after weighting in the alternatives, FSecs is now defined
as boost::rational<int64_t>.
2020-02-17 02:36:54 +01:00
string
2022-10-30 23:12:34 +01:00
renderFraction ( RAT const & frac , Literal postfx ) noexcept
Timehandling: choose safer representation for fractional seconds (closes #939)
When drafting the time handling framework some years ago,
I foresaw the possible danger of mixing up numbers relating
to fractional seconds, with other plain numbers intended as
frame counts or as micro ticks. Thus I deliberately picked
an incompatible integer type for FSecs = boost::rational<long>
However, using long is problematic in itself, since its actual
bit length is not fixed, and especially on 32bit platforms long
is quite surprisingly defined to be the same as int.
However, meanwhile, using the new C++ features, I have blocked
pretty much any possible implicit conversion path, requiring
explicit conversions in the relevant ctor invocations. So,
after weighting in the alternatives, FSecs is now defined
as boost::rational<int64_t>.
2020-02-17 02:36:54 +01:00
try {
std : : ostringstream buffer ;
if ( 1 = = frac . denominator ( ) or 0 = = frac . numerator ( ) )
buffer < < frac . numerator ( ) < < postfx ;
else
buffer < < frac < < postfx ;
return buffer . str ( ) ;
}
catch ( . . . )
{ return meta : : FAILURE_INDICATOR ; }
}
2016-01-08 00:13:59 +01:00
/** visual framerate representation (for diagnostics) */
FrameRate : : operator string ( ) const
{
Timehandling: choose safer representation for fractional seconds (closes #939)
When drafting the time handling framework some years ago,
I foresaw the possible danger of mixing up numbers relating
to fractional seconds, with other plain numbers intended as
frame counts or as micro ticks. Thus I deliberately picked
an incompatible integer type for FSecs = boost::rational<long>
However, using long is problematic in itself, since its actual
bit length is not fixed, and especially on 32bit platforms long
is quite surprisingly defined to be the same as int.
However, meanwhile, using the new C++ features, I have blocked
pretty much any possible implicit conversion path, requiring
explicit conversions in the relevant ctor invocations. So,
after weighting in the alternatives, FSecs is now defined
as boost::rational<int64_t>.
2020-02-17 02:36:54 +01:00
return renderFraction ( * this , " FPS " ) ;
2013-01-07 05:43:01 +01:00
}
Timehandling: choose safer representation for fractional seconds (closes #939)
When drafting the time handling framework some years ago,
I foresaw the possible danger of mixing up numbers relating
to fractional seconds, with other plain numbers intended as
frame counts or as micro ticks. Thus I deliberately picked
an incompatible integer type for FSecs = boost::rational<long>
However, using long is problematic in itself, since its actual
bit length is not fixed, and especially on 32bit platforms long
is quite surprisingly defined to be the same as int.
However, meanwhile, using the new C++ features, I have blocked
pretty much any possible implicit conversion path, requiring
explicit conversions in the relevant ctor invocations. So,
after weighting in the alternatives, FSecs is now defined
as boost::rational<int64_t>.
2020-02-17 02:36:54 +01:00
2013-01-07 05:43:01 +01:00
/** @internal backdoor to sneak in a raw time value
2016-01-08 00:13:59 +01:00
* bypassing any normalisation and limiting */
2013-01-07 05:43:01 +01:00
TimeValue
2025-05-17 23:12:47 +02:00
TimeValue : : buildRaw_ ( raw_time_64 raw )
2013-01-07 05:43:01 +01:00
{
return reinterpret_cast < TimeValue const & > ( raw ) ;
}
/** predefined constant for PAL framerate */
const FrameRate FrameRate : : PAL ( 25 ) ;
const FrameRate FrameRate : : NTSC ( 30000 , 1001 ) ;
2023-12-03 23:33:06 +01:00
const FrameRate FrameRate : : STEP ( 1 ) ;
2013-01-07 05:43:01 +01:00
2013-06-15 04:02:48 +02:00
const FrameRate FrameRate : : HALTED ( 1 , std : : numeric_limits < int > : : max ( ) ) ;
2013-01-07 05:43:01 +01:00
/** @return time span of one frame of this rate,
* cast into internal Lumiera time scale */
Duration
FrameRate : : duration ( ) const
{
if ( 0 = = * this )
throw error : : Logic ( " Impossible to quantise to an zero spaced frame grid "
, error : : LUMIERA_ERROR_BOTTOM_VALUE ) ;
return Duration ( 1 , * this ) ;
}
2023-11-10 03:34:29 +01:00
/** a rather arbitrary safety limit imposed on internal numbers used to represent a frame rate.
* @ remark rational numbers bear the danger to overflow for quite ordinary computations ;
* we stay away from the absolute maximum by an additional safety margin of 1 / 1000.
*/
2023-11-10 19:35:38 +01:00
const uint RATE_LIMIT { std : : numeric_limits < uint > : : max ( ) / 1024 } ;
2023-11-10 03:34:29 +01:00
/**
* @ internal helper to work around the limitations of ` uint ` .
* @ return a fractional number approximating the floating - point spec .
2023-11-10 19:35:38 +01:00
* @ todo imposing a quite coarse limitation . If this turns out to be
* a problem : we can do better , use lib : : reQuant ( rational . hpp )
2023-11-10 03:34:29 +01:00
*/
boost : : rational < uint >
__framerate_approximation ( double fps )
{
2023-11-10 19:35:38 +01:00
const double UPPER_LIMIT = int64_t ( RATE_LIMIT * 1024 ) < < 31 ;
const int64_t HAZARD = util : : ilog2 ( RATE_LIMIT ) ;
2023-11-10 03:34:29 +01:00
2023-11-10 19:35:38 +01:00
double doo = limited ( 1.0 , fabs ( fps ) * RATE_LIMIT + 0.5 , UPPER_LIMIT ) ;
int64_t boo ( doo ) ;
util : : Rat quantised { boo
, int64_t ( RATE_LIMIT )
} ;
int64_t num = quantised . numerator ( ) ;
int64_t toxic = util : : ilog2 ( abs ( num ) ) ;
toxic = util : : max ( 0 , toxic - HAZARD ) ;
int64_t base = quantised . denominator ( ) ;
if ( toxic )
{
base = util : : max ( base > > toxic , 1 ) ;
num = util : : reQuant ( num , quantised . denominator ( ) , base ) ;
}
return { limited ( 1u , num , RATE_LIMIT )
, limited ( 1u , base , RATE_LIMIT )
} ;
2023-11-10 03:34:29 +01:00
}
/**
* @ internal helper calculate the _count per time span_ approximately ,
* to the precision possible to represent as fractional ` uint ` .
*/
boost : : rational < uint >
__framerate_approximation ( size_t cnt , Duration timeReference )
{
boost : : rational < uint64_t > quot { cnt , _raw ( timeReference ) } ;
if ( quot . denominator ( ) < RATE_LIMIT
2023-11-10 19:35:38 +01:00
and quot . numerator ( ) < RATE_LIMIT * 1024 / 1e6 )
2023-11-10 03:34:29 +01:00
return { uint ( quot . numerator ( ) ) * uint ( Time : : SCALE )
, uint ( quot . denominator ( ) )
} ;
// precise computation can not be handled numerically...
return __framerate_approximation ( rational_cast < double > ( quot ) * Time : : SCALE ) ;
}
2013-09-01 17:36:05 +02:00
2018-04-28 03:02:02 +02:00
/** @internal stretch offset by a possibly fractional factor, and quantise into raw (micro tick) grid */
2013-01-07 05:43:01 +01:00
Offset
2018-04-28 03:02:02 +02:00
Offset : : stretchedByRationalFactor ( boost : : rational < int64_t > factor ) const
2013-01-07 05:43:01 +01:00
{
2018-04-28 03:02:02 +02:00
boost : : rational < int64_t > distance ( this - > t_ ) ;
2013-01-07 05:43:01 +01:00
distance * = factor ;
2025-05-17 23:12:47 +02:00
raw_time_64 microTicks = floordiv ( distance . numerator ( ) , distance . denominator ( ) ) ;
Library: rectify clipping of time::Duration (see #1263)
This is a deep refactoring to allow to represent the distance
between all valid time points as a time::Offset or time::Duration.
By design this is possible, since Time::MAX was defined as 1/30 of
the maximum value technically representable as int64_t. However,
introducing a different limiter for offsets and durations turns
out difficult, due to the inconsistencies in the exiting hierarchy
of temporal entities. Which in turn seems to stem from the unfortunate
decision to make time entities immutable, see #1261
Since the limiter is hard wired into the `time::TimeValue` constructor,
we are forced to create a "backdoor" of sorts, to pass up values
with different limiting from child classes. This would not be so
much of a problem if calculations weren't forced to go through `TimeVar`,
which does not distinguish between time points and time durations.
This solution rearranges all checks to be performed now by time::Offset,
while time::Duration will only take the absolute value at construction,
based on the fact that there is no valid construction path to yield
a duration which does not go through an offset first.
Later, when we're ready to sort out the implementation base of time values
(see #1258), this design issue should be revisited
- either we'll allow derived classes explicitly to invoke the limiter functions
- or we may be able to have an automatic conversion path from clearly
marked base implementation types, in which case we wouldn't use the
buildRaw_() and _raw() "backdoor" functions any more...
2022-12-05 00:58:32 +01:00
return Offset { buildRaw_ ( microTicks ) } ;
2013-01-07 05:43:01 +01:00
}
2024-02-11 17:38:20 +01:00
/** @warning loss of precision on large time values beyond double mantissa length `2^52 ≈ 4.5e15` */
2023-07-18 21:23:00 +02:00
Offset
Offset : : stretchedByFloatFactor ( double factor ) const
{
double distance ( this - > t_ ) ;
distance * = factor ;
2025-05-17 23:12:47 +02:00
raw_time_64 microTicks = floor ( distance ) ;
2023-07-18 21:23:00 +02:00
return Offset { buildRaw_ ( microTicks ) } ;
}
2013-01-07 05:43:01 +01:00
/** offset by the given number of frames. */
2013-11-18 00:01:43 +01:00
Offset : : Offset ( FrameCnt count , FrameRate const & fps )
Library: rectify clipping of time::Duration (see #1263)
This is a deep refactoring to allow to represent the distance
between all valid time points as a time::Offset or time::Duration.
By design this is possible, since Time::MAX was defined as 1/30 of
the maximum value technically representable as int64_t. However,
introducing a different limiter for offsets and durations turns
out difficult, due to the inconsistencies in the exiting hierarchy
of temporal entities. Which in turn seems to stem from the unfortunate
decision to make time entities immutable, see #1261
Since the limiter is hard wired into the `time::TimeValue` constructor,
we are forced to create a "backdoor" of sorts, to pass up values
with different limiting from child classes. This would not be so
much of a problem if calculations weren't forced to go through `TimeVar`,
which does not distinguish between time points and time durations.
This solution rearranges all checks to be performed now by time::Offset,
while time::Duration will only take the absolute value at construction,
based on the fact that there is no valid construction path to yield
a duration which does not go through an offset first.
Later, when we're ready to sort out the implementation base of time values
(see #1258), this design issue should be revisited
- either we'll allow derived classes explicitly to invoke the limiter functions
- or we may be able to have an automatic conversion path from clearly
marked base implementation types, in which case we wouldn't use the
buildRaw_() and _raw() "backdoor" functions any more...
2022-12-05 00:58:32 +01:00
: TimeValue { buildRaw_ (
count ? ( count < 0 ? - 1 : + 1 ) * lumiera_framecount_to_time ( : : abs ( count ) , fps )
: _raw ( Duration : : NIL ) ) }
2013-01-07 05:43:01 +01:00
{ }
/** constant to indicate "no duration" */
2022-11-29 02:00:41 +01:00
const Duration Duration : : NIL { Time : : ZERO } ;
2013-01-07 05:43:01 +01:00
2022-11-29 02:00:41 +01:00
/** maximum possible temporal extension */
Library: rectify clipping of time::Duration (see #1263)
This is a deep refactoring to allow to represent the distance
between all valid time points as a time::Offset or time::Duration.
By design this is possible, since Time::MAX was defined as 1/30 of
the maximum value technically representable as int64_t. However,
introducing a different limiter for offsets and durations turns
out difficult, due to the inconsistencies in the exiting hierarchy
of temporal entities. Which in turn seems to stem from the unfortunate
decision to make time entities immutable, see #1261
Since the limiter is hard wired into the `time::TimeValue` constructor,
we are forced to create a "backdoor" of sorts, to pass up values
with different limiting from child classes. This would not be so
much of a problem if calculations weren't forced to go through `TimeVar`,
which does not distinguish between time points and time durations.
This solution rearranges all checks to be performed now by time::Offset,
while time::Duration will only take the absolute value at construction,
based on the fact that there is no valid construction path to yield
a duration which does not go through an offset first.
Later, when we're ready to sort out the implementation base of time values
(see #1258), this design issue should be revisited
- either we'll allow derived classes explicitly to invoke the limiter functions
- or we may be able to have an automatic conversion path from clearly
marked base implementation types, in which case we wouldn't use the
buildRaw_() and _raw() "backdoor" functions any more...
2022-12-05 00:58:32 +01:00
const Duration Duration : : MAX = [ ] {
auto maxDelta { Time : : MAX - Time : : MIN } ;
// bypass limit check, which requires Duration::MAX
return reinterpret_cast < Duration const & > ( maxDelta ) ;
} ( ) ;
2023-04-27 19:38:37 +02:00
const TimeSpan TimeSpan : : ALL { Time : : MIN , Duration : : MAX } ;
2013-01-07 05:43:01 +01:00
} } // namespace lib::Time
Timehandling: choose safer representation for fractional seconds (closes #939)
When drafting the time handling framework some years ago,
I foresaw the possible danger of mixing up numbers relating
to fractional seconds, with other plain numbers intended as
frame counts or as micro ticks. Thus I deliberately picked
an incompatible integer type for FSecs = boost::rational<long>
However, using long is problematic in itself, since its actual
bit length is not fixed, and especially on 32bit platforms long
is quite surprisingly defined to be the same as int.
However, meanwhile, using the new C++ features, I have blocked
pretty much any possible implicit conversion path, requiring
explicit conversions in the relevant ctor invocations. So,
after weighting in the alternatives, FSecs is now defined
as boost::rational<int64_t>.
2020-02-17 02:36:54 +01:00
namespace util {
string
StringConv < lib : : time : : FSecs , void > : : invoke ( lib : : time : : FSecs val ) noexcept
{
return lib : : time : : renderFraction ( val , " sec " ) ;
}
} // namespace util
2013-01-07 05:43:01 +01:00
/* ===== implementation of the C API functions ===== */
2022-12-18 03:47:40 +01:00
/// @todo this utility function could be factored out into a `FSecs` or `RSec` class ///////////////////////TICKET #1262
2025-05-17 23:12:47 +02:00
raw_time_64
2011-01-09 04:56:46 +01:00
lumiera_rational_to_time ( FSecs const & fractionalSeconds )
2011-01-06 13:30:27 +01:00
{
2022-12-18 03:47:40 +01:00
// avoid numeric wrap from values not representable as 64bit µ-ticks
if ( abs ( fractionalSeconds ) > lib : : time : : FSEC_MAX )
return ( fractionalSeconds < 0 ? - 1 : + 1 )
* std : : numeric_limits < int64_t > : : max ( ) ;
2025-05-17 23:12:47 +02:00
return raw_time_64 ( util : : reQuant ( fractionalSeconds . numerator ( )
Timeline: safely calculate sum/difference of large fractional times
...in a similar vein as done for the product calculation.
In this case, we need to check the dimensions carefully and pick
the best calculation path, but as long as the overall result can
be represented, it should be possible to carry out the calculation
with fractional values, albeit introducing a small error.
As a follow-up, I have now also refactored the re-quantisation
functions, to be usable for general requantisation to another grid,
and I used these to replace the *naive* implementation of the
conversion FSecs -> µ-Grid, which caused a lot of integer-wrap-around
However, while the test now works basically without glitch or wrap,
the window position is still numerically of by 1e-6, which becomes
quite noticeably here due to the large overall span used for the test.
2022-12-01 23:23:50 +01:00
, fractionalSeconds . denominator ( )
, lib : : time : : TimeValue : : SCALE
) ) ;
2011-01-06 13:30:27 +01:00
}
2025-05-17 23:12:47 +02:00
raw_time_64
2011-05-13 04:04:02 +02:00
lumiera_framecount_to_time ( uint64_t frameCount , FrameRate const & fps )
{
// convert to 64bit
boost : : rational < uint64_t > framerate ( fps . numerator ( ) , fps . denominator ( ) ) ;
2025-05-17 23:12:47 +02:00
return rational_cast < raw_time_64 > ( lib : : time : : TimeValue : : SCALE * frameCount / framerate ) ;
2011-05-13 04:04:02 +02:00
}
2011-01-09 04:56:46 +01:00
2011-01-14 05:33:50 +01:00
2011-01-06 13:30:27 +01:00
2025-05-17 23:12:47 +02:00
raw_time_64
2010-12-06 16:18:54 +01:00
lumiera_build_time ( long millis , uint secs , uint mins , uint hours )
2009-07-12 22:51:04 +02:00
{
2025-05-17 23:12:47 +02:00
raw_time_64 time = millis
2009-07-12 22:51:04 +02:00
+ 1000 * secs
+ 1000 * 60 * mins
+ 1000 * 60 * 60 * hours ;
2018-11-17 18:00:39 +01:00
time * = TIME_SCALE_MS ;
2009-07-12 22:51:04 +02:00
return time ;
}
2025-05-17 23:12:47 +02:00
raw_time_64
2011-05-21 06:58:15 +02:00
lumiera_build_time_fps ( uint fps , uint frames , uint secs , uint mins , uint hours )
2010-12-13 18:16:15 +01:00
{
2025-05-17 23:12:47 +02:00
raw_time_64 time = 1000LL * frames / fps
2010-12-13 18:16:15 +01:00
+ 1000 * secs
+ 1000 * 60 * mins
+ 1000 * 60 * 60 * hours ;
2018-11-17 18:00:39 +01:00
time * = TIME_SCALE_MS ;
2010-12-13 18:16:15 +01:00
return time ;
}
2010-12-06 16:18:54 +01:00
int
2025-05-17 23:12:47 +02:00
lumiera_time_hours ( raw_time_64 time )
2010-12-06 16:18:54 +01:00
{
2018-11-17 18:00:39 +01:00
return time / TIME_SCALE_MS / 1000 / 60 / 60 ;
2010-12-06 16:18:54 +01:00
}
2009-07-12 22:51:04 +02:00
2010-12-06 16:18:54 +01:00
int
2025-05-17 23:12:47 +02:00
lumiera_time_minutes ( raw_time_64 time )
2010-12-06 16:18:54 +01:00
{
2018-11-17 18:00:39 +01:00
return ( time / TIME_SCALE_MS / 1000 / 60 ) % 60 ;
2010-12-06 16:18:54 +01:00
}
int
2025-05-17 23:12:47 +02:00
lumiera_time_seconds ( raw_time_64 time )
2010-12-06 16:18:54 +01:00
{
2018-11-17 18:00:39 +01:00
return ( time / TIME_SCALE_MS / 1000 ) % 60 ;
2010-12-06 16:18:54 +01:00
}
int
2025-05-17 23:12:47 +02:00
lumiera_time_millis ( raw_time_64 time )
2010-12-06 16:18:54 +01:00
{
2018-11-17 18:00:39 +01:00
return ( time / TIME_SCALE_MS ) % 1000 ;
2010-12-06 16:18:54 +01:00
}
2010-12-10 12:53:39 +01:00
int
2025-05-17 23:12:47 +02:00
lumiera_time_frames ( raw_time_64 time , uint fps )
2010-12-10 12:53:39 +01:00
{
2011-05-21 06:58:15 +02:00
REQUIRE ( fps < uint ( std : : numeric_limits < int > : : max ( ) ) ) ;
2018-11-17 18:00:39 +01:00
return floordiv < int > ( lumiera_time_millis ( time ) * int ( fps ) , TIME_SCALE_MS ) ;
2010-12-10 12:53:39 +01:00
}
2010-12-13 18:22:12 +01:00
2011-05-21 06:58:15 +02:00
2025-05-27 23:39:36 +02:00
namespace lib {
namespace time { ////////////////////////////////////////////////////////////////////////////////////////////TICKET #1259 : move all calculation functions into a C++ namespace
} } // lib::time