1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9#[allow(dead_code)]
10pub enum MtcFrameRate {
11 Fps24,
13 Fps25,
15 Fps2997,
17 Fps30,
19}
20
21impl MtcFrameRate {
22 #[must_use]
29 pub fn code(&self) -> u8 {
30 match self {
31 MtcFrameRate::Fps24 => 0,
32 MtcFrameRate::Fps25 => 1,
33 MtcFrameRate::Fps2997 => 2,
34 MtcFrameRate::Fps30 => 3,
35 }
36 }
37
38 #[must_use]
40 pub fn frames_per_sec(&self) -> f32 {
41 match self {
42 MtcFrameRate::Fps24 => 24.0,
43 MtcFrameRate::Fps25 => 25.0,
44 MtcFrameRate::Fps2997 => 29.97,
45 MtcFrameRate::Fps30 => 30.0,
46 }
47 }
48
49 #[must_use]
51 pub fn frames_per_sec_int(&self) -> u8 {
52 match self {
53 MtcFrameRate::Fps24 => 24,
54 MtcFrameRate::Fps25 => 25,
55 MtcFrameRate::Fps2997 => 30, MtcFrameRate::Fps30 => 30,
57 }
58 }
59
60 #[must_use]
62 pub fn from_code(code: u8) -> Option<Self> {
63 match code & 0x03 {
64 0 => Some(MtcFrameRate::Fps24),
65 1 => Some(MtcFrameRate::Fps25),
66 2 => Some(MtcFrameRate::Fps2997),
67 3 => Some(MtcFrameRate::Fps30),
68 _ => None,
69 }
70 }
71}
72
73#[derive(Debug, Clone, Copy, PartialEq, Eq)]
75#[allow(dead_code)]
76pub struct MtcTimecode {
77 pub hours: u8,
79 pub minutes: u8,
81 pub seconds: u8,
83 pub frames: u8,
85 pub frame_rate: MtcFrameRate,
87}
88
89impl MtcTimecode {
90 #[must_use]
92 pub fn new(hours: u8, minutes: u8, seconds: u8, frames: u8, frame_rate: MtcFrameRate) -> Self {
93 Self {
94 hours: hours.min(23),
95 minutes: minutes.min(59),
96 seconds: seconds.min(59),
97 frames: frames.min(frame_rate.frames_per_sec_int() - 1),
98 frame_rate,
99 }
100 }
101
102 #[must_use]
104 pub fn to_frame_count(&self) -> u64 {
105 let fps = u64::from(self.frame_rate.frames_per_sec_int());
106 let hours = u64::from(self.hours);
107 let minutes = u64::from(self.minutes);
108 let seconds = u64::from(self.seconds);
109 let frames = u64::from(self.frames);
110
111 hours * 3600 * fps + minutes * 60 * fps + seconds * fps + frames
112 }
113
114 #[must_use]
116 pub fn from_frame_count(frame_count: u64, rate: MtcFrameRate) -> Self {
117 let fps = u64::from(rate.frames_per_sec_int());
118 let total_seconds = frame_count / fps;
119 let frames = (frame_count % fps) as u8;
120 let seconds = (total_seconds % 60) as u8;
121 let total_minutes = total_seconds / 60;
122 let minutes = (total_minutes % 60) as u8;
123 let hours = ((total_minutes / 60) % 24) as u8;
124
125 Self::new(hours, minutes, seconds, frames, rate)
126 }
127}
128
129#[allow(dead_code)]
134pub struct MtcFullFrame;
135
136impl MtcFullFrame {
137 #[must_use]
142 pub fn encode(tc: &MtcTimecode) -> Vec<u8> {
143 let rate_code = tc.frame_rate.code();
144 let hh = (rate_code << 5) | (tc.hours & 0x1F);
145
146 vec![
147 0xF0, 0x7F, 0x7F, 0x01, 0x01, hh, tc.minutes, tc.seconds, tc.frames, 0xF7, ]
154 }
155
156 #[must_use]
160 pub fn decode(data: &[u8]) -> Option<MtcTimecode> {
161 if data.len() < 10 {
162 return None;
163 }
164 if data[0] != 0xF0
165 || data[1] != 0x7F
166 || data[2] != 0x7F
167 || data[3] != 0x01
168 || data[4] != 0x01
169 || data[9] != 0xF7
170 {
171 return None;
172 }
173
174 let hh = data[5];
175 let rate_code = (hh >> 5) & 0x03;
176 let hours = hh & 0x1F;
177 let minutes = data[6];
178 let seconds = data[7];
179 let frames = data[8];
180
181 let frame_rate = MtcFrameRate::from_code(rate_code)?;
182
183 Some(MtcTimecode::new(
184 hours, minutes, seconds, frames, frame_rate,
185 ))
186 }
187}
188
189#[allow(dead_code)]
194pub struct MtcQuarterFrame;
195
196impl MtcQuarterFrame {
197 #[must_use]
214 pub fn encode_quarter(tc: &MtcTimecode, piece: u8) -> u8 {
215 let piece = piece & 0x07;
216 let data: u8 = match piece {
217 0 => tc.frames & 0x0F,
218 1 => (tc.frames >> 4) & 0x01,
219 2 => tc.seconds & 0x0F,
220 3 => (tc.seconds >> 4) & 0x03,
221 4 => tc.minutes & 0x0F,
222 5 => (tc.minutes >> 4) & 0x03,
223 6 => tc.hours & 0x0F,
224 7 => ((tc.frame_rate.code() & 0x03) << 1) | ((tc.hours >> 4) & 0x01),
225 _ => 0,
226 };
227 (piece << 4) | (data & 0x0F)
228 }
229}
230
231#[derive(Debug)]
233#[allow(dead_code)]
234pub struct MtcReceiver {
235 nibbles: [u8; 8],
237 count: usize,
239 complete: bool,
241}
242
243impl MtcReceiver {
244 #[must_use]
246 pub fn new() -> Self {
247 Self {
248 nibbles: [0u8; 8],
249 count: 0,
250 complete: false,
251 }
252 }
253
254 pub fn process_message(&mut self, msg: u8) -> Option<MtcTimecode> {
259 let piece = (msg >> 4) & 0x07;
260 let data = msg & 0x0F;
261
262 self.nibbles[piece as usize] = data;
263 self.count += 1;
264
265 if self.count >= 8 {
267 self.complete = true;
268 self.count = 0;
269 return self.assemble();
270 }
271
272 None
273 }
274
275 fn assemble(&self) -> Option<MtcTimecode> {
277 let frames = self.nibbles[0] | (self.nibbles[1] << 4);
278 let seconds = self.nibbles[2] | (self.nibbles[3] << 4);
279 let minutes = self.nibbles[4] | (self.nibbles[5] << 4);
280 let hours = self.nibbles[6] | ((self.nibbles[7] & 0x01) << 4);
281 let rate_code = (self.nibbles[7] >> 1) & 0x03;
282
283 let frame_rate = MtcFrameRate::from_code(rate_code)?;
284 Some(MtcTimecode::new(
285 hours, minutes, seconds, frames, frame_rate,
286 ))
287 }
288
289 pub fn reset(&mut self) {
291 self.nibbles = [0u8; 8];
292 self.count = 0;
293 self.complete = false;
294 }
295
296 #[must_use]
298 pub fn is_complete(&self) -> bool {
299 self.complete
300 }
301}
302
303impl Default for MtcReceiver {
304 fn default() -> Self {
305 Self::new()
306 }
307}
308
309#[cfg(test)]
310mod tests {
311 use super::*;
312
313 #[test]
314 fn test_mtc_frame_rate_code() {
315 assert_eq!(MtcFrameRate::Fps24.code(), 0);
316 assert_eq!(MtcFrameRate::Fps25.code(), 1);
317 assert_eq!(MtcFrameRate::Fps2997.code(), 2);
318 assert_eq!(MtcFrameRate::Fps30.code(), 3);
319 }
320
321 #[test]
322 fn test_mtc_frame_rate_from_code() {
323 assert_eq!(MtcFrameRate::from_code(0), Some(MtcFrameRate::Fps24));
324 assert_eq!(MtcFrameRate::from_code(1), Some(MtcFrameRate::Fps25));
325 assert_eq!(MtcFrameRate::from_code(2), Some(MtcFrameRate::Fps2997));
326 assert_eq!(MtcFrameRate::from_code(3), Some(MtcFrameRate::Fps30));
327 }
328
329 #[test]
330 fn test_mtc_frame_rate_fps() {
331 assert!((MtcFrameRate::Fps24.frames_per_sec() - 24.0).abs() < f32::EPSILON);
332 assert!((MtcFrameRate::Fps25.frames_per_sec() - 25.0).abs() < f32::EPSILON);
333 assert!((MtcFrameRate::Fps2997.frames_per_sec() - 29.97).abs() < 0.001);
334 assert!((MtcFrameRate::Fps30.frames_per_sec() - 30.0).abs() < f32::EPSILON);
335 }
336
337 #[test]
338 fn test_mtc_timecode_new() {
339 let tc = MtcTimecode::new(1, 2, 3, 4, MtcFrameRate::Fps25);
340 assert_eq!(tc.hours, 1);
341 assert_eq!(tc.minutes, 2);
342 assert_eq!(tc.seconds, 3);
343 assert_eq!(tc.frames, 4);
344 }
345
346 #[test]
347 fn test_mtc_timecode_to_frame_count() {
348 let tc = MtcTimecode::new(0, 0, 1, 0, MtcFrameRate::Fps25);
349 assert_eq!(tc.to_frame_count(), 25);
350
351 let tc2 = MtcTimecode::new(1, 0, 0, 0, MtcFrameRate::Fps30);
352 assert_eq!(tc2.to_frame_count(), 3600 * 30);
353 }
354
355 #[test]
356 fn test_mtc_timecode_from_frame_count() {
357 let tc = MtcTimecode::from_frame_count(3600 * 25, MtcFrameRate::Fps25);
358 assert_eq!(tc.hours, 1);
359 assert_eq!(tc.minutes, 0);
360 assert_eq!(tc.seconds, 0);
361 assert_eq!(tc.frames, 0);
362 }
363
364 #[test]
365 fn test_mtc_timecode_roundtrip() {
366 let original = MtcTimecode::new(1, 30, 45, 12, MtcFrameRate::Fps25);
367 let frames = original.to_frame_count();
368 let recovered = MtcTimecode::from_frame_count(frames, MtcFrameRate::Fps25);
369 assert_eq!(original, recovered);
370 }
371
372 #[test]
373 fn test_mtc_full_frame_encode() {
374 let tc = MtcTimecode::new(1, 2, 3, 4, MtcFrameRate::Fps25);
375 let data = MtcFullFrame::encode(&tc);
376 assert_eq!(data.len(), 10);
377 assert_eq!(data[0], 0xF0);
378 assert_eq!(data[1], 0x7F);
379 assert_eq!(data[2], 0x7F);
380 assert_eq!(data[3], 0x01);
381 assert_eq!(data[4], 0x01);
382 assert_eq!(data[5], (1 << 5) | 1);
384 assert_eq!(data[6], 2);
385 assert_eq!(data[7], 3);
386 assert_eq!(data[8], 4);
387 assert_eq!(data[9], 0xF7);
388 }
389
390 #[test]
391 fn test_mtc_full_frame_decode() {
392 let tc = MtcTimecode::new(2, 10, 30, 15, MtcFrameRate::Fps30);
393 let encoded = MtcFullFrame::encode(&tc);
394 let decoded = MtcFullFrame::decode(&encoded).expect("should succeed");
395 assert_eq!(decoded.hours, 2);
396 assert_eq!(decoded.minutes, 10);
397 assert_eq!(decoded.seconds, 30);
398 assert_eq!(decoded.frames, 15);
399 assert_eq!(decoded.frame_rate, MtcFrameRate::Fps30);
400 }
401
402 #[test]
403 fn test_mtc_full_frame_decode_invalid() {
404 assert!(MtcFullFrame::decode(&[]).is_none());
405 assert!(MtcFullFrame::decode(&[0xF0, 0x00, 0x7F, 0x01, 0x01, 0, 0, 0, 0, 0xF7]).is_none());
406 }
407
408 #[test]
409 fn test_mtc_receiver_assemble() {
410 let tc = MtcTimecode::new(1, 2, 3, 4, MtcFrameRate::Fps25);
411 let mut receiver = MtcReceiver::new();
412
413 let mut result = None;
415 for piece in 0..8u8 {
416 let byte = MtcQuarterFrame::encode_quarter(&tc, piece);
417 result = receiver.process_message(byte);
418 }
419
420 let decoded = result.expect("result should be ok");
421 assert_eq!(decoded.hours, tc.hours);
422 assert_eq!(decoded.minutes, tc.minutes);
423 assert_eq!(decoded.seconds, tc.seconds);
424 assert_eq!(decoded.frames, tc.frames);
425 assert_eq!(decoded.frame_rate, tc.frame_rate);
426 }
427
428 #[test]
429 fn test_mtc_receiver_reset() {
430 let mut receiver = MtcReceiver::new();
431 receiver.process_message(0x00);
432 receiver.reset();
433 assert!(!receiver.is_complete());
434 }
435
436 #[test]
437 fn test_mtc_receiver_default() {
438 let receiver = MtcReceiver::default();
439 assert!(!receiver.is_complete());
440 }
441}