SMPTE Timecode (without drop frame) unit test pass

This commit is contained in:
Fischlurch 2011-01-21 20:24:41 +01:00
parent 1a07cc9bb2
commit 9d75739089
9 changed files with 139 additions and 29 deletions

View file

@ -302,19 +302,29 @@ namespace time {
/** special Digxel to show a sign.
* @note values limited to +1 and -1 */
struct Signum
: Digxel<int,digxel::SignFormatter>
class Signum
: public Digxel<int,digxel::SignFormatter>
{
Signum() { setValueRaw(1); }
static int
just_the_sign (int val)
{
return val<0? -1:+1;
}
public:
Signum()
{
setValueRaw(1);
mutator = just_the_sign;
}
void
operator= (int n)
{
int newSign = 0 > mutator(n)? -1:+1;
this->setValueRaw (newSign);
this->setValueRaw (mutator(n));
}
friend int operator*= (Signum s, int c) { s = c*s; return s; }
friend int operator*= (Signum& s, int c) { s = c*s; return s; }
};

View file

@ -84,6 +84,7 @@ namespace time {
static void rebuild (SmpteTC&, QuantR, TimeValue const&);
static TimeValue evaluate (SmpteTC const&, QuantR);
static uint getFramerate (QuantR, TimeValue const&);
static void rangeLimitStrategy (SmpteTC&, int& rawHours);
};

View file

@ -34,6 +34,7 @@
using std::string;
using util::unConst;
using util::isSameObject;
using util::floorwrap;
namespace lib {
@ -68,6 +69,7 @@ namespace time {
void
Smpte::rebuild (SmpteTC& tc, QuantR quantiser, TimeValue const& rawTime)
{
tc.clear();
tc.frames = quantiser.gridPoint (rawTime);
// will automatically wrap over to the secs, minutes and hour fields
}
@ -82,7 +84,7 @@ namespace time {
+ tc.secs * frameRate
+ tc.mins * frameRate * 60
+ tc.hours * frameRate * 60 * 60;
return quantiser.timeOf (gridPoint);
return quantiser.timeOf (tc.sgn * gridPoint);
}
/** yield the Framerate in effect at that point.
@ -108,9 +110,73 @@ namespace time {
ENSURE (0 < effectiveFrames);
return uint(effectiveFrames);
}
/** handle the limits of SMPTE timecode range.
* This is an extension and configuration point to control how
* to handle values beyond the official SMPTE timecode range of
* 0:0:0:0 to 23:59:59:##. When this strategy function is invoked,
* the frames, seconds and minutes fields have already been processed
* under the assumption the overall value stays in range. After returning
* from this strategy function, the rawHours value will be returned to be
* stored into the hours field without any further adjustments.
* @note currently the range is extended "naturally" (i.e. mathematically).
* The representation is flipped around the zero point and the value
* of the hours is just allowed to increase beyond 23
* @todo If necessary, this extension point should be converted into a
* configurable strategy. Possible variations
* - clip values beyond the boundaries
* - wrap around from 23:59:59:## to 0:0:0:0
* - just make the hour negative, but continue with the same
* orientation (0:0:0:0 - 1sec = -1:59:59:0)
*/
void
Smpte::rangeLimitStrategy (SmpteTC& tc, int& rawHours)
{
if ((rawHours^tc.sgn) >= 0) return;
tc.sgn = rawHours; // transfer sign into the sign field
rawHours = abs(rawHours);
REQUIRE (0 <= tc.frames && uint(tc.frames) < tc.getFps());
REQUIRE (0 <= tc.secs && tc.secs < 60 );
REQUIRE (0 <= tc.mins && tc.mins < 60 );
// sign flip was detected:
// switch orientation of all timecode fields
// i.e. -h + (m+s+f) becomes - (h+m+s+f)
uint fr (tc.getFps() - tc.frames);
uint secs (60 - tc.secs);
uint mins (60 - tc.mins);
ASSERT (fr <= tc.getFps());
ASSERT (0 < secs);
if (fr < tc.getFps())
--secs;
else
fr = 0;
ASSERT (secs <= 60);
ASSERT (0 < mins);
if (secs < 60)
--mins;
else
secs = 0;
ASSERT (mins <= 60);
ASSERT (0 < rawHours);
if (mins < 60)
--rawHours;
else
mins = 0;
tc.frames.setValueRaw (fr);
tc.secs.setValueRaw (secs);
tc.mins.setValueRaw (mins);
}
}
namespace { // Timecode implementation details
typedef util::IDiv<int> Div;
@ -118,7 +184,7 @@ namespace time {
int
wrapFrames (SmpteTC* thisTC, int rawFrames)
{
Div scaleRelation(rawFrames, thisTC->getFps());
Div scaleRelation = floorwrap<int> (rawFrames, thisTC->getFps());
thisTC->secs += scaleRelation.quot;
return scaleRelation.rem;
}
@ -126,7 +192,7 @@ namespace time {
int
wrapSeconds (SmpteTC* thisTC, int rawSecs)
{
Div scaleRelation(rawSecs, 60);
Div scaleRelation = floorwrap (rawSecs, 60);
thisTC->mins += scaleRelation.quot;
return scaleRelation.rem;
}
@ -134,7 +200,7 @@ namespace time {
int
wrapMinutes (SmpteTC* thisTC, int rawMins)
{
Div scaleRelation(rawMins, 60);
Div scaleRelation = floorwrap (rawMins, 60);
thisTC->hours += scaleRelation.quot;
return scaleRelation.rem;
}
@ -142,7 +208,7 @@ namespace time {
int
wrapHours (SmpteTC* thisTC, int rawHours)
{
thisTC->sgn = rawHours;
format::Smpte::rangeLimitStrategy (*thisTC, rawHours);
return rawHours;
}
@ -229,10 +295,21 @@ namespace time {
void
SmpteTC::rebuild() const
SmpteTC::clear()
{
frames.setValueRaw(0);
secs.setValueRaw (0);
mins.setValueRaw (0);
hours.setValueRaw (0);
sgn.setValueRaw (+1);
}
void
SmpteTC::rebuild()
{
TimeValue point = Format::evaluate (*this, *quantiser_);
Format::rebuild (unConst(*this), *quantiser_, point);
Format::rebuild (*this, *quantiser_, point);
}

View file

@ -120,9 +120,11 @@ namespace time {
SmpteTC (SmpteTC const&);
SmpteTC& operator= (SmpteTC const&);
void rebuild() const;
uint getFps() const;
void clear();
void rebuild();
HourDigit hours;
SexaDigit mins;
SexaDigit secs;

View file

@ -674,7 +674,8 @@ return: 0
END
PLANNED "Time formats and timecodes" TimeFormats_test <<END
TEST "Time formats and timecodes" TimeFormats_test <<END
out-lit: SMPTE=" 5:42:23:13" time = 5:42:23.520
return: 0
END

View file

@ -85,9 +85,9 @@ namespace test{
CHECK (1 == sig);
sig = -sig;
CHECK (-1 == sig);
sig = -98;
CHECK (-1 == sig);
CHECK (sig.show() == string("-"));
sig += 98;
CHECK (+1 == sig);
CHECK (sig.show() == string(" "));
sig *= -1;
CHECK (sig.show() == string("-"));
}

View file

@ -312,7 +312,7 @@ namespace test{
* we'll take some timings.
* @warning the results of such tests could be unreliable,
* but in this case here I saw a significant difference,
* with values of about 0.5sec / 0.7sec */
* with values of about 0.1sec / 0.7sec */
void
verifyDisplayCaching ()
{

View file

@ -114,7 +114,7 @@ namespace test{
showTimeCode(smpte);
CHECK (" 5:42:23:13" == string(smpte));
CHECK (raw - Time(35,0) == smpte.getTime());
CHECK (raw - Time(35,0) == smpte.getTime()); // quantisation to next lower frame
CHECK (13 == smpte.frames);
CHECK (23 == smpte.secs);
CHECK (42 == smpte.mins);
@ -131,20 +131,22 @@ namespace test{
CHECK (smpte.mins-- == 40);
CHECK (--smpte.mins == 38);
CHECK (" 5:38:00:01" == string(smpte));
// extension beyond origin to negative values
Time tx = smpte.getTime();
smpte.hours -= 6;
CHECK ("- 0:21:59:24"== string(smpte));
CHECK (tx - Time(6*60*60) == smpte.getTime());
CHECK ("- 0:21:59:24"== string(smpte)); // representation is symmetrical to origin
CHECK (tx - Time(6*60*60) == smpte.getTime()); // Continuous time axis
CHECK (-1 == smpte.sgn);
smpte.sgn += 123;
smpte.sgn += 123; // just flip the sign
CHECK (" 0:21:59:24"== string(smpte));
smpte.secs.setValueRaw(61);
smpte.secs.setValueRaw(61); // set "wrong" value, bypassing normalisation
CHECK (smpte.secs == 61);
CHECK (smpte.getTime() == Time(1000*24/25, 01, 22));
CHECK (smpte.secs == 61);
CHECK (smpte.secs == 61); // calculated value is correct, but doesn't change state
CHECK (" 0:21:61:24"== string(smpte));
smpte.rebuild();
smpte.rebuild(); // but rebuilding the value includes normalisation
CHECK (smpte.secs == 1);
CHECK (smpte.mins == 22);
CHECK (" 0:22:01:24"== string(smpte));

View file

@ -6746,7 +6746,7 @@ Question is: how fine grained and configurable needs this to be?
</pre>
</div>
<div title="TimecodeFormat" modifier="Ichthyostega" modified="201101190857" created="201101100308" tags="Player spec discuss draft" changecount="18">
<div title="TimecodeFormat" modifier="Ichthyostega" modified="201101211920" created="201101100308" tags="Player spec discuss draft" changecount="24">
<pre>The handling of [[Timecode]] is closely related to [[time representation and quantisation|TimeQuant]]. In fact, these two topics blend into one another. Time will be quantised into a //grid,// but this grid only makes sense when linked to a externally relevant meaning and representation, which is the Timecode. But a timecode value also denotes a specific point in time -- performing operations on a timecode is equivalent to manipulating a quantised time value.
!Problem of dependencies
@ -6773,7 +6773,24 @@ And last but not least, it is possible to get a new ~TimeValue, reflecting the c
* @@color:green;✔@@ determine the primitives which need to be on the //effective quantiser API.//
* @@color:green;✔@@ find out how a format can address the individual components it's comprised of &amp;rarr; direct access by concrete type
* @@color:green;✔@@ decide how a concrete TC value can refer to his quantiser &amp;rarr; always using smart-ptr
* @@color:red;??@@ maybe coin a //value handle// -- to tie the three required parts together
* @@color:green;✔@@ decide on how to handle wrap-around and negative values &amp;rarr; by strategy
!negative values and wrap-around
Many timecode formats were defined in the era of analogue media -- typically the key point of a timecode format was the way it can be encoded into some nits and bits within the available channel bandwidth. Often this causes the use of fixed field length representations, imposing hard limits on the number range. Adhering strictly to such ancient specifications in the context of a general purpose computer program usually results in lots of additional complexity without a fundamental reason.
When in doubt, for the design of Lumiera we tend to err for the basic mathematical definition. For time values this means to use an practically unlimited time axis with an arbitrary time origin. Under this assumptions, negative time values are just natural and will be handled without case distinctions. This way, at least the core is kept simple and straight forward. Which leaves us with some of the aforementioned additional complexities showing up when it comes to interfacing with an existing timecode format. Especially, the following points need to be clarified:
* wrap-around: when a timecode component (e.g. the seconds) exceeds the defined range, this creates a propagation (e.g. to the minutes) and a remainder (wrapped seconds value).
* range limitation: what happens when an internal time value exceeds the range of possible timecode values? (e.g. what is 23:59:59 + 70 frames?)
* how to extend the definition to negative values, if applicable.
Especially ''SMPTE Timecode'' is limited to the range from 0:0:0:0 to 23:59:59:##.
But because for Lumiera the SMPTE format is just a presentation rule applied to a more orthogonal internal repesentation, we could think of extending the allowed values...
# by just letting the hours increase arbitrarily
# by letting the timecode wrap from 23:59:59:## to 0:0:0:0 and treat this junction as continuous (effectively a &quot;modulo 1 day&quot;).
# by making the hours-field signed, but otherwise contain the same wrapping scheme (0:0:0:0 - 1sec = -1:59:59:0)
# by a representation symmetrical to the zero point (0:0:0:0 - 1sec = -0:0:1:0) -- but beware: //the underlying frame quantisation won't flip//
Because this decision is considered arbitrary and any reasoning will be context dependant, the decision is to provide a hook for a //strategy// --
Currently (1/11), the strategy is implemented according to (1) and (4) above, leaving the actual configuration of a strategy for later.
</pre>
</div>
<div title="Timeline" modifier="Ichthyostega" modified="201011220126" created="200706250721" tags="def" changecount="21">