11namespace tracktion {
inline namespace engine
15 : seconds (s), beats (b), beatsPerBar (bpb)
19bool TimecodeDuration::operator== (
const TimecodeDuration& o)
const
21 if (seconds.has_value() != o.seconds.has_value())
return false;
22 if (beats.has_value() != o.beats.has_value())
return false;
24 if (seconds.has_value())
25 if (*seconds != *o.seconds)
28 if (beats.has_value())
29 if (*beats != *o.beats)
35bool TimecodeDuration::operator!= (
const TimecodeDuration& o)
const
37 return ! (*
this == o);
40TimecodeDuration TimecodeDuration::fromSeconds (Edit& e, TimePosition start, TimePosition end)
42 return TimecodeDuration (end - start,
43 e.tempoSequence.toBeats (end) - e.tempoSequence.toBeats (start),
44 e.tempoSequence.getTimeSigAt (start).numerator);
47TimecodeDuration TimecodeDuration::fromSecondsOnly (TimeDuration duration)
49 return TimecodeDuration (duration, {}, {});
52TimecodeDuration TimecodeDuration::fromBeatsOnly (BeatDuration duration,
int beatsPerBar)
54 return TimecodeDuration ({}, duration, beatsPerBar);
83static const TimeAndName subBeatFractions[] =
96static const TimeAndName subBeatFractionsTriplets[] =
109static const int subSecDivisionsForType[] = { 1000, Edit::ticksPerQuarterNote, 24, 25, 30 };
111static const int barMultiples[] = { 1, 2, 4, 8, 16, 64, 128, 256, 1024, 4096, 16384, 65536 };
114static const TimeDuration nudge = TimeDuration::fromSeconds (0.05 / 96000.0);
118static TimeAndName getMinSecDivisions (
int level)
noexcept
121 return minSecDivisions [
std::min (13, level)];
124juce::String TimecodeSnapType::getDescription (
const TempoSetting& tempo,
bool isTripletOverride)
const
126 if (type == TimecodeType::barsBeats)
130 bool triplets = isTripletOverride || tempo.getMatchingTimeSig().triplets;
131 return TRANS (triplets ? subBeatFractionsTriplets[level].name
132 : subBeatFractions[level].name);
135 if (level == 9)
return TRANS(
"Beat");
136 if (level == 10)
return TRANS(
"Bar");
141 if (type == TimecodeType::millisecs)
142 return TRANS(getMinSecDivisions (level).name);
144 if (level == 0)
return TRANS(
"1/100 frame");
145 if (level == 1)
return TRANS(
"Frame");
147 return TRANS(getMinSecDivisions (level + 2).name);
150TimeDuration TimecodeSnapType::getApproxIntervalTime (
const TempoSetting& tempo)
const
152 return getApproxIntervalTime (tempo,
false);
157 if (type == TimecodeType::barsBeats)
164 return beatLen * subBeatFractionsTriplets [level].time;
166 return beatLen * subBeatFractions [level].time;
174 return barLength * barMultiples[level - 10];
177 return getIntervalNonBarsBeats();
180TimeDuration TimecodeSnapType::getIntervalNonBarsBeats()
const
182 if (type == TimecodeType::millisecs)
183 return TimeDuration::fromSeconds (getMinSecDivisions (level).time);
185 jassert (type != TimecodeType::barsBeats);
187 auto oneFrame = 1.0 / subSecDivisionsForType[
static_cast<int> (type)];
190 return TimeDuration::fromSeconds (oneFrame * 0.01);
193 return TimeDuration::fromSeconds (oneFrame);
195 return TimeDuration::fromSeconds (getMinSecDivisions (level + 2).time);
200 if (type == TimecodeType::barsBeats)
203 return TRANS(
"Bar 1");
205 auto barsBeats = sequence.toBarsAndBeats (time + nudge);
206 auto bars = barsBeats.bars + 1;
207 auto beats = barsBeats.getWholeBeats() + 1;
209 if (level < 9)
return juce::String::formatted (
"%d|%d|%03d", bars, beats, (
int) (barsBeats.getFractionalBeats().inBeats() * Edit::ticksPerQuarterNote));
215 if (type == TimecodeType::millisecs)
217 if (time == TimePosition() && useStartLabelIfZero)
224 return TimecodeDisplayFormat::toFullTimecode (time, 0,
false);
226 return TimecodeDisplayFormat::toFullTimecode (time, 1000,
false);
229 if (time == TimePosition() && useStartLabelIfZero)
236 return TimecodeDisplayFormat::toFullTimecode (time, 0,
false);
238 return TimecodeDisplayFormat::toFullTimecode (time, subSecDivisionsForType[
static_cast<int> (type)],
false);
241int TimecodeSnapType::getOneBarLevel() const noexcept
243 return type == TimecodeType::barsBeats ? 10 : (type == TimecodeType::millisecs ? 4 : 2);
246TimePosition TimecodeSnapType::roundTimeDown (TimePosition t,
const TempoSequence& sequence)
const
248 return roundTime (t, sequence, 0.0);
251TimePosition TimecodeSnapType::roundTimeDown (TimePosition t,
const TempoSequence& sequence,
bool isTripletsOverride)
const
253 return roundTime (t, sequence, 0.0, isTripletsOverride);
256TimePosition TimecodeSnapType::roundTimeNearest (TimePosition t,
const TempoSequence& sequence)
const
258 return roundTime (t, sequence, 0.5 - 1.0e-10);
261TimePosition TimecodeSnapType::roundTimeNearest (TimePosition t,
const TempoSequence& sequence,
bool isTripletsOverride)
const
263 return roundTime (t, sequence, 0.5 - 1.0e-10, isTripletsOverride);
266TimePosition TimecodeSnapType::roundTimeUp (TimePosition t,
const TempoSequence& sequence)
const
268 return roundTime (t, sequence, 1.0 - 1.0e-10);
271TimePosition TimecodeSnapType::roundTimeUp (TimePosition t,
const TempoSequence& sequence,
bool isTripletsOverride)
const
273 return roundTime (t, sequence, 1.0 - 1.0e-10, isTripletsOverride);
276TimePosition TimecodeSnapType::roundTime (TimePosition t,
const TempoSequence& sequence,
double adjustment)
const
278 return roundTime (t, sequence, adjustment, sequence.isTripletsAtTime (t));
281TimePosition TimecodeSnapType::roundTime (TimePosition t,
const TempoSequence& sequence,
double adjustment,
bool tripletsOverride)
const
283 if (type == TimecodeType::barsBeats)
285 auto barsBeats = sequence.toBarsAndBeats (t);
286 auto& tempo = sequence.getTempoAt (t);
290 auto q = tripletsOverride ? subBeatFractionsTriplets[level].time
291 : subBeatFractions[level].time;
293 barsBeats.beats = BeatDuration::fromBeats (q *
std::floor (barsBeats.beats.inBeats() / q + adjustment));
297 barsBeats.beats = BeatDuration::fromBeats (
std::floor (barsBeats.beats.inBeats() + adjustment));
301 auto barsPlusBeats = barsBeats.bars + barsBeats.beats.inBeats() / tempo.getMatchingTimeSig().numerator;
302 auto q = barMultiples[level - 10];
304 barsBeats.bars = q * (
int)
std::floor (barsPlusBeats / q + adjustment);
305 barsBeats.beats = {};
308 return sequence.toTime (barsBeats);
311 auto q = getIntervalNonBarsBeats();
315TimecodeSnapType TimecodeSnapType::getSnapTypeForMaximumSnapLevelOf (TimePosition t,
const TempoSequence& sequence)
const
317 return getSnapTypeForMaximumSnapLevelOf (t, sequence, sequence.isTripletsAtTime (t));
320TimecodeSnapType TimecodeSnapType::getSnapTypeForMaximumSnapLevelOf (TimePosition t,
const TempoSequence& sequence,
bool isTripletsOverride)
const
322 const TimecodeDisplayFormat
format (type);
323 auto numTypes =
format.getNumSnapTypes();
326 for (i = level; i < numTypes; ++i)
328 TimecodeSnapType snap (type, i);
330 if (std::abs ((t - snap.roundTimeNearest (t, sequence, isTripletsOverride)).inSeconds()) > 1.0e-6)
337 return format.getSnapType (i);
340TimecodeSnapType TimecodeSnapType::get1BeatSnapType()
342 return TimecodeSnapType (TimecodeType::barsBeats, 9);
346bool TimecodeDisplayFormat::isBarsBeats()
const {
return type == TimecodeType::barsBeats; }
347bool TimecodeDisplayFormat::isMilliseconds()
const {
return type == TimecodeType::millisecs; }
348bool TimecodeDisplayFormat::isSMPTE()
const {
return type == TimecodeType::fps24 || type == TimecodeType::fps25 || type == TimecodeType::fps30; }
350int TimecodeDisplayFormat::getFPS()
const
352 const int defaultFPS = 24;
356 case TimecodeType::millisecs:
return defaultFPS;
357 case TimecodeType::barsBeats:
return defaultFPS;
358 case TimecodeType::fps24:
return 24;
359 case TimecodeType::fps25:
return 25;
360 case TimecodeType::fps30:
return 30;
365juce::String TimecodeDisplayFormat::getRoundingDescription()
const
369 case TimecodeType::millisecs:
return TRANS(
"Snap to nearest round number");
370 case TimecodeType::barsBeats:
return TRANS(
"Snap to nearest beat or subdivision");
371 case TimecodeType::fps24:
372 case TimecodeType::fps25:
373 case TimecodeType::fps30:
return TRANS(
"Snap to nearest frame");
380int TimecodeDisplayFormat::getSubSecondDivisions()
const
382 return subSecDivisionsForType[
static_cast<int> (type)];
385juce::String TimecodeDisplayFormat::getString (
const TempoSequence& tempo,
const TimePosition time,
bool isRelative)
const
387 if (type == TimecodeType::barsBeats)
389 tempo::BarsAndBeats barsBeats;
391 BeatDuration fraction;
395 barsBeats = tempo.toBarsAndBeats (time + nudge);
396 bars = barsBeats.bars + 1;
397 beats = barsBeats.getWholeBeats() + 1;
398 fraction = barsBeats.getFractionalBeats();
402 barsBeats = tempo.toBarsAndBeats (time - nudge);
403 bars = -barsBeats.bars - 1;
404 beats = (tempo.getTimeSig(0)->numerator - 1) - barsBeats.getWholeBeats();
405 fraction = BeatDuration::fromBeats (1.0) - barsBeats.getFractionalBeats();
409 barsBeats = tempo.toBarsAndBeats (time + nudge);
410 bars = barsBeats.bars + 1;
411 beats = barsBeats.getWholeBeats() + 1;
412 fraction = barsBeats.getFractionalBeats();
415 auto s =
juce::String::formatted (
"%d|%d|%03d", bars, beats, (
int) (fraction.inBeats() * Edit::ticksPerQuarterNote));
416 return time < 0s ? (
"-" + s) : s;
419 return TimecodeDisplayFormat::toFullTimecode (time, getSubSecondDivisions());
422int TimecodeDisplayFormat::getNumParts()
const
424 return type == TimecodeType::barsBeats ? 3 : 4;
427juce::String TimecodeDisplayFormat::getSeparator (
int part)
const
429 return (type == TimecodeType::barsBeats) ?
","
430 : ((part == 0 && type == TimecodeType::millisecs) ?
"." :
":");
433int TimecodeDisplayFormat::getMaxCharsInPart (
int part,
bool canBeNegative)
const
437 const char m[5][4] = { { 3, 2, 2, 2 },
443 return m[
static_cast<int> (type)][part];
446 const char m[5][4] = { { 3, 2, 2, 2 },
452 return m[
static_cast<int> (type)][part];
455int TimecodeDisplayFormat::getMaxValueOfPart (
const TempoSequence& sequence, TimecodeDuration currentTime,
int part,
bool isRelative)
const
457 if (type == TimecodeType::barsBeats && part == 1)
459 if (currentTime.beatsPerBar != 0.0)
460 return currentTime.beatsPerBar - (isRelative ? 1 : 0);
462 return sequence.getTimeSigAt (toPosition (*currentTime.seconds)).numerator - (isRelative ? 1 : 0);
465 const short m[5][4] = { { 999, 59, 59, 48 },
466 { 959, 99, 999, 9999 },
469 { 29, 59, 59, 48 } };
471 return m[
static_cast<int> (type)][part];
474int TimecodeDisplayFormat::getMinValueOfPart (
int part,
bool isRelative)
const
476 return (type == TimecodeType::barsBeats && part > 0 && ! isRelative) ? 1 : 0;
487void TimecodeDisplayFormat::getPartStrings (TimecodeDuration duration,
488 const TempoSequence& tempo,
492 if (type == TimecodeType::barsBeats)
494 tempo::BarsAndBeats barsBeats;
496 if (duration.beats.has_value())
498 auto t = (*duration.beats).inBeats() + nudge.
inSeconds();
500 barsBeats.bars =
int (t / duration.beatsPerBar);
501 barsBeats.beats = BeatDuration::fromBeats (t - (barsBeats.bars * duration.beatsPerBar));
503 else if (duration.seconds.has_value())
505 auto time = *duration.seconds;
513 barsBeats = tempo.toBarsAndBeats (toPosition (time + nudge));
517 auto val = (
int) (barsBeats.getFractionalBeats().inBeats() * Edit::ticksPerQuarterNote);
520 text[0] = (
char) (
'0' + (val / 100) % 10);
521 text[1] = (
char) (
'0' + (val / 10) % 10);
522 text[2] = (
char) (
'0' + val % 10);
527 for (
int part = 1; part < 3; ++part)
529 int val = (part == 1) ? barsBeats.getWholeBeats() : barsBeats.bars;
534 results[part] << val;
537 else if (duration.seconds.has_value())
539 auto t = (
tracktion::abs (*duration.seconds) + nudge).inSeconds();
541 if (type == TimecodeType::millisecs)
543 auto val = ((
int) (t * 1000.0)) % 1000;
546 text[0] = (
char) (
'0' + (val / 100) % 10);
547 text[1] = (
char) (
'0' + (val / 10) % 10);
548 text[2] = (
char) (
'0' + val % 10);
552 else if (type == TimecodeType::fps24)
554 results[0] = twoCharString (((
int) (t * 24)) % 24);
556 else if (type == TimecodeType::fps25)
558 results[0] = twoCharString (((
int) (t * 25)) % 25);
562 jassert (type == TimecodeType::fps30);
563 results[0] = twoCharString (((
int) (t * 30)) % 30);
566 if (*duration.seconds < 0s)
569 auto hours = (
int) (t * (1.0 / 3600.0));
570 results[3] << twoCharString (hours);
572 auto mins = (((
int) t) / 60) % 60;
573 results[2] = twoCharString (mins);
575 auto secs = (((
int) t) % 60);
576 results[1] = twoCharString (secs);
584TimecodeDuration TimecodeDisplayFormat::getNewTimeWithPartValue (TimecodeDuration time,
const TempoSequence& tempo,
585 int part,
int newValue,
bool isRelative)
const
587 if (type == TimecodeType::barsBeats)
589 if (
time.beats.has_value())
591 auto t = (*
time.beats).inBeats();
593 auto bars =
int (t /
time.beatsPerBar);
594 auto beats = t - (bars *
time.beatsPerBar);
598 if (part == 2) bars = newValue;
599 else if (part == 1) beats = newValue;
600 else if (part == 0) ticks = newValue /
double (Edit::ticksPerQuarterNote);
602 return TimecodeDuration::fromBeatsOnly (BeatDuration::fromBeats (bars *
time.beatsPerBar + beats + ticks),
time.beatsPerBar);
611 auto barsBeats = tempo.toBarsAndBeats (t);
613 if (part == 0) pos.add (BeatDuration::fromBeats (newValue / (
double) Edit::ticksPerQuarterNote) - barsBeats.getFractionalBeats());
614 else if (part == 1) pos.add (BeatDuration::fromBeats ((isRelative ? newValue : (newValue - 1)) - barsBeats.getWholeBeats()));
615 else if (part == 2) pos.addBars ((isRelative ? newValue : (newValue - 1)) - barsBeats.bars);
617 return TimecodeDuration::fromSecondsOnly (toDuration (*
time.seconds < 0s ? -pos.getTime() : pos.getTime()));
623 auto hours = (
int) (t / 3600.0);
624 auto mins = (intT / 60) % 60;
625 auto secs = (intT % 60);
626 auto frac = t - intT;
630 auto subSecDivs = getSubSecondDivisions();
631 frac = ((newValue + subSecDivs * 1000) % subSecDivs) / (
double) subSecDivs;
635 secs = (newValue + 3600) % 60;
639 mins = (newValue + 3600) % 60;
646 t = hours * 3600.0 + mins * 60.0 + secs + frac;
648 return TimecodeDuration::fromSecondsOnly (TimeDuration::fromSeconds (*
time.seconds < 0s ? -t : t));
655 auto absSecs = tracktion::abs (seconds).
inSeconds();
657 if (showHours || (absSecs >= 60.0 * 60.0))
659 (
int) (absSecs / (60.0 * 60.0)),
660 ((
int) (absSecs / 60.0)) % 60,
661 ((
int) absSecs) % 60);
664 ((
int) (absSecs / 60.0)) % 60,
665 ((
int) absSecs) % 60);
667 if (subSecondDivisions > 0)
678 bool isTripletOverride)
const
680 if (type >= TimecodeType::fps24 && (1.0 / getSubSecondDivisions()) / onScreenTimePerPixel.
inSeconds() > 2)
683 auto numSnapTypes = getNumSnapTypes();
685 for (
int i = 0; i < numSnapTypes; ++i)
688 auto res = snap.getApproxIntervalTime (tempo, isTripletOverride);
689 auto t = res / onScreenTimePerPixel.
inSeconds();
695 return getSnapType (numSnapTypes);
698int TimecodeDisplayFormat::getNumSnapTypes()
const
700 return type == TimecodeType::millisecs ? 13
701 : (type == TimecodeType::barsBeats ? 19 : 15);
704TimecodeSnapType TimecodeDisplayFormat::getSnapType (
int index)
const
706 return TimecodeSnapType (type,
juce::jlimit (0, getNumSnapTypes() - 1, index));
710TimecodeDisplayIterator::TimecodeDisplayIterator (
const Edit& edit, TimePosition startTime,
711 TimecodeSnapType minSnapTypeToUse,
bool to)
712 : sequence (edit.tempoSequence),
713 minSnapType (minSnapTypeToUse),
714 time (minSnapType.roundTimeDown (startTime, sequence)),
715 isTripletOverride (to)
721 bool triplets = isTripletOverride || sequence.isTripletsAtTime (
time);
722 auto nextTime =
std::max (0_tp, minSnapType.roundTimeUp (
time + 1.0e-5s, sequence, triplets));
724 if (nextTime <=
time)
727 nextTime =
time + minSnapType.getApproxIntervalTime (*sequence.getTempo (0), triplets);
731 currentSnapType = minSnapType.getSnapTypeForMaximumSnapLevelOf (
time, sequence, triplets);
735juce::String TimecodeDisplayIterator::getTimecodeAsString()
const
737 return currentSnapType.getTimecodeString (time, sequence,
true);
740bool TimecodeDisplayIterator::isOneBarOrGreater() const noexcept
742 return currentSnapType.getLevel() >= currentSnapType.getOneBarLevel();
Type get() const noexcept
String getDescription(const String &returnValueForZeroTime="0") const
static String formatted(const String &formatStr, Args... args)
static String charToString(juce_wchar character)
Holds a list of TempoSetting objects, to form a sequence of tempo changes.
A tempo value, as used in a TempoSequence.
TimeSigSetting & getMatchingTimeSig() const
Returns the time signature at this tempo's time in the sequence.
TimeDuration getApproxBeatLength() const
Returns the approximate length of one beat based on the bpm and matching time sig denonimator.
#define TRANS(stringLiteral)
#define NEEDS_TRANS(stringLiteral)
Type jlimit(Type lowerLimit, Type upperLimit, Type valueToConstrain) noexcept
int roundToInt(const FloatType value) noexcept
tempo::Sequence::Position createPosition(const TempoSequence &s)
Creates a Position to iterate over the given TempoSequence.
constexpr TimePosition toPosition(TimeDuration)
Converts a TimeDuration to a TimePosition.
TimePosition abs(TimePosition)
Returns the absolute of this TimePosition.
Represents a duration in real-life time.
constexpr double inSeconds() const
Returns the TimeDuration as a number of seconds.
Represents a position in real-life time.
constexpr double inSeconds() const
Returns the TimePosition as a number of seconds.
TimePosition next()
returns the next time.