Add operator<= for HMSF.
[dcpomatic.git] / src / lib / dcpomatic_time.h
1 /*
2     Copyright (C) 2014-2021 Carl Hetherington <cth@carlh.net>
3
4     This file is part of DCP-o-matic.
5
6     DCP-o-matic is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10
11     DCP-o-matic is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15
16     You should have received a copy of the GNU General Public License
17     along with DCP-o-matic.  If not, see <http://www.gnu.org/licenses/>.
18
19 */
20
21
22 /** @file  src/lib/dcpomatic_time.h
23  *  @brief Types to describe time.
24  */
25
26
27 #ifndef DCPOMATIC_TIME_H
28 #define DCPOMATIC_TIME_H
29
30
31 #include "frame_rate_change.h"
32 #include "dcpomatic_assert.h"
33 #include <boost/optional.hpp>
34 #include <stdint.h>
35 #include <cmath>
36 #include <ostream>
37 #include <iomanip>
38 #include <cstdio>
39
40
41 struct dcpomatic_time_ceil_test;
42 struct dcpomatic_time_floor_test;
43
44
45 namespace dcpomatic {
46
47
48 class HMSF
49 {
50 public:
51         HMSF () {}
52
53         HMSF (int h_, int m_, int s_, int f_)
54                 : h(h_)
55                 , m(m_)
56                 , s(s_)
57                 , f(f_)
58         {}
59
60         int h = 0;
61         int m = 0;
62         int s = 0;
63         int f = 0;
64 };
65
66
67 bool operator<=(HMSF const& a, HMSF const& b);
68
69
70 /** A time in seconds, expressed as a number scaled up by Time::HZ.  We want two different
71  *  versions of this class, dcpomatic::ContentTime and dcpomatic::DCPTime, and we want it to be impossible to
72  *  convert implicitly between the two.  Hence there's this template hack.  I'm not
73  *  sure if it's the best way to do it.
74  *
75  *  S is the name of `this' class and O is its opposite (see the typedefs below).
76  */
77 template <class S, class O>
78 class Time
79 {
80 public:
81         Time ()
82                 : _t (0)
83         {}
84
85         typedef int64_t Type;
86
87         explicit Time (Type t)
88                 : _t (t)
89         {}
90
91         explicit Time (Type n, Type d)
92                 : _t (n * HZ / d)
93         {}
94
95         /* Explicit conversion from type O */
96         Time (Time<O, S> d, FrameRateChange f);
97
98         /** @param hmsf Hours, minutes, seconds, frames.
99          *  @param fps Frame rate
100          */
101         Time (HMSF const& hmsf, float fps) {
102                 *this = from_seconds (hmsf.h * 3600)
103                         + from_seconds (hmsf.m * 60)
104                         + from_seconds (hmsf.s)
105                         + from_frames (hmsf.f, fps);
106         }
107
108         Type get () const {
109                 return _t;
110         }
111
112         bool operator< (Time<S, O> const & o) const {
113                 return _t < o._t;
114         }
115
116         bool operator<= (Time<S, O> const & o) const {
117                 return _t <= o._t;
118         }
119
120         bool operator== (Time<S, O> const & o) const {
121                 return _t == o._t;
122         }
123
124         bool operator!= (Time<S, O> const & o) const {
125                 return _t != o._t;
126         }
127
128         bool operator>= (Time<S, O> const & o) const {
129                 return _t >= o._t;
130         }
131
132         bool operator> (Time<S, O> const & o) const {
133                 return _t > o._t;
134         }
135
136         Time<S, O> operator+ (Time<S, O> const & o) const {
137                 return Time<S, O> (_t + o._t);
138         }
139
140         Time<S, O> & operator+= (Time<S, O> const & o) {
141                 _t += o._t;
142                 return *this;
143         }
144
145         Time<S, O> operator- () const {
146                 return Time<S, O> (-_t);
147         }
148
149         Time<S, O> operator- (Time<S, O> const & o) const {
150                 return Time<S, O> (_t - o._t);
151         }
152
153         Time<S, O> & operator-= (Time<S, O> const & o) {
154                 _t -= o._t;
155                 return *this;
156         }
157
158         Time<S, O> operator* (int o) const {
159                 return Time<S, O> (_t * o);
160         }
161
162         Time<S, O> operator/ (int o) const {
163                 return Time<S, O> (_t / o);
164         }
165
166         /** Round up to the nearest sampling interval
167          *  at some sampling rate.
168          *  @param r Sampling rate.
169          */
170         Time<S, O> ceil (double r) const {
171                 return Time<S, O> (llrint(HZ * frames_ceil(r) / r));
172         }
173
174         Time<S, O> floor (double r) const {
175                 return Time<S, O> (llrint(HZ * frames_floor(r) / r));
176         }
177
178         Time<S, O> round (double r) const {
179                 return Time<S, O> (llrint(HZ * frames_round(r) / r));
180         }
181
182         double seconds () const {
183                 return double (_t) / HZ;
184         }
185
186         Time<S, O> abs () const {
187                 return Time<S, O> (std::abs(_t));
188         }
189
190         template <typename T>
191         int64_t frames_round (T r) const {
192                 /* We must cast to double here otherwise if T is integer
193                    the calculation will round down before we get the chance
194                    to llrint().
195                 */
196                 return llrint (_t * double(r) / HZ);
197         }
198
199         template <typename T>
200         int64_t frames_floor (T r) const {
201                 return ::floor (_t * r / HZ);
202         }
203
204         template <typename T>
205         int64_t frames_ceil (T r) const {
206                 /* We must cast to double here otherwise if T is integer
207                    the calculation will round down before we get the chance
208                    to ceil().
209                 */
210                 return ::ceil (_t * double(r) / HZ);
211         }
212
213         /** Split a time into hours, minutes, seconds and frames.
214          *  @param r Frames per second.
215          *  @return Split time.
216          */
217         template <typename T>
218         HMSF split (T r) const
219         {
220                 /* Do this calculation with frames so that we can round
221                    to a frame boundary at the start rather than the end.
222                 */
223                 auto ff = frames_round (r);
224                 HMSF hmsf;
225
226                 hmsf.h = ff / (3600 * r);
227                 ff -= static_cast<int64_t>(hmsf.h) * 3600 * r;
228                 hmsf.m = ff / (60 * r);
229                 ff -= static_cast<int64_t>(hmsf.m) * 60 * r;
230                 hmsf.s = ff / r;
231                 ff -= static_cast<int64_t>(hmsf.s) * r;
232
233                 hmsf.f = static_cast<int> (ff);
234                 return hmsf;
235         }
236
237         template <typename T>
238         std::string timecode (T r) const {
239                 auto hmsf = split (r);
240
241                 char buffer[128];
242                 snprintf (buffer, sizeof(buffer), "%02d:%02d:%02d:%02d", hmsf.h, hmsf.m, hmsf.s, hmsf.f);
243                 return buffer;
244         }
245
246         static Time<S, O> from_seconds (double s) {
247                 return Time<S, O> (llrint (s * HZ));
248         }
249
250         template <class T>
251         static Time<S, O> from_frames (int64_t f, T r) {
252                 DCPOMATIC_ASSERT (r > 0);
253                 return Time<S, O> (f * HZ / r);
254         }
255
256         static Time<S, O> delta () {
257                 return Time<S, O> (1);
258         }
259
260         static Time<S, O> min () {
261                 return Time<S, O> (-INT64_MAX);
262         }
263
264         static Time<S, O> max () {
265                 return Time<S, O> (INT64_MAX);
266         }
267
268         static const int HZ = 96000;
269
270 private:
271         friend struct ::dcpomatic_time_ceil_test;
272         friend struct ::dcpomatic_time_floor_test;
273
274         Type _t;
275 };
276
277
278 class ContentTimeDifferentiator {};
279 class DCPTimeDifferentiator {};
280
281
282 /* Specializations for the two allowed explicit conversions */
283
284 template<>
285 Time<ContentTimeDifferentiator, DCPTimeDifferentiator>::Time (Time<DCPTimeDifferentiator, ContentTimeDifferentiator> d, FrameRateChange f);
286
287 template<>
288 Time<DCPTimeDifferentiator, ContentTimeDifferentiator>::Time (Time<ContentTimeDifferentiator, DCPTimeDifferentiator> d, FrameRateChange f);
289
290
291 /** Time relative to the start or position of a piece of content in its native frame rate */
292 typedef Time<ContentTimeDifferentiator, DCPTimeDifferentiator> ContentTime;
293 /** Time relative to the start of the output DCP in its frame rate */
294 typedef Time<DCPTimeDifferentiator, ContentTimeDifferentiator> DCPTime;
295
296 template <class T>
297 class TimePeriod
298 {
299 public:
300         TimePeriod () {}
301
302         TimePeriod (T f, T t)
303                 : from (f)
304                 , to (t)
305         {}
306
307         /** start time of sampling interval that the period is from */
308         T from;
309         /** start time of next sampling interval after the period */
310         T to;
311
312         T duration () const {
313                 return to - from;
314         }
315
316         TimePeriod<T> operator+ (T const & o) const {
317                 return TimePeriod<T> (from + o, to + o);
318         }
319
320         boost::optional<TimePeriod<T>> overlap (TimePeriod<T> const & other) const {
321                 T const max_from = std::max (from, other.from);
322                 T const min_to = std::min (to, other.to);
323
324                 if (max_from >= min_to) {
325                         return {};
326                 }
327
328                 return TimePeriod<T> (max_from, min_to);
329         }
330
331         bool contains (T const & other) const {
332                 return (from <= other && other < to);
333         }
334
335         bool operator< (TimePeriod<T> const & o) const {
336                 if (from != o.from) {
337                         return from < o.from;
338                 }
339                 return to < o.to;
340         }
341
342         bool operator== (TimePeriod<T> const & other) const {
343                 return from == other.from && to == other.to;
344         }
345
346         bool operator!= (TimePeriod<T> const & other) const {
347                 return !(*this == other);
348         }
349 };
350
351
352 /** @param A Period which is subtracted from.
353  *  @param B Periods to subtract from `A', must be in ascending order of start time and must not overlap.
354  */
355 template <class T>
356 std::list<TimePeriod<T>> subtract (TimePeriod<T> A, std::list<TimePeriod<T>> const & B)
357 {
358         std::list<TimePeriod<T>> result;
359         result.push_back (A);
360
361         for (auto i: B) {
362                 std::list<TimePeriod<T>> new_result;
363                 for (auto j: result) {
364                         auto ov = i.overlap (j);
365                         if (ov) {
366                                 if (*ov == i) {
367                                         /* A contains all of B */
368                                         if (i.from != j.from) {
369                                                 new_result.push_back (TimePeriod<T>(j.from, i.from));
370                                         }
371                                         if (i.to != j.to) {
372                                                 new_result.push_back (TimePeriod<T>(i.to, j.to));
373                                         }
374                                 } else if (*ov == j) {
375                                         /* B contains all of A */
376                                 } else if (i.from < j.from) {
377                                         /* B overlaps start of A */
378                                         new_result.push_back (TimePeriod<T>(i.to, j.to));
379                                 } else if (i.to > j.to) {
380                                         /* B overlaps end of A */
381                                         new_result.push_back (TimePeriod<T>(j.from, i.from));
382                                 }
383                         } else {
384                                 new_result.push_back (j);
385                         }
386                 }
387                 result = new_result;
388         }
389
390         return result;
391 }
392
393
394 typedef TimePeriod<ContentTime> ContentTimePeriod;
395 typedef TimePeriod<DCPTime> DCPTimePeriod;
396
397
398 DCPTime min (DCPTime a, DCPTime b);
399 DCPTime max (DCPTime a, DCPTime b);
400 ContentTime min (ContentTime a, ContentTime b);
401 ContentTime max (ContentTime a, ContentTime b);
402 std::string to_string (ContentTime t);
403 std::string to_string (DCPTime t);
404 std::string to_string (DCPTimePeriod p);
405
406
407 }
408
409
410 #endif