1#![doc = document_features::document_features!()]
51#![cfg_attr(not(feature = "std"), no_std)]
52
53use core::{
54 convert::{AsMut, AsRef},
55 num::{NonZeroU32, ParseIntError},
56 ops::{Add, Div, Mul, Sub},
57 str::FromStr,
58};
59#[cfg(feature = "facet")]
60use facet::Facet;
61#[cfg(all(feature = "std", doc))]
62use std::time::Duration;
63
64#[cfg(feature = "std")]
65pub mod std_traits;
66
67#[cfg(test)]
68mod tests;
69
70#[cfg(feature = "float_frame_rate")]
71pub type FramesPerSecF32 = typed_floats::StrictlyPositiveFinite<f32>;
72#[cfg(feature = "float_frame_rate")]
73pub type FramesPerSecF64 = typed_floats::StrictlyPositiveFinite<f64>;
74
75pub type FramesPerSec = NonZeroU32;
76
77#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
82#[cfg_attr(feature = "facet", derive(Facet))]
83#[cfg_attr(feature = "facet", facet(opaque))]
84#[cfg_attr(
85 feature = "rkyv",
86 derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
87)]
88#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
89pub struct FrameRate {
90 num: NonZeroU32,
92 den: NonZeroU32,
94}
95
96impl FrameRate {
97 pub const FILM: Self = Self {
99 num: NonZeroU32::new(24).unwrap(),
100 den: NonZeroU32::new(1).unwrap(),
101 };
102 pub const FPS_30: Self = Self {
104 num: NonZeroU32::new(30).unwrap(),
105 den: NonZeroU32::new(1).unwrap(),
106 };
107 pub const FPS_60: Self = Self {
109 num: NonZeroU32::new(60).unwrap(),
110 den: NonZeroU32::new(1).unwrap(),
111 };
112 pub const NTSC: Self = Self {
114 num: NonZeroU32::new(30000).unwrap(),
115 den: NonZeroU32::new(1001).unwrap(),
116 };
117 pub const NTSC_FILM: Self = Self {
119 num: NonZeroU32::new(24000).unwrap(),
120 den: NonZeroU32::new(1001).unwrap(),
121 };
122 pub const NTSC_HIGH: Self = Self {
124 num: NonZeroU32::new(60000).unwrap(),
125 den: NonZeroU32::new(1001).unwrap(),
126 };
127 pub const PAL: Self = Self {
129 num: NonZeroU32::new(25).unwrap(),
130 den: NonZeroU32::new(1).unwrap(),
131 };
132 pub const PAL_HIGH: Self = Self {
134 num: NonZeroU32::new(50).unwrap(),
135 den: NonZeroU32::new(1).unwrap(),
136 };
137
138 #[inline]
148 pub fn new(num: u32, den: u32) -> Option<Self> {
149 Some(Self {
150 num: NonZeroU32::new(num)?,
151 den: NonZeroU32::new(den)?,
152 })
153 }
154
155 #[inline]
157 pub fn from_int(fps: u32) -> Option<Self> {
158 Self::new(fps, 1)
159 }
160
161 #[inline]
163 pub fn num(&self) -> u32 {
164 self.num.get()
165 }
166
167 #[inline]
169 pub fn den(&self) -> u32 {
170 self.den.get()
171 }
172}
173
174impl From<NonZeroU32> for FrameRate {
175 fn from(fps: NonZeroU32) -> Self {
176 Self {
177 num: fps,
178 den: unsafe { NonZeroU32::new_unchecked(1) },
179 }
180 }
181}
182
183#[cfg(not(feature = "low_res"))]
187pub const TICKS_PER_SECOND: i64 = 3_603_600;
188#[cfg(feature = "low_res")]
192pub const TICKS_PER_SECOND: i64 = 25_200;
193
194#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)]
200#[cfg_attr(feature = "facet", derive(Facet))]
201#[cfg_attr(
202 feature = "rkyv",
203 derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize),
204 rkyv(attr(derive(
205 Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash
206 )))
207)]
208#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
209pub struct Tick(i64);
210
211impl IntoIterator for Tick {
212 type IntoIter = TickIter;
213 type Item = i64;
214
215 fn into_iter(self) -> Self::IntoIter {
216 TickIter(self.0)
217 }
218}
219
220#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
222#[cfg_attr(feature = "facet", derive(Facet))]
223pub struct TickIter(i64);
224
225impl Iterator for TickIter {
226 type Item = i64;
227
228 fn next(&mut self) -> Option<Self::Item> {
229 if i64::MAX == self.0 {
230 None
231 } else {
232 let value = self.0;
233 self.0 += 1;
234
235 Some(value)
236 }
237 }
238}
239
240impl DoubleEndedIterator for TickIter {
241 fn next_back(&mut self) -> Option<Self::Item> {
242 if i64::MIN == self.0 {
243 None
244 } else {
245 self.0 -= 1;
246 Some(self.0)
247 }
248 }
249}
250
251#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
253#[cfg_attr(feature = "facet", derive(Facet))]
254pub struct TickRevIter(i64);
255
256impl Iterator for TickRevIter {
257 type Item = i64;
258
259 fn next(&mut self) -> Option<Self::Item> {
260 let value = self.0;
261 self.0 -= 1;
262 if i64::MIN == self.0 {
263 None
264 } else {
265 Some(value)
266 }
267 }
268}
269
270impl AsRef<i64> for Tick {
271 fn as_ref(&self) -> &i64 {
272 &self.0
273 }
274}
275
276impl AsMut<i64> for Tick {
277 fn as_mut(&mut self) -> &mut i64 {
278 &mut self.0
279 }
280}
281
282macro_rules! impl_tick_from {
283 ($ty:ty) => {
284 impl From<$ty> for Tick {
285 fn from(value: $ty) -> Self {
286 Self(value as _)
287 }
288 }
289 };
290}
291
292macro_rules! impl_from_tick {
293 ($ty:ty) => {
294 impl From<Tick> for $ty {
295 fn from(tick: Tick) -> Self {
296 tick.0 as _
297 }
298 }
299
300 impl From<&Tick> for $ty {
301 fn from(tick: &Tick) -> Self {
302 tick.0 as _
303 }
304 }
305 };
306}
307
308impl_from_tick!(u64);
309impl_from_tick!(u128);
310impl_from_tick!(usize);
311impl_from_tick!(i64);
312impl_from_tick!(i128);
313impl_from_tick!(isize);
314impl_from_tick!(f32);
315impl_from_tick!(f64);
316
317impl_tick_from!(u8);
318impl_tick_from!(u16);
319impl_tick_from!(u32);
320impl_tick_from!(i8);
321impl_tick_from!(i16);
322impl_tick_from!(i32);
323impl_tick_from!(i64);
324
325macro_rules! round {
326 ($ty:ty, $value:expr) => {{
327 let value = $value;
328 #[cfg(feature = "std")]
329 let value = value.round();
330 #[cfg(not(feature = "std"))]
331 let value = if value >= 0.0 {
332 value + 0.5
333 } else {
334 value - 0.5
335 };
336
337 value as i64
338 }};
339}
340
341impl From<f32> for Tick {
342 fn from(value: f32) -> Self {
343 Self(round!(f32, value))
344 }
345}
346
347impl From<f64> for Tick {
348 fn from(value: f64) -> Self {
349 Self(round!(f64, value))
350 }
351}
352
353impl FromStr for Tick {
354 type Err = ParseIntError;
355
356 fn from_str(s: &str) -> Result<Self, Self::Err> {
357 s.parse::<i64>().map(Tick)
358 }
359}
360
361impl Add for Tick {
362 type Output = Tick;
363
364 fn add(self, rhs: Self) -> Self::Output {
365 Tick(self.0 + rhs.0)
366 }
367}
368
369impl Sub for Tick {
370 type Output = Tick;
371
372 fn sub(self, rhs: Self) -> Self::Output {
373 Tick(self.0 - rhs.0)
374 }
375}
376
377macro_rules! impl_mul_div_float {
378 ($ty:ty) => {
379 impl Mul<$ty> for Tick {
380 type Output = Self;
381
382 fn mul(self, rhs: $ty) -> Self::Output {
383 let value = self.0 as $ty * rhs;
384 Self(round!($ty, value))
385 }
386 }
387
388 impl Div<$ty> for Tick {
389 type Output = Self;
390
391 fn div(self, rhs: $ty) -> Self::Output {
392 let value = self.0 as $ty / rhs;
393 Self(round!($ty, value))
394 }
395 }
396 };
397}
398
399impl_mul_div_float!(f32);
400impl_mul_div_float!(f64);
401
402macro_rules! impl_mul_div_int {
403 ($ty:ty) => {
404 impl Mul<$ty> for Tick {
405 type Output = Self;
406
407 fn mul(self, rhs: $ty) -> Self::Output {
408 Self((self.0 as $ty * rhs) as _)
409 }
410 }
411
412 impl Div<$ty> for Tick {
413 type Output = Self;
414
415 fn div(self, rhs: $ty) -> Self::Output {
416 Self((self.0 as $ty / rhs) as _)
417 }
418 }
419 };
420}
421
422impl_mul_div_int!(u8);
423impl_mul_div_int!(u16);
424impl_mul_div_int!(u32);
425impl_mul_div_int!(u64);
426impl_mul_div_int!(u128);
427impl_mul_div_int!(usize);
428impl_mul_div_int!(i8);
429impl_mul_div_int!(i16);
430impl_mul_div_int!(i32);
431impl_mul_div_int!(i64);
432impl_mul_div_int!(i128);
433impl_mul_div_int!(isize);
434
435impl Tick {
436 #[inline]
437 pub fn new(value: i64) -> Self {
438 Self(value)
439 }
440
441 #[inline]
443 pub fn from_secs(secs: f64) -> Self {
444 Self((secs * TICKS_PER_SECOND as f64) as i64)
445 }
446
447 #[inline]
449 pub fn to_secs(&self) -> f64 {
450 self.0 as f64 / TICKS_PER_SECOND as f64
451 }
452
453 #[inline]
455 pub fn lerp(self, other: Self, t: f64) -> Self {
456 let t = t.clamp(0.0, 1.0);
457 Self(round!(f64, self.0 as f64 * (1.0 - t) + other.0 as f64 * t))
458 }
459
460 #[inline]
465 pub fn to_timecode(self, frame_rate: FrameRate) -> (i64, i64, i64, i64) {
466 let divisor = TICKS_PER_SECOND as i128 * frame_rate.den() as i128;
469 let total_frames = ((self.0 as i128 * frame_rate.num() as i128
470 + divisor / 2)
471 / divisor) as i64;
472
473 let nominal_fps = (frame_rate.num() as i64 + frame_rate.den() as i64
475 - 1)
476 / frame_rate.den() as i64;
477
478 let frames = total_frames % nominal_fps;
479 let total_seconds = total_frames / nominal_fps;
480 let seconds = total_seconds % 60;
481 let total_minutes = total_seconds / 60;
482 let minutes = total_minutes % 60;
483 let hours = total_minutes / 60;
484
485 (hours, minutes, seconds, frames)
486 }
487
488 #[inline]
491 pub fn from_timecode(
492 hours: i64,
493 minutes: i64,
494 seconds: i64,
495 frames: i64,
496 frame_rate: FrameRate,
497 ) -> Self {
498 let nominal_fps = (frame_rate.num() as i64 + frame_rate.den() as i64
500 - 1)
501 / frame_rate.den() as i64;
502
503 let total_frames = hours * 3600 * nominal_fps
504 + minutes * 60 * nominal_fps
505 + seconds * nominal_fps
506 + frames;
507
508 Self(
512 (total_frames as i128
513 * TICKS_PER_SECOND as i128
514 * frame_rate.den() as i128
515 / frame_rate.num() as i128) as i64,
516 )
517 }
518}
519
520pub trait FrameRateConversion<T> {
522 fn to_frames(self, frame_rate: T) -> i64;
523 fn from_frames(frames: i64, frame_rate: T) -> Self;
524}
525
526impl FrameRateConversion<FramesPerSec> for Tick {
527 fn to_frames(self, frame_rate: FramesPerSec) -> i64 {
529 (self.0 as i128 * frame_rate.get() as i128 / TICKS_PER_SECOND as i128)
530 as _
531 }
532
533 fn from_frames(frames: i64, frame_rate: FramesPerSec) -> Self {
535 Self(
536 (frames as i128 * TICKS_PER_SECOND as i128
537 / frame_rate.get() as i128) as _,
538 )
539 }
540}
541
542#[cfg(feature = "float_frame_rate")]
543impl FrameRateConversion<FramesPerSecF32> for Tick {
544 fn to_frames(self, frame_rate: FramesPerSecF32) -> i64 {
547 (self.0 as f64 * frame_rate.get() as f64 / TICKS_PER_SECOND as f64)
548 .round() as _
549 }
550
551 fn from_frames(frames: i64, frame_rate: FramesPerSecF32) -> Self {
554 Self(
555 (frames as f64 * TICKS_PER_SECOND as f64 / frame_rate.get() as f64)
556 .round() as _,
557 )
558 }
559}
560
561#[cfg(feature = "float_frame_rate")]
562impl FrameRateConversion<FramesPerSecF64> for Tick {
563 fn to_frames(self, frame_rate: FramesPerSecF64) -> i64 {
566 (self.0 as f64 * frame_rate.get() / TICKS_PER_SECOND as f64).round()
567 as _
568 }
569
570 fn from_frames(frames: i64, frame_rate: FramesPerSecF64) -> Self {
573 Self(
574 (frames as f64 * TICKS_PER_SECOND as f64 / frame_rate.get()).round()
575 as _,
576 )
577 }
578}