LUMIERA.clone/tests/basics/time/time-formats-test.cpp
Ichthyostega 155bf95ce5 Doxygen: magically insert a reference to the test class
this bit of Sed magic relies on the fact that we happen to write
the almost correct class name of a test into the header comment.

HOWTO:
for F in $(find tests -type f \( -name '*.cpp' \)  -exec egrep -q '§§TODO§§' {} \; -print);
  do sed -r -i -e'
    2          {h;x;s/\s+(.+)\(Test\).*$/\\ref \1_test/;x};
    /§§TODO§§/ {s/§§TODO§§//;G;s/\n//}'
    $F;
done
2017-02-22 03:17:18 +01:00

303 lines
11 KiB
C++

/*
TimeFormats(Test) - timecode handling and formatting
Copyright (C) Lumiera.org
2010, Hermann Vosseler <Ichthyostega@web.de>
This program 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.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
* *****************************************************/
/** @file time-formats-test.cpp
** unit test \ref TimeFormats_test
*/
#include "lib/test/run.hpp"
//#include "lib/test/test-helper.hpp"
#include "proc/asset/meta/time-grid.hpp"
#include "lib/time/timequant.hpp"
#include "lib/time/timecode.hpp"
#include "lib/time/mutation.hpp"
#include "lib/format-cout.hpp"
#include "lib/util.hpp"
#include <boost/lexical_cast.hpp>
#include <string>
#include <cstdlib>
using boost::lexical_cast;
using util::isnil;
using std::rand;
using std::string;
namespace lib {
namespace time{
namespace test{
using proc::asset::meta::TimeGrid;
using format::Frames;
using format::Smpte;
namespace{
const FrameCnt MAX_FRAME = 265*24*60*60*25;
string
generateRandomFrameNr()
{
FrameCnt frameNr(0);
while (!frameNr)
frameNr = rand() % (2*MAX_FRAME) - MAX_FRAME;
return lexical_cast<string>(frameNr)+"#";
}
}
/****************************************************//**
* @test verify handling of grid aligned timecode values.
* - full cycle from parsing to formatting
* - mutating the components of timecode
* - some formatting corner cases
* - formatting in various formats
*/
class TimeFormats_test : public Test
{
virtual void
run (Arg)
{
TimeGrid::build("pal0", FrameRate::PAL);
checkTimecodeUsageCycle ();
// checkFrames ();
// checkSeconds ();
// checkHms ();
checkSmpte();
// checkDropFrame();
// checkCopyAssignments();
}
/** @test demonstrate a full usage cycle of timecode and time values.
* Starting with a textual representation according to a specific timecode format,
* and based on the knowledge of the implicit underlying time grid (coordinate system,
* here with origin=0 and framerate=25fps), this timecode string may be parsed.
* This brings us (back) to the very origin, which is a raw TimeValue (internal time).
* Now, this time value might be manipulated, compared to other values etc.
* Anyway, at some point these time values need to be related to some time scale again,
* leading to quantised time values, which — finally — can be cast into a timecode format
* for external representation again, thus closing the circle.
*/
void
checkTimecodeUsageCycle ()
{
string quellCode = generateRandomFrameNr();
PQuant refScale = Quantiser::retrieve("pal0");
// get internal (raw) time value
TimeValue t1 = format::Frames::parse(quellCode, *refScale);
ENSURE (0 != t1);
// manipulating
TimeVar v1(t1);
v1 += Time(FSecs(6,5));
CHECK (t1 < v1);
// quantising into an external grid
QuTime q1 (t1, "pal0");
CHECK (q1 == t1);
// further mutations (here nudge by +5 grid steps)
QuTime q2 = q1;
q2.accept (Mutation::nudge(+5));
CHECK (q1 < q2);
// converting (back) into a timecode format
FrameNr frames1(q1);
FrameNr frames2(q2);
CHECK (5 == frames2 - frames1);
q2.accept (Mutation::changeTime(v1));
CHECK (30 == q2.formatAs<Frames>() - frames1);
CHECK (quellCode == string(frames1));
CHECK (quellCode != string(frames2));
showTimeCode (frames1);
showTimeCode (frames2);
showTimeCode (q2.formatAs<Smpte>());
}
template<class TC>
void
showTimeCode (TC timecode)
{
cout << timecode.describe()<<"=\""<<timecode<<"\" time = "<< timecode.getTime() << endl;
}
void
checkFrames ()
{
UNIMPLEMENTED ("verify frame count time format");
}
void
checkSeconds ()
{
UNIMPLEMENTED ("verify fractional seconds as timecode format");
}
void
checkHms ()
{
UNIMPLEMENTED ("verify hour-minutes-seconds-millis timecode");
}
/** @test detailed coverage of SMPTE timecode representation.
* Using a scale grid with PAL framerate; this test doesn't
* cover the handling of drop-frame timecode.
* - creating a timecode representation causes frame quantisation
* - the components of SMPTE timecode can be accessed and manipulated
* - timecode can be incremented/decremented as a whole
* - we allow extension of the scale towards negative values
* - for these, the representation is flipped and the negative
* orientation only indicated through the sign field.
*/
void
checkSmpte ()
{
Time raw(555,23,42,5);
QuTime t1 (raw, "pal0");
SmpteTC smpte(t1);
cout << "----SMPTE-----" << endl;
showTimeCode(smpte);
CHECK (" 5:42:23:13" == string(smpte));
CHECK (raw - Time(35,0) == smpte.getTime()); // timecode value got quantised towards next lower frame
CHECK (13 == smpte.frames);
CHECK (23 == smpte.secs);
CHECK (42 == smpte.mins);
CHECK ( 5 == smpte.hours);
CHECK ( 1 == smpte.sgn);
CHECK ("SMPTE" == smpte.describe());
++smpte;
CHECK (" 5:42:23:14" == string(smpte));
smpte.frames += 12;
CHECK (" 5:42:24:01" == string(smpte));
smpte.secs = -120;
CHECK (" 5:40:00:01" == string(smpte));
CHECK (smpte.mins-- == 40);
CHECK (--smpte.mins == 38);
CHECK (" 5:38:00:01" == string(smpte));
TimeVar tx = smpte.getTime();
CHECK (tx == Time(0,0,38,5) + Time(FSecs(1,25)));
// Extended SMPTE: extension of the axis beyond origin towards negative values
smpte.hours -= 6;
CHECK ("- 0:21:59:24"== string(smpte)); // representation is symmetrical to origin
CHECK (tx - Time(0,0,0,6) == smpte.getTime()); // Continuous time axis
CHECK (-1 == smpte.sgn); // Note: for these negative (extended) SMPTE...
CHECK (smpte.mins > 0); // ...the representation is really flipped around zero
CHECK (smpte.secs > 0);
CHECK (smpte.frames > 0);
tx = smpte.getTime();
++smpte.frames; // now *increasing* the frame value
CHECK ("- 0:22:00:00"== string(smpte)); // means decreasing the resulting time
CHECK (smpte.getTime() == tx - Time(1000/25,0,0,0));
++smpte; // but the orientation of the increment on the *whole* TC values is unaltered
CHECK ("- 0:21:59:24"== string(smpte)); // so this actually *advanced* time by one frame
CHECK (tx == smpte.getTime());
CHECK (tx < TimeValue(0));
smpte.mins -= 2*60; // now lets flip the representation again...
CHECK (" 1:38:00:01"== string(smpte));
CHECK (+1 == smpte.sgn);
CHECK (smpte.getTime() > 0);
CHECK (tx + Time(0,0,0,2) == smpte.getTime());
smpte.secs -= 2*60*60; // and again...
CHECK (tx == smpte.getTime());
CHECK ("- 0:21:59:24"== string(smpte));
smpte.sgn += 123; // just flip the sign
CHECK (" 0:21:59:24"== string(smpte));
CHECK (tx == -smpte.getTime());
CHECK (+1 == smpte.sgn); // sign value is limited to +1 / -1
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); // calculated value is correct, but doesn't change state
CHECK (" 0:21:61:24"== string(smpte));
smpte.rebuild(); // but rebuilding the value includes normalisation
CHECK (smpte.secs == 1);
CHECK (smpte.mins == 22);
CHECK (" 0:22:01:24"== string(smpte));
smpte.frames.setValueRaw (25);
CHECK (" 0:22:01:25"== string(smpte));
smpte.hours = -1; // flipped representation handles denormalised values properly
CHECK ("- 0:37:58:00"== string(smpte));
smpte.mins.setValueRaw (59);
smpte.secs.setValueRaw (61);
smpte.frames.setValueRaw(-26); // provoke multiple over/underflows...
smpte.hours.setValueRaw (-2);
CHECK ("--2:59:61:-26"==string(smpte));
tx = smpte.getTime();
CHECK (tx == -1*(Time(0,61,59) - Time(0,0,0,2) - Time(FSecs(26,25))));
smpte.invertOrientation();
CHECK (" 1:00:00:01"== string(smpte));
CHECK (tx == smpte.getTime()); // applying invertOrientation() doesn't change the value
smpte.frames.setValueRaw(-1);
tx -= Time(FSecs(2,25));
CHECK (tx == smpte.getTime());
CHECK (" 1:00:00:-1"== string(smpte));
smpte.invertOrientation(); // invoking on positive should create double negated representation
CHECK ("--1:00:00:01"== string(smpte)); // and here especially also causes a series of overflows
CHECK (tx == smpte.getTime()); // but without affecting the overall value
}
void
checkDropFrame ()
{
UNIMPLEMENTED ("verify especially SMPTE-drop-frame timecode");
}
void
checkCopyAssignments ()
{
UNIMPLEMENTED ("verify Timecode values can be copied and assigned properly");
}
};
/** Register this test class... */
LAUNCHER (TimeFormats_test, "unit common");
}}} // namespace lib::time::test