tracktion-engine 3.0-10-g034fdde4aa5
Tracktion Engine — High level data model for audio applications

« « « Anklang Documentation
Loading...
Searching...
No Matches
tracktion_TimecodeDisplayFormat.cpp
Go to the documentation of this file.
1 /*
2 ,--. ,--. ,--. ,--.
3 ,-' '-.,--.--.,--,--.,---.| |,-.,-' '-.`--' ,---. ,--,--, Copyright 2024
4 '-. .-'| .--' ,-. | .--'| /'-. .-',--.| .-. || \ Tracktion Software
5 | | | | \ '-' \ `--.| \ \ | | | |' '-' '| || | Corporation
6 `---' `--' `--`--'`---'`--'`--' `---' `--' `---' `--''--' www.tracktion.com
7
8 Tracktion Engine uses a GPL/commercial licence - see LICENCE.md for details.
9*/
10
11namespace tracktion { inline namespace engine
12{
13
14TimecodeDuration::TimecodeDuration (std::optional<TimeDuration> s, std::optional<BeatDuration> b, int bpb)
15 : seconds (s), beats (b), beatsPerBar (bpb)
16{
17}
18
19bool TimecodeDuration::operator== (const TimecodeDuration& o) const
20{
21 if (seconds.has_value() != o.seconds.has_value()) return false;
22 if (beats.has_value() != o.beats.has_value()) return false;
23
24 if (seconds.has_value())
25 if (*seconds != *o.seconds)
26 return false;
27
28 if (beats.has_value())
29 if (*beats != *o.beats)
30 return false;
31
32 return true;
33}
34
35bool TimecodeDuration::operator!= (const TimecodeDuration& o) const
36{
37 return ! (*this == o);
38}
39
40TimecodeDuration TimecodeDuration::fromSeconds (Edit& e, TimePosition start, TimePosition end)
41{
42 return TimecodeDuration (end - start,
43 e.tempoSequence.toBeats (end) - e.tempoSequence.toBeats (start),
44 e.tempoSequence.getTimeSigAt (start).numerator);
45}
46
47TimecodeDuration TimecodeDuration::fromSecondsOnly (TimeDuration duration)
48{
49 return TimecodeDuration (duration, {}, {});
50}
51
52TimecodeDuration TimecodeDuration::fromBeatsOnly (BeatDuration duration, int beatsPerBar)
53{
54 return TimecodeDuration ({}, duration, beatsPerBar);
55}
56
58{
59 double time;
60 const char* name;
61};
62
63static const TimeAndName minSecDivisions[] =
64{
65 { 0.001, NEEDS_TRANS("1 millisec") },
66 { 0.01, NEEDS_TRANS("10 millisecs") },
67 { 0.1, NEEDS_TRANS("100 millisecs") },
68 { 0.5, NEEDS_TRANS("1/2 second") },
69 { 1.0, NEEDS_TRANS("second") },
70 { 2.0, NEEDS_TRANS("2 seconds") },
71 { 5.0, NEEDS_TRANS("5 seconds") },
72 { 10.0, NEEDS_TRANS("10 seconds") },
73 { 30.0, NEEDS_TRANS("30 seconds") },
74 { 60.0, NEEDS_TRANS("minute") },
75 { 5 * 60.0, NEEDS_TRANS("5 minutes") },
76 { 15 * 60.0, NEEDS_TRANS("15 minutes") },
77 { 30 * 60.0, NEEDS_TRANS("30 minutes") },
78 { 60 * 60.0, NEEDS_TRANS("hour") },
79 { 5 * 60 * 60.0, NEEDS_TRANS("5 hours") }
80};
81
82
83static const TimeAndName subBeatFractions[] =
84{
85 { 1.0 / 960, NEEDS_TRANS("1 tick") },
86 { 2.0 / 960, NEEDS_TRANS("2 ticks") },
87 { 5.0 / 960, NEEDS_TRANS("5 ticks") },
88 { 15.0 / 960, NEEDS_TRANS("1/64 beat") },
89 { 30.0 / 960, NEEDS_TRANS("1/32 beat") },
90 { 60.0 / 960, NEEDS_TRANS("1/16 beat") },
91 { 120.0 / 960, NEEDS_TRANS("1/8 beat") },
92 { 240.0 / 960, NEEDS_TRANS("1/4 beat") },
93 { 480.0 / 960, NEEDS_TRANS("1/2 beat") }
94};
95
96static const TimeAndName subBeatFractionsTriplets[] =
97{
98 { 1.0 / 960, NEEDS_TRANS("1 tick") },
99 { 2.0 / 960, NEEDS_TRANS("2 ticks") },
100 { 5.0 / 960, NEEDS_TRANS("5 ticks") },
101 { 1.0 / 48.0, NEEDS_TRANS("1/48 beat") },
102 { 1.0 / 24.0, NEEDS_TRANS("1/24 beat") },
103 { 1.0 / 12.0, NEEDS_TRANS("1/12 beat") },
104 { 1.0 / 9.0, NEEDS_TRANS("1/9 beat") },
105 { 1.0 / 6.0, NEEDS_TRANS("1/6 beat") },
106 { 1.0 / 3.0, NEEDS_TRANS("1/3 beat") }
107};
108
109static const int subSecDivisionsForType[] = { 1000, Edit::ticksPerQuarterNote, 24, 25, 30 };
110
111static const int barMultiples[] = { 1, 2, 4, 8, 16, 64, 128, 256, 1024, 4096, 16384, 65536 };
112
113// maximum value we can add without shoving a time over into the next 'slot'
114static const TimeDuration nudge = TimeDuration::fromSeconds (0.05 / 96000.0);
115
116
117//==============================================================================
118static TimeAndName getMinSecDivisions (int level) noexcept
119{
120 jassert (level >= 0);
121 return minSecDivisions [std::min (13, level)];
122}
123
124juce::String TimecodeSnapType::getDescription (const TempoSetting& tempo, bool isTripletOverride) const
125{
126 if (type == TimecodeType::barsBeats)
127 {
128 if (level < 9)
129 {
130 bool triplets = isTripletOverride || tempo.getMatchingTimeSig().triplets;
131 return TRANS (triplets ? subBeatFractionsTriplets[level].name
132 : subBeatFractions[level].name);
133 }
134
135 if (level == 9) return TRANS("Beat");
136 if (level == 10) return TRANS("Bar");
137
138 return TRANS("33 bars").replace ("33", juce::String (barMultiples[std::min (level, 19) - 10]));
139 }
140
141 if (type == TimecodeType::millisecs)
142 return TRANS(getMinSecDivisions (level).name);
143
144 if (level == 0) return TRANS("1/100 frame");
145 if (level == 1) return TRANS("Frame");
146
147 return TRANS(getMinSecDivisions (level + 2).name);
148}
149
150TimeDuration TimecodeSnapType::getApproxIntervalTime (const TempoSetting& tempo) const
151{
152 return getApproxIntervalTime (tempo, false);
153}
154
155TimeDuration TimecodeSnapType::getApproxIntervalTime (const TempoSetting& tempo, bool isTripletsOverride) const
156{
157 if (type == TimecodeType::barsBeats)
158 {
159 const auto beatLen = tempo.getApproxBeatLength();
160
161 if (level < 9)
162 {
163 if (isTripletsOverride || tempo.getMatchingTimeSig().triplets)
164 return beatLen * subBeatFractionsTriplets [level].time;
165
166 return beatLen * subBeatFractions [level].time;
167 }
168
169 if (level == 9)
170 return beatLen;
171
172 auto barLength = beatLen * tempo.getMatchingTimeSig().numerator.get();
173
174 return barLength * barMultiples[level - 10];
175 }
176
177 return getIntervalNonBarsBeats();
178}
179
180TimeDuration TimecodeSnapType::getIntervalNonBarsBeats() const
181{
182 if (type == TimecodeType::millisecs)
183 return TimeDuration::fromSeconds (getMinSecDivisions (level).time);
184
185 jassert (type != TimecodeType::barsBeats);
186
187 auto oneFrame = 1.0 / subSecDivisionsForType[static_cast<int> (type)];
188
189 if (level == 0)
190 return TimeDuration::fromSeconds (oneFrame * 0.01);
191
192 if (level == 1)
193 return TimeDuration::fromSeconds (oneFrame);
194
195 return TimeDuration::fromSeconds (getMinSecDivisions (level + 2).time);
196}
197
198juce::String TimecodeSnapType::getTimecodeString (TimePosition time, const TempoSequence& sequence, bool useStartLabelIfZero) const
199{
200 if (type == TimecodeType::barsBeats)
201 {
202 if (time == TimePosition() && useStartLabelIfZero)
203 return TRANS("Bar 1");
204
205 auto barsBeats = sequence.toBarsAndBeats (time + nudge);
206 auto bars = barsBeats.bars + 1;
207 auto beats = barsBeats.getWholeBeats() + 1;
208
209 if (level < 9) return juce::String::formatted ("%d|%d|%03d", bars, beats, (int) (barsBeats.getFractionalBeats().inBeats() * Edit::ticksPerQuarterNote));
210 if (level == 9) return juce::String::formatted ("%d|%d", bars, beats);
211
212 return TRANS("Bar") + " " + juce::String (bars);
213 }
214
215 if (type == TimecodeType::millisecs)
216 {
217 if (time == TimePosition() && useStartLabelIfZero)
218 return "0";
219
220 if (level >= 9)
221 return juce::RelativeTime (time.inSeconds()).getDescription();
222
223 if (level >= 4)
224 return TimecodeDisplayFormat::toFullTimecode (time, 0, false);
225
226 return TimecodeDisplayFormat::toFullTimecode (time, 1000, false);
227 }
228
229 if (time == TimePosition() && useStartLabelIfZero)
230 return "0";
231
232 if (level >= 7)
233 return juce::RelativeTime (time.inSeconds()).getDescription();
234
235 if (level >= 2)
236 return TimecodeDisplayFormat::toFullTimecode (time, 0, false);
237
238 return TimecodeDisplayFormat::toFullTimecode (time, subSecDivisionsForType[static_cast<int> (type)], false);
239}
240
241int TimecodeSnapType::getOneBarLevel() const noexcept
242{
243 return type == TimecodeType::barsBeats ? 10 : (type == TimecodeType::millisecs ? 4 : 2);
244}
245
246TimePosition TimecodeSnapType::roundTimeDown (TimePosition t, const TempoSequence& sequence) const
247{
248 return roundTime (t, sequence, 0.0);
249}
250
251TimePosition TimecodeSnapType::roundTimeDown (TimePosition t, const TempoSequence& sequence, bool isTripletsOverride) const
252{
253 return roundTime (t, sequence, 0.0, isTripletsOverride);
254}
255
256TimePosition TimecodeSnapType::roundTimeNearest (TimePosition t, const TempoSequence& sequence) const
257{
258 return roundTime (t, sequence, 0.5 - 1.0e-10);
259}
260
261TimePosition TimecodeSnapType::roundTimeNearest (TimePosition t, const TempoSequence& sequence, bool isTripletsOverride) const
262{
263 return roundTime (t, sequence, 0.5 - 1.0e-10, isTripletsOverride);
264}
265
266TimePosition TimecodeSnapType::roundTimeUp (TimePosition t, const TempoSequence& sequence) const
267{
268 return roundTime (t, sequence, 1.0 - 1.0e-10);
269}
270
271TimePosition TimecodeSnapType::roundTimeUp (TimePosition t, const TempoSequence& sequence, bool isTripletsOverride) const
272{
273 return roundTime (t, sequence, 1.0 - 1.0e-10, isTripletsOverride);
274}
275
276TimePosition TimecodeSnapType::roundTime (TimePosition t, const TempoSequence& sequence, double adjustment) const
277{
278 return roundTime (t, sequence, adjustment, sequence.isTripletsAtTime (t));
279}
280
281TimePosition TimecodeSnapType::roundTime (TimePosition t, const TempoSequence& sequence, double adjustment, bool tripletsOverride) const
282{
283 if (type == TimecodeType::barsBeats)
284 {
285 auto barsBeats = sequence.toBarsAndBeats (t);
286 auto& tempo = sequence.getTempoAt (t);
287
288 if (level < 9)
289 {
290 auto q = tripletsOverride ? subBeatFractionsTriplets[level].time
291 : subBeatFractions[level].time;
292
293 barsBeats.beats = BeatDuration::fromBeats (q * std::floor (barsBeats.beats.inBeats() / q + adjustment));
294 }
295 else if (level == 9)
296 {
297 barsBeats.beats = BeatDuration::fromBeats (std::floor (barsBeats.beats.inBeats() + adjustment));
298 }
299 else
300 {
301 auto barsPlusBeats = barsBeats.bars + barsBeats.beats.inBeats() / tempo.getMatchingTimeSig().numerator;
302 auto q = barMultiples[level - 10];
303
304 barsBeats.bars = q * (int) std::floor (barsPlusBeats / q + adjustment);
305 barsBeats.beats = {};
306 }
307
308 return sequence.toTime (barsBeats);
309 }
310
311 auto q = getIntervalNonBarsBeats();
312 return toPosition (q * std::floor ((t / q) + adjustment));
313}
314
315TimecodeSnapType TimecodeSnapType::getSnapTypeForMaximumSnapLevelOf (TimePosition t, const TempoSequence& sequence) const
316{
317 return getSnapTypeForMaximumSnapLevelOf (t, sequence, sequence.isTripletsAtTime (t));
318}
319
320TimecodeSnapType TimecodeSnapType::getSnapTypeForMaximumSnapLevelOf (TimePosition t, const TempoSequence& sequence, bool isTripletsOverride) const
321{
322 const TimecodeDisplayFormat format (type);
323 auto numTypes = format.getNumSnapTypes();
324 int i;
325
326 for (i = level; i < numTypes; ++i)
327 {
328 TimecodeSnapType snap (type, i);
329
330 if (std::abs ((t - snap.roundTimeNearest (t, sequence, isTripletsOverride)).inSeconds()) > 1.0e-6)
331 {
332 --i;
333 break;
334 }
335 }
336
337 return format.getSnapType (i);
338}
339
340TimecodeSnapType TimecodeSnapType::get1BeatSnapType()
341{
342 return TimecodeSnapType (TimecodeType::barsBeats, 9);
343}
344
345//==============================================================================
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; }
349
350int TimecodeDisplayFormat::getFPS() const
351{
352 const int defaultFPS = 24;
353
354 switch (type)
355 {
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;
361 default: jassertfalse; return defaultFPS;
362 }
363}
364
365juce::String TimecodeDisplayFormat::getRoundingDescription() const
366{
367 switch (type)
368 {
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");
374 default: jassertfalse; break;
375 }
376
377 return {};
378}
379
380int TimecodeDisplayFormat::getSubSecondDivisions() const
381{
382 return subSecDivisionsForType[static_cast<int> (type)];
383}
384
385juce::String TimecodeDisplayFormat::getString (const TempoSequence& tempo, const TimePosition time, bool isRelative) const
386{
387 if (type == TimecodeType::barsBeats)
388 {
389 tempo::BarsAndBeats barsBeats;
390 int bars, beats;
391 BeatDuration fraction;
392
393 if (! isRelative)
394 {
395 barsBeats = tempo.toBarsAndBeats (time + nudge);
396 bars = barsBeats.bars + 1;
397 beats = barsBeats.getWholeBeats() + 1;
398 fraction = barsBeats.getFractionalBeats();
399 }
400 else if (time < 0s)
401 {
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();
406 }
407 else
408 {
409 barsBeats = tempo.toBarsAndBeats (time + nudge);
410 bars = barsBeats.bars + 1;
411 beats = barsBeats.getWholeBeats() + 1;
412 fraction = barsBeats.getFractionalBeats();
413 }
414
415 auto s = juce::String::formatted ("%d|%d|%03d", bars, beats, (int) (fraction.inBeats() * Edit::ticksPerQuarterNote));
416 return time < 0s ? ("-" + s) : s;
417 }
418
419 return TimecodeDisplayFormat::toFullTimecode (time, getSubSecondDivisions());
420}
421
422int TimecodeDisplayFormat::getNumParts() const
423{
424 return type == TimecodeType::barsBeats ? 3 : 4;
425}
426
427juce::String TimecodeDisplayFormat::getSeparator (int part) const
428{
429 return (type == TimecodeType::barsBeats) ? ","
430 : ((part == 0 && type == TimecodeType::millisecs) ? "." : ":");
431}
432
433int TimecodeDisplayFormat::getMaxCharsInPart (int part, bool canBeNegative) const
434{
435 if (canBeNegative)
436 {
437 const char m[5][4] = { { 3, 2, 2, 2 },
438 { 3, 2, 4, 4 },
439 { 2, 2, 2, 3 },
440 { 2, 2, 2, 3 },
441 { 2, 2, 2, 3 } };
442
443 return m[static_cast<int> (type)][part];
444 }
445
446 const char m[5][4] = { { 3, 2, 2, 2 },
447 { 3, 2, 4, 4 },
448 { 2, 2, 2, 2 },
449 { 2, 2, 2, 2 },
450 { 2, 2, 2, 2 } };
451
452 return m[static_cast<int> (type)][part];
453}
454
455int TimecodeDisplayFormat::getMaxValueOfPart (const TempoSequence& sequence, TimecodeDuration currentTime, int part, bool isRelative) const
456{
457 if (type == TimecodeType::barsBeats && part == 1)
458 {
459 if (currentTime.beatsPerBar != 0.0)
460 return currentTime.beatsPerBar - (isRelative ? 1 : 0);
461
462 return sequence.getTimeSigAt (toPosition (*currentTime.seconds)).numerator - (isRelative ? 1 : 0);
463 }
464
465 const short m[5][4] = { { 999, 59, 59, 48 },
466 { 959, 99, 999, 9999 },
467 { 23, 59, 59, 48 },
468 { 24, 59, 59, 48 },
469 { 29, 59, 59, 48 } };
470
471 return m[static_cast<int> (type)][part];
472}
473
474int TimecodeDisplayFormat::getMinValueOfPart (int part, bool isRelative) const
475{
476 return (type == TimecodeType::barsBeats && part > 0 && ! isRelative) ? 1 : 0;
477}
478
479static juce::String twoCharString (int n)
480{
481 if (n < 10)
482 return juce::String::charToString ('0') + (juce::juce_wchar) (n + '0');
483
484 return juce::String (n);
485}
486
487void TimecodeDisplayFormat::getPartStrings (TimecodeDuration duration,
488 const TempoSequence& tempo,
489 bool isRelative,
490 juce::String results[4]) const
491{
492 if (type == TimecodeType::barsBeats)
493 {
494 tempo::BarsAndBeats barsBeats;
495
496 if (duration.beats.has_value())
497 {
498 auto t = (*duration.beats).inBeats() + nudge.inSeconds();
499
500 barsBeats.bars = int (t / duration.beatsPerBar);
501 barsBeats.beats = BeatDuration::fromBeats (t - (barsBeats.bars * duration.beatsPerBar));
502 }
503 else if (duration.seconds.has_value())
504 {
505 auto time = *duration.seconds;
506
507 if (time < 0s)
508 {
509 time = -time;
510 results[2] = "-";
511 }
512
513 barsBeats = tempo.toBarsAndBeats (toPosition (time + nudge));
514 }
515
516 {
517 auto val = (int) (barsBeats.getFractionalBeats().inBeats() * Edit::ticksPerQuarterNote);
518
519 char text[4];
520 text[0] = (char) ('0' + (val / 100) % 10);
521 text[1] = (char) ('0' + (val / 10) % 10);
522 text[2] = (char) ('0' + val % 10);
523 text[3] = 0;
524 results[0] = text;
525 }
526
527 for (int part = 1; part < 3; ++part)
528 {
529 int val = (part == 1) ? barsBeats.getWholeBeats() : barsBeats.bars;
530
531 if (! isRelative)
532 ++val;
533
534 results[part] << val;
535 }
536 }
537 else if (duration.seconds.has_value())
538 {
539 auto t = (tracktion::abs (*duration.seconds) + nudge).inSeconds();
540
541 if (type == TimecodeType::millisecs)
542 {
543 auto val = ((int) (t * 1000.0)) % 1000;
544
545 char text[4];
546 text[0] = (char) ('0' + (val / 100) % 10);
547 text[1] = (char) ('0' + (val / 10) % 10);
548 text[2] = (char) ('0' + val % 10);
549 text[3] = 0;
550 results[0] = text;
551 }
552 else if (type == TimecodeType::fps24)
553 {
554 results[0] = twoCharString (((int) (t * 24)) % 24);
555 }
556 else if (type == TimecodeType::fps25)
557 {
558 results[0] = twoCharString (((int) (t * 25)) % 25);
559 }
560 else
561 {
562 jassert (type == TimecodeType::fps30);
563 results[0] = twoCharString (((int) (t * 30)) % 30);
564 }
565
566 if (*duration.seconds < 0s)
567 results[3] = "-";
568
569 auto hours = (int) (t * (1.0 / 3600.0));
570 results[3] << twoCharString (hours);
571
572 auto mins = (((int) t) / 60) % 60;
573 results[2] = twoCharString (mins);
574
575 auto secs = (((int) t) % 60);
576 results[1] = twoCharString (secs);
577 }
578 else
579 {
581 }
582}
583
584TimecodeDuration TimecodeDisplayFormat::getNewTimeWithPartValue (TimecodeDuration time, const TempoSequence& tempo,
585 int part, int newValue, bool isRelative) const
586{
587 if (type == TimecodeType::barsBeats)
588 {
589 if (time.beats.has_value())
590 {
591 auto t = (*time.beats).inBeats();
592
593 auto bars = int (t / time.beatsPerBar);
594 auto beats = t - (bars * time.beatsPerBar);
595 auto ticks = std::fmod (beats, 1.0);
596 beats = int (beats);
597
598 if (part == 2) bars = newValue;
599 else if (part == 1) beats = newValue;
600 else if (part == 0) ticks = newValue / double (Edit::ticksPerQuarterNote);
601
602 return TimecodeDuration::fromBeatsOnly (BeatDuration::fromBeats (bars * time.beatsPerBar + beats + ticks), time.beatsPerBar);
603 }
604 else
605 {
606 auto t = toPosition (tracktion::abs (*time.seconds));
607
608 auto pos = createPosition (tempo);
609 pos.set (t);
610
611 auto barsBeats = tempo.toBarsAndBeats (t);
612
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);
616
617 return TimecodeDuration::fromSecondsOnly (toDuration (*time.seconds < 0s ? -pos.getTime() : pos.getTime()));
618 }
619 }
620
621 auto t = tracktion::abs (*time.seconds).inSeconds();
622 auto intT = (int) t;
623 auto hours = (int) (t / 3600.0);
624 auto mins = (intT / 60) % 60;
625 auto secs = (intT % 60);
626 auto frac = t - intT;
627
628 if (part == 0)
629 {
630 auto subSecDivs = getSubSecondDivisions();
631 frac = ((newValue + subSecDivs * 1000) % subSecDivs) / (double) subSecDivs;
632 }
633 else if (part == 1)
634 {
635 secs = (newValue + 3600) % 60;
636 }
637 else if (part == 2)
638 {
639 mins = (newValue + 3600) % 60;
640 }
641 else if (part == 3)
642 {
643 hours = std::max (0, newValue);
644 }
645
646 t = hours * 3600.0 + mins * 60.0 + secs + frac;
647
648 return TimecodeDuration::fromSecondsOnly (TimeDuration::fromSeconds (*time.seconds < 0s ? -t : t));
649}
650
651//==============================================================================
652juce::String TimecodeDisplayFormat::toFullTimecode (TimePosition seconds, int subSecondDivisions, bool showHours)
653{
654 juce::String result = (seconds < 0s) ? "-" : "";
655 auto absSecs = tracktion::abs (seconds).inSeconds();
656
657 if (showHours || (absSecs >= 60.0 * 60.0))
658 result += juce::String::formatted ("%02d:%02d:%02d",
659 (int) (absSecs / (60.0 * 60.0)),
660 ((int) (absSecs / 60.0)) % 60,
661 ((int) absSecs) % 60);
662 else
663 result += juce::String::formatted ("%d:%02d",
664 ((int) (absSecs / 60.0)) % 60,
665 ((int) absSecs) % 60);
666
667 if (subSecondDivisions > 0)
668 result += juce::String::formatted (":%0*d",
669 juce::String (subSecondDivisions - 1).length(),
670 juce::roundToInt (absSecs * subSecondDivisions) % subSecondDivisions);
671
672 return result;
673}
674
675//==============================================================================
676TimecodeSnapType TimecodeDisplayFormat::getBestSnapType (const TempoSetting& tempo,
677 TimeDuration onScreenTimePerPixel,
678 bool isTripletOverride) const
679{
680 if (type >= TimecodeType::fps24 && (1.0 / getSubSecondDivisions()) / onScreenTimePerPixel.inSeconds() > 2)
681 return TimecodeSnapType (type, 1);
682
683 auto numSnapTypes = getNumSnapTypes();
684
685 for (int i = 0; i < numSnapTypes; ++i)
686 {
687 TimecodeSnapType snap (type, i);
688 auto res = snap.getApproxIntervalTime (tempo, isTripletOverride);
689 auto t = res / onScreenTimePerPixel.inSeconds();
690
691 if (t > 12s)
692 return snap;
693 }
694
695 return getSnapType (numSnapTypes);
696}
697
698int TimecodeDisplayFormat::getNumSnapTypes() const
699{
700 return type == TimecodeType::millisecs ? 13
701 : (type == TimecodeType::barsBeats ? 19 : 15);
702}
703
704TimecodeSnapType TimecodeDisplayFormat::getSnapType (int index) const
705{
706 return TimecodeSnapType (type, juce::jlimit (0, getNumSnapTypes() - 1, index));
707}
708
709//==============================================================================
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)
716{
717}
718
720{
721 bool triplets = isTripletOverride || sequence.isTripletsAtTime (time);
722 auto nextTime = std::max (0_tp, minSnapType.roundTimeUp (time + 1.0e-5s, sequence, triplets));
723
724 if (nextTime <= time)
725 {
727 nextTime = time + minSnapType.getApproxIntervalTime (*sequence.getTempo (0), triplets);
728 }
729
730 time = nextTime;
731 currentSnapType = minSnapType.getSnapTypeForMaximumSnapLevelOf (time, sequence, triplets);
732 return time;
733}
734
735juce::String TimecodeDisplayIterator::getTimecodeAsString() const
736{
737 return currentSnapType.getTimecodeString (time, sequence, true);
738}
739
740bool TimecodeDisplayIterator::isOneBarOrGreater() const noexcept
741{
742 return currentSnapType.getLevel() >= currentSnapType.getOneBarLevel();
743}
744
745}} // namespace tracktion { inline namespace engine
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.
T floor(T... args)
T fmod(T... args)
T format(T... args)
#define TRANS(stringLiteral)
#define NEEDS_TRANS(stringLiteral)
#define jassert(expression)
#define jassertfalse
typedef int
typedef double
T max(T... args)
T min(T... args)
wchar_t juce_wchar
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.
time