2010-12-21 02:05:13 +01:00
|
|
|
|
/*
|
|
|
|
|
|
Quantiser - aligning time values to a time grid
|
|
|
|
|
|
|
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)
|
clean-up: relocate frame quantisation operations
The `FixedFrameQuantiser` relied on three functions from the raw-time handling library.
Since this (and NTSC drop-frame) are the only usages, these functions
can be relocated into the implemntation translation unit `lib/time/quantiser.cpp`
On closer inspection, this reveals some room for improvements:
Instead of relying on raw-computation functions written in C,
we could rather revert the dependency and express these computations
in terms of our Time-entities, which are written in C++, are much more
systematic and provide consistency checks and protection against numeric
overflow, all integrated with linear arithmetic and concise notation.
After performing these rearrangements,
most of the functions can be collapsed into ''almost nothing''.
This was taken as opportunity to re-check and improve the remaining
implementation core of the `FixedFrameQuantiser` -- the handling of
extreme corner cases can be much improved, now representing the
"grid-local time" as `Offset`, which doubles the possible value range.
The reworked unit test shows that, with this change, now the limitation
happens prior to quantisation, meaning that we always get a grid-aligned
result, even in the most extreme corner cases.
2025-05-28 23:49:01 +02:00
|
|
|
|
2010, Stefan Kangas <skangas@skangas.se>
|
|
|
|
|
|
2010-2025, Hermann Vosseler <Ichthyostega@web.de>
|
2010-12-21 02:05:13 +01: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.
|
2010-12-21 02:05:13 +01: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
|
|
|
|
* *****************************************************************/
|
2010-12-21 02:05:13 +01:00
|
|
|
|
|
|
|
|
|
|
|
2016-11-03 18:22:31 +01:00
|
|
|
|
/** @file quantiser.cpp
|
2016-11-07 16:22:04 +01:00
|
|
|
|
** Implementation of support functions to create grid-aligned time and timecode values
|
2016-11-03 18:20:10 +01:00
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
|
2011-01-04 01:45:11 +01:00
|
|
|
|
#include "lib/time/quantiser.hpp"
|
2010-12-21 02:05:13 +01:00
|
|
|
|
#include "lib/time/timevalue.hpp"
|
2011-01-05 18:15:44 +01:00
|
|
|
|
#include "lib/time/timequant.hpp"
|
clean-up: relocate frame quantisation operations
The `FixedFrameQuantiser` relied on three functions from the raw-time handling library.
Since this (and NTSC drop-frame) are the only usages, these functions
can be relocated into the implemntation translation unit `lib/time/quantiser.cpp`
On closer inspection, this reveals some room for improvements:
Instead of relying on raw-computation functions written in C,
we could rather revert the dependency and express these computations
in terms of our Time-entities, which are written in C++, are much more
systematic and provide consistency checks and protection against numeric
overflow, all integrated with linear arithmetic and concise notation.
After performing these rearrangements,
most of the functions can be collapsed into ''almost nothing''.
This was taken as opportunity to re-check and improve the remaining
implementation core of the `FixedFrameQuantiser` -- the handling of
extreme corner cases can be much improved, now representing the
"grid-local time" as `Offset`, which doubles the possible value range.
The reworked unit test shows that, with this change, now the limitation
happens prior to quantisation, meaning that we always get a grid-aligned
result, even in the most extreme corner cases.
2025-05-28 23:49:01 +02:00
|
|
|
|
#include "lib/util-quant.hpp"
|
|
|
|
|
|
#include "lib/rational.hpp"
|
2011-01-06 13:31:13 +01:00
|
|
|
|
|
2010-12-21 02:05:13 +01:00
|
|
|
|
using std::string;
|
|
|
|
|
|
|
|
|
|
|
|
|
2011-01-06 13:31:13 +01:00
|
|
|
|
namespace error = lumiera::error;
|
|
|
|
|
|
|
2010-12-21 02:05:13 +01:00
|
|
|
|
namespace lib {
|
|
|
|
|
|
namespace time {
|
|
|
|
|
|
|
clean-up: relocate frame quantisation operations
The `FixedFrameQuantiser` relied on three functions from the raw-time handling library.
Since this (and NTSC drop-frame) are the only usages, these functions
can be relocated into the implemntation translation unit `lib/time/quantiser.cpp`
On closer inspection, this reveals some room for improvements:
Instead of relying on raw-computation functions written in C,
we could rather revert the dependency and express these computations
in terms of our Time-entities, which are written in C++, are much more
systematic and provide consistency checks and protection against numeric
overflow, all integrated with linear arithmetic and concise notation.
After performing these rearrangements,
most of the functions can be collapsed into ''almost nothing''.
This was taken as opportunity to re-check and improve the remaining
implementation core of the `FixedFrameQuantiser` -- the handling of
extreme corner cases can be much improved, now representing the
"grid-local time" as `Offset`, which doubles the possible value range.
The reworked unit test shows that, with this change, now the limitation
happens prior to quantisation, meaning that we always get a grid-aligned
result, even in the most extreme corner cases.
2025-05-28 23:49:01 +02:00
|
|
|
|
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<raw_time_64>::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));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2010-12-21 02:05:13 +01:00
|
|
|
|
|
2011-06-05 00:10:37 +02:00
|
|
|
|
PQuant
|
|
|
|
|
|
getDefaultGridFallback()
|
|
|
|
|
|
{
|
|
|
|
|
|
static PQuant globalDefaultGrid (new FixedFrameQuantiser(1));
|
|
|
|
|
|
return globalDefaultGrid; ///////////////////////TICKET #810
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2011-01-06 13:31:13 +01:00
|
|
|
|
|
2011-01-15 03:49:35 +01:00
|
|
|
|
Grid::~Grid() { } // hint to emit the VTable here...
|
2011-01-06 04:21:18 +01:00
|
|
|
|
|
2011-01-06 13:31:13 +01:00
|
|
|
|
|
2012-12-01 07:46:49 +01:00
|
|
|
|
/* 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
|
clean-up: relocate frame quantisation operations
The `FixedFrameQuantiser` relied on three functions from the raw-time handling library.
Since this (and NTSC drop-frame) are the only usages, these functions
can be relocated into the implemntation translation unit `lib/time/quantiser.cpp`
On closer inspection, this reveals some room for improvements:
Instead of relying on raw-computation functions written in C,
we could rather revert the dependency and express these computations
in terms of our Time-entities, which are written in C++, are much more
systematic and provide consistency checks and protection against numeric
overflow, all integrated with linear arithmetic and concise notation.
After performing these rearrangements,
most of the functions can be collapsed into ''almost nothing''.
This was taken as opportunity to re-check and improve the remaining
implementation core of the `FixedFrameQuantiser` -- the handling of
extreme corner cases can be much improved, now representing the
"grid-local time" as `Offset`, which doubles the possible value range.
The reworked unit test shows that, with this change, now the limitation
happens prior to quantisation, meaning that we always get a grid-aligned
result, even in the most extreme corner cases.
2025-05-28 23:49:01 +02:00
|
|
|
|
* This allows to use the Advice-system to retrieve grid definitions directly from the session
|
2011-01-16 23:58:42 +01:00
|
|
|
|
*/
|
2011-01-05 18:15:44 +01:00
|
|
|
|
|
|
|
|
|
|
|
2011-01-16 23:58:42 +01:00
|
|
|
|
/**
|
|
|
|
|
|
* build a quantised time value by explicitly specifying a
|
|
|
|
|
|
* grid alignment facility and without any hidden reference
|
|
|
|
|
|
* to the Lumiera session. This is mainly intended for
|
|
|
|
|
|
* debugging and unit testing.
|
|
|
|
|
|
*/
|
2011-01-15 00:52:02 +01:00
|
|
|
|
QuTime::QuTime (TimeValue raw, PQuant quantisation_to_use)
|
2011-01-13 23:56:52 +01:00
|
|
|
|
: Time(raw)
|
2011-01-15 00:52:02 +01:00
|
|
|
|
, quantiser_(quantisation_to_use)
|
2011-01-05 18:15:44 +01:00
|
|
|
|
{ }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2011-01-06 13:31:13 +01:00
|
|
|
|
/** Create a quantiser based on a fixed constant spaced grid, rooted at the reference point
|
|
|
|
|
|
* as origin of the scale. Quantisation then means to determine the grid interval containing
|
|
|
|
|
|
* a given raw time value. Here, the grid interval number zero \em starts at the origin;
|
|
|
|
|
|
* each interval includes its lower bound and excludes its upper bound.*/
|
2011-01-09 04:56:46 +01:00
|
|
|
|
FixedFrameQuantiser::FixedFrameQuantiser (FrameRate const& frames_per_second, TimeValue referencePoint)
|
2011-01-06 13:31:13 +01:00
|
|
|
|
: origin_(referencePoint)
|
2011-01-09 11:41:08 +01:00
|
|
|
|
, raster_(__ensure_nonzero(frames_per_second.duration()))
|
|
|
|
|
|
{ }
|
|
|
|
|
|
|
|
|
|
|
|
FixedFrameQuantiser::FixedFrameQuantiser (Duration const& frame_duration, TimeValue referencePoint)
|
|
|
|
|
|
: origin_(referencePoint)
|
|
|
|
|
|
, raster_(__ensure_nonzero(frame_duration))
|
2011-01-05 18:15:44 +01:00
|
|
|
|
{ }
|
|
|
|
|
|
|
2010-12-21 02:05:13 +01:00
|
|
|
|
|
|
|
|
|
|
|
2011-05-05 19:05:48 +02:00
|
|
|
|
|
|
|
|
|
|
/** convenience shortcut: \em materialise a raw time value
|
clean-up: relocate frame quantisation operations
The `FixedFrameQuantiser` relied on three functions from the raw-time handling library.
Since this (and NTSC drop-frame) are the only usages, these functions
can be relocated into the implemntation translation unit `lib/time/quantiser.cpp`
On closer inspection, this reveals some room for improvements:
Instead of relying on raw-computation functions written in C,
we could rather revert the dependency and express these computations
in terms of our Time-entities, which are written in C++, are much more
systematic and provide consistency checks and protection against numeric
overflow, all integrated with linear arithmetic and concise notation.
After performing these rearrangements,
most of the functions can be collapsed into ''almost nothing''.
This was taken as opportunity to re-check and improve the remaining
implementation core of the `FixedFrameQuantiser` -- the handling of
extreme corner cases can be much improved, now representing the
"grid-local time" as `Offset`, which doubles the possible value range.
The reworked unit test shows that, with this change, now the limitation
happens prior to quantisation, meaning that we always get a grid-aligned
result, even in the most extreme corner cases.
2025-05-28 23:49:01 +02:00
|
|
|
|
* based on this grid or time axis, but returning a raw time value.
|
2023-05-29 04:43:10 +02:00
|
|
|
|
* Implemented as combination of the #gridPoint and #timeOf operations,
|
2011-05-05 19:05:48 +02:00
|
|
|
|
* i.e. we quantise into this scale, but transform the result back onto
|
2023-01-02 23:45:55 +01:00
|
|
|
|
* the global raw time value scale.
|
2011-05-05 19:05:48 +02:00
|
|
|
|
* @warning this operation incurs information loss. Values may be rounded
|
|
|
|
|
|
* and / or clipped, according to the grid used. And, contrary to
|
|
|
|
|
|
* a QuTime value, the information about the actual grid is
|
2023-05-31 03:27:13 +02:00
|
|
|
|
* discarded. Please resist the temptation to abuse this
|
|
|
|
|
|
* operation to "just get a simple number", without
|
|
|
|
|
|
* understanding what quantisation means!
|
2011-05-05 19:05:48 +02:00
|
|
|
|
*/
|
|
|
|
|
|
TimeValue
|
|
|
|
|
|
Quantiser::materialise (TimeValue const& raw) const
|
|
|
|
|
|
{
|
|
|
|
|
|
return timeOf (gridPoint (raw));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2022-12-05 01:05:23 +01:00
|
|
|
|
/** grid quantisation (alignment).
|
|
|
|
|
|
* Determine the next lower grid interval start point,
|
|
|
|
|
|
* using a simple constant spaced time grid defined by
|
|
|
|
|
|
* origin and framerate stored within this quantiser.
|
|
|
|
|
|
* @warning returned frame count might exceed the valid
|
|
|
|
|
|
* range when converting back into a TimeValue.
|
clean-up: relocate frame quantisation operations
The `FixedFrameQuantiser` relied on three functions from the raw-time handling library.
Since this (and NTSC drop-frame) are the only usages, these functions
can be relocated into the implemntation translation unit `lib/time/quantiser.cpp`
On closer inspection, this reveals some room for improvements:
Instead of relying on raw-computation functions written in C,
we could rather revert the dependency and express these computations
in terms of our Time-entities, which are written in C++, are much more
systematic and provide consistency checks and protection against numeric
overflow, all integrated with linear arithmetic and concise notation.
After performing these rearrangements,
most of the functions can be collapsed into ''almost nothing''.
This was taken as opportunity to re-check and improve the remaining
implementation core of the `FixedFrameQuantiser` -- the handling of
extreme corner cases can be much improved, now representing the
"grid-local time" as `Offset`, which doubles the possible value range.
The reworked unit test shows that, with this change, now the limitation
happens prior to quantisation, meaning that we always get a grid-aligned
result, even in the most extreme corner cases.
2025-05-28 23:49:01 +02:00
|
|
|
|
* @see FixedFrameQuantiser::grid_aligned
|
2022-12-05 01:05:23 +01:00
|
|
|
|
*/
|
|
|
|
|
|
FrameCnt
|
|
|
|
|
|
FixedFrameQuantiser::gridPoint (TimeValue const& rawTime) const
|
|
|
|
|
|
{
|
clean-up: relocate frame quantisation operations
The `FixedFrameQuantiser` relied on three functions from the raw-time handling library.
Since this (and NTSC drop-frame) are the only usages, these functions
can be relocated into the implemntation translation unit `lib/time/quantiser.cpp`
On closer inspection, this reveals some room for improvements:
Instead of relying on raw-computation functions written in C,
we could rather revert the dependency and express these computations
in terms of our Time-entities, which are written in C++, are much more
systematic and provide consistency checks and protection against numeric
overflow, all integrated with linear arithmetic and concise notation.
After performing these rearrangements,
most of the functions can be collapsed into ''almost nothing''.
This was taken as opportunity to re-check and improve the remaining
implementation core of the `FixedFrameQuantiser` -- the handling of
extreme corner cases can be much improved, now representing the
"grid-local time" as `Offset`, which doubles the possible value range.
The reworked unit test shows that, with this change, now the limitation
happens prior to quantisation, meaning that we always get a grid-aligned
result, even in the most extreme corner cases.
2025-05-28 23:49:01 +02:00
|
|
|
|
return grid_aligned (Offset{origin_, rawTime}, raster_);
|
2022-12-05 01:05:23 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** transform into the local time scale grid aligned.
|
clean-up: relocate frame quantisation operations
The `FixedFrameQuantiser` relied on three functions from the raw-time handling library.
Since this (and NTSC drop-frame) are the only usages, these functions
can be relocated into the implemntation translation unit `lib/time/quantiser.cpp`
On closer inspection, this reveals some room for improvements:
Instead of relying on raw-computation functions written in C,
we could rather revert the dependency and express these computations
in terms of our Time-entities, which are written in C++, are much more
systematic and provide consistency checks and protection against numeric
overflow, all integrated with linear arithmetic and concise notation.
After performing these rearrangements,
most of the functions can be collapsed into ''almost nothing''.
This was taken as opportunity to re-check and improve the remaining
implementation core of the `FixedFrameQuantiser` -- the handling of
extreme corner cases can be much improved, now representing the
"grid-local time" as `Offset`, which doubles the possible value range.
The reworked unit test shows that, with this change, now the limitation
happens prior to quantisation, meaning that we always get a grid-aligned
result, even in the most extreme corner cases.
2025-05-28 23:49:01 +02:00
|
|
|
|
* The actual calculation first determines the number of the grid interval
|
|
|
|
|
|
* containing the given rawTime, followed by multiplying this interval number
|
2011-01-06 13:31:13 +01:00
|
|
|
|
* with the grid spacing.
|
clean-up: relocate frame quantisation operations
The `FixedFrameQuantiser` relied on three functions from the raw-time handling library.
Since this (and NTSC drop-frame) are the only usages, these functions
can be relocated into the implemntation translation unit `lib/time/quantiser.cpp`
On closer inspection, this reveals some room for improvements:
Instead of relying on raw-computation functions written in C,
we could rather revert the dependency and express these computations
in terms of our Time-entities, which are written in C++, are much more
systematic and provide consistency checks and protection against numeric
overflow, all integrated with linear arithmetic and concise notation.
After performing these rearrangements,
most of the functions can be collapsed into ''almost nothing''.
This was taken as opportunity to re-check and improve the remaining
implementation core of the `FixedFrameQuantiser` -- the handling of
extreme corner cases can be much improved, now representing the
"grid-local time" as `Offset`, which doubles the possible value range.
The reworked unit test shows that, with this change, now the limitation
happens prior to quantisation, meaning that we always get a grid-aligned
result, even in the most extreme corner cases.
2025-05-28 23:49:01 +02:00
|
|
|
|
* @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
|
2011-01-06 13:31:13 +01:00
|
|
|
|
*/
|
clean-up: relocate frame quantisation operations
The `FixedFrameQuantiser` relied on three functions from the raw-time handling library.
Since this (and NTSC drop-frame) are the only usages, these functions
can be relocated into the implemntation translation unit `lib/time/quantiser.cpp`
On closer inspection, this reveals some room for improvements:
Instead of relying on raw-computation functions written in C,
we could rather revert the dependency and express these computations
in terms of our Time-entities, which are written in C++, are much more
systematic and provide consistency checks and protection against numeric
overflow, all integrated with linear arithmetic and concise notation.
After performing these rearrangements,
most of the functions can be collapsed into ''almost nothing''.
This was taken as opportunity to re-check and improve the remaining
implementation core of the `FixedFrameQuantiser` -- the handling of
extreme corner cases can be much improved, now representing the
"grid-local time" as `Offset`, which doubles the possible value range.
The reworked unit test shows that, with this change, now the limitation
happens prior to quantisation, meaning that we always get a grid-aligned
result, even in the most extreme corner cases.
2025-05-28 23:49:01 +02:00
|
|
|
|
Offset
|
2022-12-05 01:05:23 +01:00
|
|
|
|
FixedFrameQuantiser::gridLocal (TimeValue const& rawTime) const
|
2011-01-06 04:21:18 +01:00
|
|
|
|
{
|
clean-up: relocate frame quantisation operations
The `FixedFrameQuantiser` relied on three functions from the raw-time handling library.
Since this (and NTSC drop-frame) are the only usages, these functions
can be relocated into the implemntation translation unit `lib/time/quantiser.cpp`
On closer inspection, this reveals some room for improvements:
Instead of relying on raw-computation functions written in C,
we could rather revert the dependency and express these computations
in terms of our Time-entities, which are written in C++, are much more
systematic and provide consistency checks and protection against numeric
overflow, all integrated with linear arithmetic and concise notation.
After performing these rearrangements,
most of the functions can be collapsed into ''almost nothing''.
This was taken as opportunity to re-check and improve the remaining
implementation core of the `FixedFrameQuantiser` -- the handling of
extreme corner cases can be much improved, now representing the
"grid-local time" as `Offset`, which doubles the possible value range.
The reworked unit test shows that, with this change, now the limitation
happens prior to quantisation, meaning that we always get a grid-aligned
result, even in the most extreme corner cases.
2025-05-28 23:49:01 +02:00
|
|
|
|
return raster_ * grid_aligned (Offset{origin_, rawTime}
|
|
|
|
|
|
,raster_);
|
2011-01-06 04:21:18 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2011-01-14 05:33:50 +01:00
|
|
|
|
/** calculate time value of a grid interval (frame) start point
|
clean-up: relocate frame quantisation operations
The `FixedFrameQuantiser` relied on three functions from the raw-time handling library.
Since this (and NTSC drop-frame) are the only usages, these functions
can be relocated into the implemntation translation unit `lib/time/quantiser.cpp`
On closer inspection, this reveals some room for improvements:
Instead of relying on raw-computation functions written in C,
we could rather revert the dependency and express these computations
in terms of our Time-entities, which are written in C++, are much more
systematic and provide consistency checks and protection against numeric
overflow, all integrated with linear arithmetic and concise notation.
After performing these rearrangements,
most of the functions can be collapsed into ''almost nothing''.
This was taken as opportunity to re-check and improve the remaining
implementation core of the `FixedFrameQuantiser` -- the handling of
extreme corner cases can be much improved, now representing the
"grid-local time" as `Offset`, which doubles the possible value range.
The reworked unit test shows that, with this change, now the limitation
happens prior to quantisation, meaning that we always get a grid-aligned
result, even in the most extreme corner cases.
2025-05-28 23:49:01 +02:00
|
|
|
|
* @param gridPoint index number of the grid point (0 is at origin)
|
|
|
|
|
|
* @return time point measured in Lumiera internal time
|
2011-01-14 05:33:50 +01:00
|
|
|
|
* @warning returned time values are limited by the
|
|
|
|
|
|
* valid range of lumiera::Time
|
|
|
|
|
|
*/
|
|
|
|
|
|
TimeValue
|
2013-11-18 00:01:43 +01:00
|
|
|
|
FixedFrameQuantiser::timeOf (FrameCnt gridPoint) const
|
2011-01-14 05:33:50 +01:00
|
|
|
|
{
|
clean-up: relocate frame quantisation operations
The `FixedFrameQuantiser` relied on three functions from the raw-time handling library.
Since this (and NTSC drop-frame) are the only usages, these functions
can be relocated into the implemntation translation unit `lib/time/quantiser.cpp`
On closer inspection, this reveals some room for improvements:
Instead of relying on raw-computation functions written in C,
we could rather revert the dependency and express these computations
in terms of our Time-entities, which are written in C++, are much more
systematic and provide consistency checks and protection against numeric
overflow, all integrated with linear arithmetic and concise notation.
After performing these rearrangements,
most of the functions can be collapsed into ''almost nothing''.
This was taken as opportunity to re-check and improve the remaining
implementation core of the `FixedFrameQuantiser` -- the handling of
extreme corner cases can be much improved, now representing the
"grid-local time" as `Offset`, which doubles the possible value range.
The reworked unit test shows that, with this change, now the limitation
happens prior to quantisation, meaning that we always get a grid-aligned
result, even in the most extreme corner cases.
2025-05-28 23:49:01 +02:00
|
|
|
|
return origin_ + gridPoint * raster_;
|
2011-01-14 05:33:50 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2011-01-13 23:56:52 +01:00
|
|
|
|
|
2011-01-15 03:49:35 +01:00
|
|
|
|
/** 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)
|
clean-up: relocate frame quantisation operations
The `FixedFrameQuantiser` relied on three functions from the raw-time handling library.
Since this (and NTSC drop-frame) are the only usages, these functions
can be relocated into the implemntation translation unit `lib/time/quantiser.cpp`
On closer inspection, this reveals some room for improvements:
Instead of relying on raw-computation functions written in C,
we could rather revert the dependency and express these computations
in terms of our Time-entities, which are written in C++, are much more
systematic and provide consistency checks and protection against numeric
overflow, all integrated with linear arithmetic and concise notation.
After performing these rearrangements,
most of the functions can be collapsed into ''almost nothing''.
This was taken as opportunity to re-check and improve the remaining
implementation core of the `FixedFrameQuantiser` -- the handling of
extreme corner cases can be much improved, now representing the
"grid-local time" as `Offset`, which doubles the possible value range.
The reworked unit test shows that, with this change, now the limitation
happens prior to quantisation, meaning that we always get a grid-aligned
result, even in the most extreme corner cases.
2025-05-28 23:49:01 +02:00
|
|
|
|
* @return time point measured in Lumiera internal time
|
2011-01-15 03:49:35 +01:00
|
|
|
|
* @warning returned time values are limited by the
|
|
|
|
|
|
* valid range of lumiera::Time
|
|
|
|
|
|
*/
|
|
|
|
|
|
TimeValue
|
|
|
|
|
|
FixedFrameQuantiser::timeOf (FSecs gridTime, int gridOffset) const
|
|
|
|
|
|
{
|
|
|
|
|
|
Time gt(gridTime);
|
|
|
|
|
|
TimeVar timePoint = gt + origin_;
|
2011-06-08 02:12:38 +02:00
|
|
|
|
timePoint += gridOffset * Offset(raster_);
|
2011-01-15 03:49:35 +01:00
|
|
|
|
return timePoint;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2011-01-13 23:56:52 +01:00
|
|
|
|
|
2010-12-21 02:05:13 +01:00
|
|
|
|
|
2011-01-04 01:45:11 +01:00
|
|
|
|
LUMIERA_ERROR_DEFINE (UNKNOWN_GRID, "referring to an undefined grid or scale in value quantisation");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
clean-up: relocate frame quantisation operations
The `FixedFrameQuantiser` relied on three functions from the raw-time handling library.
Since this (and NTSC drop-frame) are the only usages, these functions
can be relocated into the implemntation translation unit `lib/time/quantiser.cpp`
On closer inspection, this reveals some room for improvements:
Instead of relying on raw-computation functions written in C,
we could rather revert the dependency and express these computations
in terms of our Time-entities, which are written in C++, are much more
systematic and provide consistency checks and protection against numeric
overflow, all integrated with linear arithmetic and concise notation.
After performing these rearrangements,
most of the functions can be collapsed into ''almost nothing''.
This was taken as opportunity to re-check and improve the remaining
implementation core of the `FixedFrameQuantiser` -- the handling of
extreme corner cases can be much improved, now representing the
"grid-local time" as `Offset`, which doubles the possible value range.
The reworked unit test shows that, with this change, now the limitation
happens prior to quantisation, meaning that we always get a grid-aligned
result, even in the most extreme corner cases.
2025-05-28 23:49:01 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* ===== NTSC drop-frame conversions ===== */
|
2011-01-06 13:31:13 +01:00
|
|
|
|
|
clean-up: relocate frame quantisation operations
The `FixedFrameQuantiser` relied on three functions from the raw-time handling library.
Since this (and NTSC drop-frame) are the only usages, these functions
can be relocated into the implemntation translation unit `lib/time/quantiser.cpp`
On closer inspection, this reveals some room for improvements:
Instead of relying on raw-computation functions written in C,
we could rather revert the dependency and express these computations
in terms of our Time-entities, which are written in C++, are much more
systematic and provide consistency checks and protection against numeric
overflow, all integrated with linear arithmetic and concise notation.
After performing these rearrangements,
most of the functions can be collapsed into ''almost nothing''.
This was taken as opportunity to re-check and improve the remaining
implementation core of the `FixedFrameQuantiser` -- the handling of
extreme corner cases can be much improved, now representing the
"grid-local time" as `Offset`, which doubles the possible value range.
The reworked unit test shows that, with this change, now the limitation
happens prior to quantisation, meaning that we always get a grid-aligned
result, even in the most extreme corner cases.
2025-05-28 23:49:01 +02:00
|
|
|
|
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
|