mirror of
https://github.com/Ardour/ardour.git
synced 2025-12-07 07:14:56 +01:00
This fixes the build for me with clang 11.1.0 (from LLVM repositories) on Debian buster as C++11. As far as I know this is the general best practice because of ADL voodoo, but Ardour's approach to namespaces is pretty anarchic, and I'm not really sure exactly why this wasn't building for some types. Also tested to build find with the default buster GCC 8.3.0.
854 lines
21 KiB
C++
854 lines
21 KiB
C++
/*
|
|
* Copyright (C) 2017 Paul Davis <paul@linuxaudiosystems.com>
|
|
*
|
|
* 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.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*/
|
|
|
|
#define Timecode_IS_AROUND_ZERO(sm) (!(sm).frames && !(sm).seconds && !(sm).minutes && !(sm).hours)
|
|
#define Timecode_IS_ZERO(sm) (!(sm).frames && !(sm).seconds && !(sm).minutes && !(sm).hours && !(sm.subframes))
|
|
|
|
#include <math.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
|
|
#include "temporal/time.h"
|
|
|
|
namespace Timecode {
|
|
|
|
double Time::default_rate = 30.0;
|
|
|
|
|
|
/** Increment @a timecode by exactly one sample (keep subframes value).
|
|
* Realtime safe.
|
|
* @return true if seconds wrap.
|
|
*/
|
|
Wrap
|
|
increment (Time& timecode, uint32_t subframes_per_frame)
|
|
{
|
|
Wrap wrap = NONE;
|
|
|
|
if (timecode.negative) {
|
|
if (Timecode_IS_AROUND_ZERO (timecode) && timecode.subframes) {
|
|
// We have a zero transition involving only subframes
|
|
timecode.subframes = subframes_per_frame - timecode.subframes;
|
|
timecode.negative = false;
|
|
return SECONDS;
|
|
}
|
|
|
|
timecode.negative = false;
|
|
wrap = decrement (timecode, subframes_per_frame);
|
|
if (!Timecode_IS_ZERO (timecode)) {
|
|
timecode.negative = true;
|
|
}
|
|
return wrap;
|
|
}
|
|
|
|
switch ((int)ceil (timecode.rate)) {
|
|
case 24:
|
|
if (timecode.frames == 23) {
|
|
timecode.frames = 0;
|
|
wrap = SECONDS;
|
|
}
|
|
break;
|
|
case 25:
|
|
if (timecode.frames == 24) {
|
|
timecode.frames = 0;
|
|
wrap = SECONDS;
|
|
}
|
|
break;
|
|
case 30:
|
|
if (timecode.drop) {
|
|
if (timecode.frames == 29) {
|
|
if (((timecode.minutes + 1) % 10) && (timecode.seconds == 59)) {
|
|
timecode.frames = 2;
|
|
}
|
|
else {
|
|
timecode.frames = 0;
|
|
}
|
|
wrap = SECONDS;
|
|
}
|
|
} else {
|
|
|
|
if (timecode.frames == 29) {
|
|
timecode.frames = 0;
|
|
wrap = SECONDS;
|
|
}
|
|
}
|
|
break;
|
|
case 60:
|
|
if (timecode.frames == 59) {
|
|
timecode.frames = 0;
|
|
wrap = SECONDS;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (wrap == SECONDS) {
|
|
if (timecode.seconds == 59) {
|
|
timecode.seconds = 0;
|
|
wrap = MINUTES;
|
|
if (timecode.minutes == 59) {
|
|
timecode.minutes = 0;
|
|
wrap = HOURS;
|
|
timecode.hours++;
|
|
} else {
|
|
timecode.minutes++;
|
|
}
|
|
} else {
|
|
timecode.seconds++;
|
|
}
|
|
} else {
|
|
timecode.frames++;
|
|
}
|
|
|
|
return wrap;
|
|
}
|
|
|
|
|
|
/** Decrement @a timecode by exactly one sample (keep subframes value)
|
|
* Realtime safe.
|
|
* @return true if seconds wrap. */
|
|
Wrap
|
|
decrement (Time& timecode, uint32_t subframes_per_frame)
|
|
{
|
|
Wrap wrap = NONE;
|
|
|
|
if (timecode.negative || Timecode_IS_ZERO (timecode)) {
|
|
timecode.negative = false;
|
|
wrap = increment (timecode, subframes_per_frame);
|
|
timecode.negative = true;
|
|
return wrap;
|
|
} else if (Timecode_IS_AROUND_ZERO (timecode) && timecode.subframes) {
|
|
// We have a zero transition involving only subframes
|
|
timecode.subframes = subframes_per_frame - timecode.subframes;
|
|
timecode.negative = true;
|
|
return SECONDS;
|
|
}
|
|
|
|
switch ((int)ceil (timecode.rate)) {
|
|
case 24:
|
|
if (timecode.frames == 0) {
|
|
timecode.frames = 23;
|
|
wrap = SECONDS;
|
|
}
|
|
break;
|
|
case 25:
|
|
if (timecode.frames == 0) {
|
|
timecode.frames = 24;
|
|
wrap = SECONDS;
|
|
}
|
|
break;
|
|
case 30:
|
|
if (timecode.drop) {
|
|
if ((timecode.minutes % 10) && (timecode.seconds == 0)) {
|
|
if (timecode.frames <= 2) {
|
|
timecode.frames = 29;
|
|
wrap = SECONDS;
|
|
}
|
|
} else if (timecode.frames == 0) {
|
|
timecode.frames = 29;
|
|
wrap = SECONDS;
|
|
}
|
|
|
|
} else {
|
|
if (timecode.frames == 0) {
|
|
timecode.frames = 29;
|
|
wrap = SECONDS;
|
|
}
|
|
}
|
|
break;
|
|
case 60:
|
|
if (timecode.frames == 0) {
|
|
timecode.frames = 59;
|
|
wrap = SECONDS;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (wrap == SECONDS) {
|
|
if (timecode.seconds == 0) {
|
|
timecode.seconds = 59;
|
|
wrap = MINUTES;
|
|
if (timecode.minutes == 0) {
|
|
timecode.minutes = 59;
|
|
wrap = HOURS;
|
|
timecode.hours--;
|
|
}
|
|
else {
|
|
timecode.minutes--;
|
|
}
|
|
} else {
|
|
timecode.seconds--;
|
|
}
|
|
} else {
|
|
timecode.frames--;
|
|
}
|
|
|
|
if (Timecode_IS_ZERO (timecode)) {
|
|
timecode.negative = false;
|
|
}
|
|
|
|
return wrap;
|
|
}
|
|
|
|
|
|
/** Go to lowest absolute subframe value in this sample (set to 0 :-)) */
|
|
void
|
|
frames_floot (Time& timecode)
|
|
{
|
|
timecode.subframes = 0;
|
|
if (Timecode_IS_ZERO (timecode)) {
|
|
timecode.negative = false;
|
|
}
|
|
}
|
|
|
|
|
|
/** Increment @a timecode by one subframe */
|
|
Wrap
|
|
increment_subframes (Time& timecode, uint32_t subframes_per_frame)
|
|
{
|
|
Wrap wrap = NONE;
|
|
|
|
if (timecode.negative) {
|
|
timecode.negative = false;
|
|
wrap = decrement_subframes (timecode, subframes_per_frame);
|
|
if (!Timecode_IS_ZERO (timecode)) {
|
|
timecode.negative = true;
|
|
}
|
|
return wrap;
|
|
}
|
|
|
|
timecode.subframes++;
|
|
if (timecode.subframes >= subframes_per_frame) {
|
|
timecode.subframes = 0;
|
|
increment (timecode, subframes_per_frame);
|
|
return FRAMES;
|
|
}
|
|
return NONE;
|
|
}
|
|
|
|
|
|
/** Decrement @a timecode by one subframe */
|
|
Wrap
|
|
decrement_subframes (Time& timecode, uint32_t subframes_per_frame)
|
|
{
|
|
Wrap wrap = NONE;
|
|
|
|
if (timecode.negative) {
|
|
timecode.negative = false;
|
|
wrap = increment_subframes (timecode, subframes_per_frame);
|
|
timecode.negative = true;
|
|
return wrap;
|
|
}
|
|
|
|
if (timecode.subframes <= 0) {
|
|
timecode.subframes = 0;
|
|
if (Timecode_IS_ZERO (timecode)) {
|
|
timecode.negative = true;
|
|
timecode.subframes = 1;
|
|
return FRAMES;
|
|
} else {
|
|
decrement (timecode, subframes_per_frame);
|
|
timecode.subframes = 79;
|
|
return FRAMES;
|
|
}
|
|
} else {
|
|
timecode.subframes--;
|
|
if (Timecode_IS_ZERO (timecode)) {
|
|
timecode.negative = false;
|
|
}
|
|
return NONE;
|
|
}
|
|
}
|
|
|
|
|
|
/** Go to next whole second (frames == 0 or frames == 2) */
|
|
Wrap
|
|
increment_seconds (Time& timecode, uint32_t subframes_per_frame)
|
|
{
|
|
Wrap wrap = NONE;
|
|
|
|
// Clear subframes
|
|
frames_floot (timecode);
|
|
|
|
if (timecode.negative) {
|
|
// Wrap second if on second boundary
|
|
wrap = increment (timecode, subframes_per_frame);
|
|
// Go to lowest absolute frame value
|
|
seconds_floor (timecode);
|
|
if (Timecode_IS_ZERO (timecode)) {
|
|
timecode.negative = false;
|
|
}
|
|
} else {
|
|
// Go to highest possible frame in this second
|
|
switch ((int)ceil (timecode.rate)) {
|
|
case 24:
|
|
timecode.frames = 23;
|
|
break;
|
|
case 25:
|
|
timecode.frames = 24;
|
|
break;
|
|
case 30:
|
|
timecode.frames = 29;
|
|
break;
|
|
case 60:
|
|
timecode.frames = 59;
|
|
break;
|
|
}
|
|
|
|
// Increment by one frame
|
|
wrap = increment (timecode, subframes_per_frame);
|
|
}
|
|
|
|
return wrap;
|
|
}
|
|
|
|
|
|
/** Go to lowest (absolute) frame value in this second
|
|
* Doesn't care about positive/negative */
|
|
void
|
|
seconds_floor (Time& timecode)
|
|
{
|
|
// Clear subframes
|
|
frames_floot (timecode);
|
|
|
|
// Go to lowest possible frame in this second
|
|
switch ((int)ceil (timecode.rate)) {
|
|
case 24:
|
|
case 25:
|
|
case 30:
|
|
case 60:
|
|
if (!(timecode.drop)) {
|
|
timecode.frames = 0;
|
|
} else {
|
|
if ((timecode.minutes % 10) && (timecode.seconds == 0)) {
|
|
timecode.frames = 2;
|
|
} else {
|
|
timecode.frames = 0;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (Timecode_IS_ZERO (timecode)) {
|
|
timecode.negative = false;
|
|
}
|
|
}
|
|
|
|
|
|
/** Go to next whole minute (seconds == 0, frames == 0 or frames == 2) */
|
|
Wrap
|
|
increment_minutes (Time& timecode, uint32_t subframes_per_frame)
|
|
{
|
|
Wrap wrap = NONE;
|
|
|
|
// Clear subframes
|
|
frames_floot (timecode);
|
|
|
|
if (timecode.negative) {
|
|
// Wrap if on minute boundary
|
|
wrap = increment_seconds (timecode, subframes_per_frame);
|
|
// Go to lowest possible value in this minute
|
|
minutes_floor (timecode);
|
|
} else {
|
|
// Go to highest possible second
|
|
timecode.seconds = 59;
|
|
// Wrap minute by incrementing second
|
|
wrap = increment_seconds (timecode, subframes_per_frame);
|
|
}
|
|
|
|
return wrap;
|
|
}
|
|
|
|
|
|
/** Go to lowest absolute value in this minute */
|
|
void
|
|
minutes_floor (Time& timecode)
|
|
{
|
|
// Go to lowest possible second
|
|
timecode.seconds = 0;
|
|
// Go to lowest possible frame
|
|
seconds_floor (timecode);
|
|
|
|
if (Timecode_IS_ZERO (timecode)) {
|
|
timecode.negative = false;
|
|
}
|
|
}
|
|
|
|
|
|
/** Go to next whole hour (minute = 0, second = 0, frame = 0) */
|
|
Wrap
|
|
increment_hours (Time& timecode, uint32_t subframes_per_frame)
|
|
{
|
|
Wrap wrap = NONE;
|
|
|
|
// Clear subframes
|
|
frames_floot (timecode);
|
|
|
|
if (timecode.negative) {
|
|
// Wrap if on hour boundary
|
|
wrap = increment_minutes (timecode, subframes_per_frame);
|
|
// Go to lowest possible value in this hour
|
|
hours_floor(timecode);
|
|
} else {
|
|
timecode.minutes = 59;
|
|
wrap = increment_minutes (timecode, subframes_per_frame);
|
|
}
|
|
|
|
return wrap;
|
|
}
|
|
|
|
|
|
/** Go to lowest absolute value in this hour */
|
|
void
|
|
hours_floor(Time& timecode)
|
|
{
|
|
timecode.minutes = 0;
|
|
timecode.seconds = 0;
|
|
timecode.frames = 0;
|
|
timecode.subframes = 0;
|
|
|
|
if (Timecode_IS_ZERO (timecode)) {
|
|
timecode.negative = false;
|
|
}
|
|
}
|
|
|
|
double
|
|
timecode_to_frames_per_second(TimecodeFormat t)
|
|
{
|
|
switch (t) {
|
|
case timecode_23976:
|
|
return (24000.0/1001.0); //23.976;
|
|
|
|
break;
|
|
case timecode_24:
|
|
return 24;
|
|
|
|
break;
|
|
case timecode_24976:
|
|
return (25000.0/1001.0); //24.976;
|
|
|
|
break;
|
|
case timecode_25:
|
|
return 25;
|
|
|
|
break;
|
|
case timecode_2997:
|
|
return (30000.0/1001.0); //29.97;
|
|
|
|
break;
|
|
case timecode_2997drop:
|
|
return (30000.0/1001.0); //29.97;
|
|
|
|
break;
|
|
case timecode_2997000:
|
|
return 29.97;
|
|
|
|
break;
|
|
case timecode_2997000drop:
|
|
return 29.97;
|
|
|
|
break;
|
|
case timecode_30:
|
|
return 30;
|
|
|
|
break;
|
|
case timecode_30drop:
|
|
return 30;
|
|
|
|
break;
|
|
case timecode_5994:
|
|
return (60000.0/1001.0); //59.94;
|
|
|
|
break;
|
|
case timecode_60:
|
|
return 60;
|
|
|
|
break;
|
|
default:
|
|
//std::cerr << "Editor received unexpected timecode type" << std::endl;
|
|
break;
|
|
}
|
|
return 30.0;
|
|
}
|
|
|
|
bool
|
|
timecode_has_drop_frames(TimecodeFormat t)
|
|
{
|
|
switch (t) {
|
|
case timecode_23976:
|
|
return false;
|
|
|
|
break;
|
|
case timecode_24:
|
|
return false;
|
|
|
|
break;
|
|
case timecode_24976:
|
|
return false;
|
|
|
|
break;
|
|
case timecode_25:
|
|
return false;
|
|
|
|
break;
|
|
case timecode_2997:
|
|
return false;
|
|
|
|
break;
|
|
case timecode_2997drop:
|
|
return true;
|
|
|
|
break;
|
|
case timecode_2997000:
|
|
return false;
|
|
|
|
break;
|
|
case timecode_2997000drop:
|
|
return true;
|
|
|
|
break;
|
|
case timecode_30:
|
|
return false;
|
|
|
|
break;
|
|
case timecode_30drop:
|
|
return true;
|
|
|
|
break;
|
|
case timecode_5994:
|
|
return false;
|
|
|
|
break;
|
|
case timecode_60:
|
|
return false;
|
|
|
|
break;
|
|
default:
|
|
//error << "Editor received unexpected timecode type" << endmsg;
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
std::string
|
|
timecode_format_name (TimecodeFormat const t)
|
|
{
|
|
switch (t) {
|
|
case timecode_23976:
|
|
return "23.98";
|
|
|
|
break;
|
|
case timecode_24:
|
|
return "24";
|
|
|
|
break;
|
|
case timecode_24976:
|
|
return "24.98";
|
|
|
|
break;
|
|
case timecode_25:
|
|
return "25";
|
|
|
|
break;
|
|
case timecode_2997000:
|
|
case timecode_2997:
|
|
return "29.97";
|
|
|
|
break;
|
|
case timecode_2997000drop:
|
|
case timecode_2997drop:
|
|
return "29.97 drop";
|
|
|
|
break;
|
|
case timecode_30:
|
|
return "30";
|
|
|
|
break;
|
|
case timecode_30drop:
|
|
return "30 drop";
|
|
|
|
break;
|
|
case timecode_5994:
|
|
return "59.94";
|
|
|
|
break;
|
|
case timecode_60:
|
|
return "60";
|
|
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return "??";
|
|
}
|
|
|
|
std::string timecode_format_time (Timecode::Time TC)
|
|
{
|
|
char buf[32];
|
|
if (TC.negative) {
|
|
snprintf (buf, sizeof (buf), "-%02" PRIu32 ":%02" PRIu32 ":%02" PRIu32 "%c%02" PRIu32,
|
|
TC.hours, TC.minutes, TC.seconds, TC.drop ? ';' : ':', TC.frames);
|
|
} else {
|
|
snprintf (buf, sizeof (buf), " %02" PRIu32 ":%02" PRIu32 ":%02" PRIu32 "%c%02" PRIu32,
|
|
TC.hours, TC.minutes, TC.seconds, TC.drop ? ';' : ':', TC.frames);
|
|
}
|
|
return std::string(buf);
|
|
}
|
|
|
|
std::string timecode_format_sampletime (
|
|
int64_t sample,
|
|
double sample_sample_rate,
|
|
double timecode_frames_per_second, bool timecode_drop_frames)
|
|
{
|
|
Time t;
|
|
sample_to_timecode(
|
|
sample, t, false, false,
|
|
timecode_frames_per_second, timecode_drop_frames,
|
|
sample_sample_rate,
|
|
80, false, 0);
|
|
return timecode_format_time(t);
|
|
}
|
|
|
|
bool parse_timecode_format(std::string tc, Timecode::Time &TC) {
|
|
char negative[2];
|
|
char ignored[2];
|
|
TC.subframes = 0;
|
|
if (sscanf (tc.c_str(), "%[- ]%" PRId32 ":%" PRId32 ":%" PRId32 "%[:;]%" PRId32,
|
|
negative, &TC.hours, &TC.minutes, &TC.seconds, ignored, &TC.frames) != 6) {
|
|
TC.hours = TC.minutes = TC.seconds = TC.frames = 0;
|
|
TC.negative = false;
|
|
return false;
|
|
}
|
|
if (negative[0]=='-') {
|
|
TC.negative = true;
|
|
} else {
|
|
TC.negative = false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void
|
|
timecode_to_sample(
|
|
Timecode::Time const& timecode, int64_t& sample,
|
|
bool use_offset, bool use_subframes,
|
|
/* Note - framerate info is taken from Timecode::Time& */
|
|
double sample_sample_rate /**< may include pull up/down */,
|
|
uint32_t subframes_per_frame,
|
|
/* optional offset - can be improved: function pointer to lazily query this*/
|
|
bool offset_is_negative, int64_t offset_samples
|
|
)
|
|
{
|
|
const double samples_per_timecode_frame = (double) sample_sample_rate / (double) timecode.rate;
|
|
|
|
if (timecode.drop) {
|
|
// The drop frame format was created to better approximate the 30000/1001 = 29.97002997002997....
|
|
// framerate of NTSC color TV. The used frame rate of drop fra,e is 29.97, which drifts by about
|
|
// 0.108 frame per hour, or about 1.3 frames per 12 hours. This is not perfect, but a lot better
|
|
// than using 30 non drop, which will drift with about 1.8 frame per minute.
|
|
// Using 29.97, drop frame real time can be accurate only every 10th minute (10 minutes of 29.97 fps
|
|
// is exactly 17982 samples). One minute is 1798.2 samples, but we count 30 frames per second
|
|
// (30 * 60 = 1800). This means that at the first minute boundary (at the end of 0:0:59:29) we
|
|
// are 1.8 framess too late relative to real time. By dropping 2 frames (jumping to 0:1:0:2) we are
|
|
// approx. 0.2 frames too early. This adds up with 0.2 too early for each minute until we are 1.8
|
|
// samples too early at 0:9:0:2 (9 * 0.2 = 1.8). The 10th minute brings us 1.8 frames later again
|
|
// (at end of 0:9:59:29), which sums up to 0 (we are back to zero at 0:10:0:0 :-).
|
|
//
|
|
// In table form:
|
|
//
|
|
// Timecode value frames offset subframes offset seconds (rounded) 44100 sample (rounded)
|
|
// 0:00:00:00 0.0 0 0.000 0 (accurate)
|
|
// 0:00:59:29 1.8 144 60.027 2647177
|
|
// 0:01:00:02 -0.2 -16 60.060 2648648
|
|
// 0:01:59:29 1.6 128 120.020 5292883
|
|
// 0:02:00:02 -0.4 -32 120.053 5294354
|
|
// 0:02:59:29 1.4 112 180.013 7938588
|
|
// 0:03:00:02 -0.6 -48 180.047 7940060
|
|
// 0:03:59:29 1.2 96 240.007 10584294
|
|
// 0:04:00:02 -0.8 -64 240.040 10585766
|
|
// 0:04:59:29 1.0 80 300.000 13230000
|
|
// 0:05:00:02 -1.0 -80 300.033 13231471
|
|
// 0:05:59:29 0.8 64 359.993 15875706
|
|
// 0:06:00:02 -1.2 -96 360.027 15877177
|
|
// 0:06:59:29 0.6 48 419.987 18521411
|
|
// 0:07:00:02 -1.4 -112 420.020 18522883
|
|
// 0:07:59:29 0.4 32 478.980 21167117
|
|
// 0:08:00:02 -1.6 -128 480.013 21168589
|
|
// 0:08:59:29 0.2 16 539.973 23812823
|
|
// 0:09:00:02 -1.8 -144 540.007 23814294
|
|
// 0:09:59:29 0.0+ 0+ 599.967 26458529
|
|
// 0:10:00:00 0.0 0 600.000 26460000 (accurate)
|
|
//
|
|
// Per Sigmond <per@sigmond.no>
|
|
//
|
|
// This schma would compensate exactly for a frame-rate of 30 * 0.999. but the
|
|
// actual rate is 30000/1001 - which results in an offset of -3.6ms per hour or
|
|
// about -86ms over a 24-hour period. (SMPTE 12M-1999)
|
|
//
|
|
// Robin Gareus <robin@gareus.org>
|
|
|
|
const int64_t fps_i = ceil(timecode.rate);
|
|
int64_t totalMinutes = 60 * timecode.hours + timecode.minutes;
|
|
int64_t frameNumber = fps_i * 3600 * timecode.hours
|
|
+ fps_i * 60 * timecode.minutes
|
|
+ fps_i * timecode.seconds + timecode.frames
|
|
- 2 * (totalMinutes - totalMinutes / 10);
|
|
sample = frameNumber * sample_sample_rate / (double) timecode.rate;
|
|
} else {
|
|
/*
|
|
Non drop is easy.. just note the use of
|
|
rint(timecode.rate) * samples_per_timecode_frame
|
|
(frames per Timecode second), which is larger than
|
|
sample_rate() in the non-integer Timecode rate case.
|
|
*/
|
|
|
|
sample = (int64_t) rint(
|
|
(
|
|
((timecode.hours * 60 * 60) + (timecode.minutes * 60) + timecode.seconds)
|
|
*
|
|
(rint(timecode.rate) * samples_per_timecode_frame)
|
|
)
|
|
+ (timecode.frames * samples_per_timecode_frame)
|
|
);
|
|
}
|
|
|
|
if (use_subframes) {
|
|
sample += (int64_t) rint(((double)timecode.subframes * samples_per_timecode_frame) / (double)subframes_per_frame);
|
|
}
|
|
|
|
if (use_offset) {
|
|
if (offset_is_negative) {
|
|
if (sample >= offset_samples) {
|
|
sample -= offset_samples;
|
|
} else {
|
|
/* Prevent song-time from becoming negative */
|
|
sample = 0;
|
|
}
|
|
} else {
|
|
if (timecode.negative) {
|
|
if (sample <= offset_samples) {
|
|
sample = offset_samples - sample;
|
|
} else {
|
|
sample = 0;
|
|
}
|
|
} else {
|
|
sample += offset_samples;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
sample_to_timecode (
|
|
int64_t sample, Timecode::Time& timecode,
|
|
bool use_offset, bool use_subframes,
|
|
/* framerate info */
|
|
double timecode_frames_per_second,
|
|
bool timecode_drop_frames,
|
|
double sample_sample_rate/**< can include pull up/down */,
|
|
uint32_t subframes_per_frame,
|
|
/* optional offset - can be improved: function pointer to lazily query this*/
|
|
bool offset_is_negative, int64_t offset_samples
|
|
)
|
|
{
|
|
int64_t offset_sample;
|
|
|
|
if (!use_offset) {
|
|
timecode.negative = (sample < 0);
|
|
offset_sample = ::llabs(sample);
|
|
} else {
|
|
if (offset_is_negative) {
|
|
offset_sample = sample + offset_samples;
|
|
timecode.negative = false;
|
|
} else {
|
|
if (sample < offset_samples) {
|
|
offset_sample = (offset_samples - sample);
|
|
timecode.negative = true;
|
|
} else {
|
|
offset_sample = sample - offset_samples;
|
|
timecode.negative = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (timecode_drop_frames) {
|
|
int64_t frameNumber = floor( (double)offset_sample * timecode_frames_per_second / sample_sample_rate);
|
|
|
|
/* there are 17982 samples in 10 min @ 29.97df */
|
|
const int64_t D = frameNumber / 17982;
|
|
const int64_t M = frameNumber % 17982;
|
|
|
|
timecode.subframes = rint(subframes_per_frame
|
|
* ((double)offset_sample * timecode_frames_per_second / sample_sample_rate - (double)frameNumber));
|
|
|
|
if (timecode.subframes == subframes_per_frame) {
|
|
timecode.subframes = 0;
|
|
frameNumber++;
|
|
}
|
|
|
|
frameNumber += 18*D + 2*((M - 2) / 1798);
|
|
|
|
timecode.frames = frameNumber % 30;
|
|
timecode.seconds = (frameNumber / 30) % 60;
|
|
timecode.minutes = ((frameNumber / 30) / 60) % 60;
|
|
timecode.hours = (((frameNumber / 30) / 60) / 60);
|
|
|
|
} else {
|
|
double timecode_frames_left_exact;
|
|
double timecode_frames_fraction;
|
|
int64_t timecode_frames_left;
|
|
const double samples_per_timecode_frame = sample_sample_rate / timecode_frames_per_second;
|
|
const int64_t frames_per_hour = (int64_t)(3600. * rint(timecode_frames_per_second) * samples_per_timecode_frame);
|
|
|
|
timecode.hours = offset_sample / frames_per_hour;
|
|
|
|
// Extract whole hours. Do this to prevent rounding errors with
|
|
// high sample numbers in the calculations that follow.
|
|
timecode_frames_left_exact = (double)(offset_sample % frames_per_hour) / samples_per_timecode_frame;
|
|
timecode_frames_fraction = timecode_frames_left_exact - floor( timecode_frames_left_exact );
|
|
|
|
timecode.subframes = (int32_t) rint(timecode_frames_fraction * subframes_per_frame);
|
|
timecode_frames_left = (int64_t) floor (timecode_frames_left_exact);
|
|
|
|
if (use_subframes && timecode.subframes == subframes_per_frame) {
|
|
timecode_frames_left++;
|
|
timecode.subframes = 0;
|
|
}
|
|
|
|
timecode.minutes = timecode_frames_left / ((int32_t) lrint (timecode_frames_per_second) * 60);
|
|
timecode_frames_left = timecode_frames_left % ((int32_t) lrint (timecode_frames_per_second) * 60);
|
|
timecode.seconds = timecode_frames_left / (int32_t) lrint(timecode_frames_per_second);
|
|
timecode.frames = timecode_frames_left % (int32_t) lrint(timecode_frames_per_second);
|
|
}
|
|
|
|
if (!use_subframes) {
|
|
timecode.subframes = 0;
|
|
}
|
|
/* set frame rate and drop sample */
|
|
timecode.rate = timecode_frames_per_second;
|
|
timecode.drop = timecode_drop_frames;
|
|
}
|
|
|
|
std::ostream&
|
|
operator<<(std::ostream& ostr, const Time& t)
|
|
{
|
|
return t.print (ostr);
|
|
}
|
|
|
|
} // namespace Timecode
|
|
|