1#![allow(dead_code)]
23#![allow(clippy::cast_precision_loss)]
24
25use crate::{FrameRateInfo, Timecode, TimecodeError};
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub enum LtcBit {
32 Zero,
34 One,
36}
37
38impl LtcBit {
39 pub fn as_u8(self) -> u8 {
41 match self {
42 Self::Zero => 0,
43 Self::One => 1,
44 }
45 }
46}
47
48impl From<bool> for LtcBit {
49 fn from(b: bool) -> Self {
50 if b {
51 Self::One
52 } else {
53 Self::Zero
54 }
55 }
56}
57
58impl From<u8> for LtcBit {
59 fn from(v: u8) -> Self {
60 if v != 0 {
61 Self::One
62 } else {
63 Self::Zero
64 }
65 }
66}
67
68#[derive(Debug, Clone, PartialEq, Eq)]
72pub struct LtcFrame {
73 pub timecode: Timecode,
75 pub user_bits: u32,
77 pub drop_frame: bool,
79 pub color_frame: bool,
81 pub biphase_polarity: bool,
83 pub bit_offset: usize,
85}
86
87#[derive(Debug, Clone)]
105pub struct LtcParser {
106 pub fps: u8,
108 pub default_drop_frame: bool,
110}
111
112impl LtcParser {
113 const SYNC_BITS: [u8; 16] = [0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1];
116
117 pub fn new(fps: u8, default_drop_frame: bool) -> Self {
122 Self {
123 fps,
124 default_drop_frame,
125 }
126 }
127
128 pub fn decode_bits(&self, bits: &[LtcBit]) -> Vec<LtcFrame> {
133 if bits.len() < 80 {
134 return Vec::new();
135 }
136 let mut frames = Vec::new();
137 let mut i = 0;
138 while i + 80 <= bits.len() {
139 if self.check_sync(bits, i + 64) {
141 if let Ok(frame) = self.decode_frame(bits, i) {
142 frames.push(frame);
143 i += 80;
144 continue;
145 }
146 }
147 i += 1;
148 }
149 frames
150 }
151
152 pub fn decode_frame(&self, bits: &[LtcBit], offset: usize) -> Result<LtcFrame, TimecodeError> {
154 if offset + 80 > bits.len() {
155 return Err(TimecodeError::BufferTooSmall);
156 }
157
158 let word = &bits[offset..offset + 80];
159
160 let nibble = |positions: [usize; 4]| -> u8 {
162 positions
163 .iter()
164 .enumerate()
165 .map(|(shift, &pos)| word[pos].as_u8() << shift)
166 .sum()
167 };
168
169 let frame_units = nibble([0, 1, 2, 3]);
171 let frame_tens = nibble([8, 9, 0, 0]) & 0x03;
173 let drop_frame = word[10].as_u8() != 0;
174 let color_frame = word[11].as_u8() != 0;
175
176 let sec_units = nibble([16, 17, 18, 19]);
178 let sec_tens = nibble([24, 25, 26, 0]) & 0x07;
180
181 let min_units = nibble([32, 33, 34, 35]);
183 let min_tens = nibble([40, 41, 42, 0]) & 0x07;
185
186 let hr_units = nibble([48, 49, 50, 51]);
188 let hr_tens = nibble([56, 57, 0, 0]) & 0x03;
190 let biphase_polarity = word[58].as_u8() != 0;
191
192 let frames = frame_tens * 10 + frame_units;
193 let seconds = sec_tens * 10 + sec_units;
194 let minutes = min_tens * 10 + min_units;
195 let hours = hr_tens * 10 + hr_units;
196
197 let ub_positions: [[usize; 4]; 8] = [
200 [4, 5, 0, 0], [6, 7, 0, 0],
202 [12, 13, 0, 0],
203 [14, 15, 0, 0],
204 [22, 23, 0, 0],
205 [28, 29, 0, 0], [36, 37, 0, 0],
207 [44, 45, 0, 0],
208 ];
209 let mut user_bits: u32 = 0;
210 for (idx, pos) in ub_positions.iter().enumerate() {
211 let nibval = (word[pos[0]].as_u8() | (word[pos[1]].as_u8() << 1)) as u32;
212 user_bits |= nibval << (idx * 2);
213 }
214
215 let frame_rate_info = FrameRateInfo {
216 fps: self.fps,
217 drop_frame,
218 };
219
220 let timecode = Timecode {
221 hours,
222 minutes,
223 seconds,
224 frames,
225 frame_rate: frame_rate_info,
226 user_bits,
227 };
228
229 Ok(LtcFrame {
230 timecode,
231 user_bits,
232 drop_frame,
233 color_frame,
234 biphase_polarity,
235 bit_offset: offset,
236 })
237 }
238
239 pub fn check_sync(&self, bits: &[LtcBit], offset: usize) -> bool {
241 if offset + 16 > bits.len() {
242 return false;
243 }
244 Self::SYNC_BITS
245 .iter()
246 .enumerate()
247 .all(|(i, &expected)| bits[offset + i].as_u8() == expected)
248 }
249
250 pub fn encode_frame(&self, tc: &Timecode) -> Vec<LtcBit> {
254 let mut word = vec![LtcBit::Zero; 80];
255
256 let set_bit = |word: &mut Vec<LtcBit>, pos: usize, val: u8| {
257 word[pos] = LtcBit::from(val);
258 };
259
260 let fu = tc.frames % 10;
262 let ft = tc.frames / 10;
263 for i in 0..4 {
264 set_bit(&mut word, i, (fu >> i) & 1);
265 }
266 set_bit(&mut word, 8, ft & 1);
267 set_bit(&mut word, 9, (ft >> 1) & 1);
268 set_bit(&mut word, 10, tc.frame_rate.drop_frame as u8);
269
270 let su = tc.seconds % 10;
272 let st = tc.seconds / 10;
273 for i in 0..4 {
274 set_bit(&mut word, 16 + i, (su >> i) & 1);
275 }
276 for i in 0..3 {
277 set_bit(&mut word, 24 + i, (st >> i) & 1);
278 }
279
280 let mu = tc.minutes % 10;
282 let mt = tc.minutes / 10;
283 for i in 0..4 {
284 set_bit(&mut word, 32 + i, (mu >> i) & 1);
285 }
286 for i in 0..3 {
287 set_bit(&mut word, 40 + i, (mt >> i) & 1);
288 }
289
290 let hu = tc.hours % 10;
292 let ht = tc.hours / 10;
293 for i in 0..4 {
294 set_bit(&mut word, 48 + i, (hu >> i) & 1);
295 }
296 for i in 0..2 {
297 set_bit(&mut word, 56 + i, (ht >> i) & 1);
298 }
299
300 for (i, &b) in Self::SYNC_BITS.iter().enumerate() {
302 set_bit(&mut word, 64 + i, b);
303 }
304
305 word
306 }
307}
308
309pub fn build_ltc_word(
311 hours: u8,
312 minutes: u8,
313 seconds: u8,
314 frames: u8,
315 drop_frame: bool,
316 fps: u8,
317) -> Vec<LtcBit> {
318 use crate::FrameRateInfo;
319 let tc = Timecode {
320 hours,
321 minutes,
322 seconds,
323 frames,
324 frame_rate: FrameRateInfo { fps, drop_frame },
325 user_bits: 0,
326 };
327 let parser = LtcParser::new(fps, drop_frame);
328 parser.encode_frame(&tc)
329}
330
331#[cfg(test)]
334mod tests {
335 use super::*;
336
337 fn make_parser() -> LtcParser {
338 LtcParser::new(25, false)
339 }
340
341 #[test]
342 fn test_ltcbit_from_bool() {
343 assert_eq!(LtcBit::from(true), LtcBit::One);
344 assert_eq!(LtcBit::from(false), LtcBit::Zero);
345 }
346
347 #[test]
348 fn test_ltcbit_from_u8() {
349 assert_eq!(LtcBit::from(1u8), LtcBit::One);
350 assert_eq!(LtcBit::from(0u8), LtcBit::Zero);
351 assert_eq!(LtcBit::from(255u8), LtcBit::One);
352 }
353
354 #[test]
355 fn test_ltcbit_as_u8() {
356 assert_eq!(LtcBit::One.as_u8(), 1);
357 assert_eq!(LtcBit::Zero.as_u8(), 0);
358 }
359
360 #[test]
361 fn test_check_sync_valid() {
362 let mut bits = vec![LtcBit::Zero; 80];
363 for (i, &b) in LtcParser::SYNC_BITS.iter().enumerate() {
364 bits[64 + i] = LtcBit::from(b);
365 }
366 assert!(make_parser().check_sync(&bits, 64));
367 }
368
369 #[test]
370 fn test_check_sync_invalid() {
371 let bits = vec![LtcBit::Zero; 80];
372 assert!(!make_parser().check_sync(&bits, 64));
373 }
374
375 #[test]
376 fn test_check_sync_too_short() {
377 let bits = vec![LtcBit::Zero; 10];
378 assert!(!make_parser().check_sync(&bits, 0));
379 }
380
381 #[test]
382 fn test_encode_decode_roundtrip_simple() {
383 let parser = make_parser();
384 let tc = Timecode {
385 hours: 1,
386 minutes: 2,
387 seconds: 3,
388 frames: 4,
389 frame_rate: crate::FrameRateInfo {
390 fps: 25,
391 drop_frame: false,
392 },
393 user_bits: 0,
394 };
395 let encoded = parser.encode_frame(&tc);
396 assert_eq!(encoded.len(), 80);
397 let decoded = parser.decode_frame(&encoded, 0).unwrap();
398 assert_eq!(decoded.timecode.hours, 1);
399 assert_eq!(decoded.timecode.minutes, 2);
400 assert_eq!(decoded.timecode.seconds, 3);
401 assert_eq!(decoded.timecode.frames, 4);
402 }
403
404 #[test]
405 fn test_encode_decode_midnight() {
406 let parser = make_parser();
407 let tc = Timecode {
408 hours: 0,
409 minutes: 0,
410 seconds: 0,
411 frames: 0,
412 frame_rate: crate::FrameRateInfo {
413 fps: 25,
414 drop_frame: false,
415 },
416 user_bits: 0,
417 };
418 let encoded = parser.encode_frame(&tc);
419 let decoded = parser.decode_frame(&encoded, 0).unwrap();
420 assert_eq!(decoded.timecode.hours, 0);
421 assert_eq!(decoded.timecode.seconds, 0);
422 }
423
424 #[test]
425 fn test_decode_bits_finds_one_frame() {
426 let parser = make_parser();
427 let tc = Timecode {
428 hours: 0,
429 minutes: 1,
430 seconds: 2,
431 frames: 3,
432 frame_rate: crate::FrameRateInfo {
433 fps: 25,
434 drop_frame: false,
435 },
436 user_bits: 0,
437 };
438 let bits = parser.encode_frame(&tc);
439 let frames = parser.decode_bits(&bits);
440 assert_eq!(frames.len(), 1);
441 }
442
443 #[test]
444 fn test_decode_bits_empty() {
445 assert!(make_parser().decode_bits(&[]).is_empty());
446 }
447
448 #[test]
449 fn test_decode_bits_too_short() {
450 let bits = vec![LtcBit::Zero; 40];
451 assert!(make_parser().decode_bits(&bits).is_empty());
452 }
453
454 #[test]
455 fn test_decode_frame_buffer_too_small() {
456 let bits = vec![LtcBit::Zero; 79];
457 let err = make_parser().decode_frame(&bits, 0);
458 assert_eq!(err, Err(TimecodeError::BufferTooSmall));
459 }
460
461 #[test]
462 fn test_decode_drop_frame_flag() {
463 let parser = LtcParser::new(30, true);
464 let tc = Timecode {
465 hours: 0,
466 minutes: 0,
467 seconds: 5,
468 frames: 0,
469 frame_rate: crate::FrameRateInfo {
470 fps: 30,
471 drop_frame: true,
472 },
473 user_bits: 0,
474 };
475 let encoded = parser.encode_frame(&tc);
476 let decoded = parser.decode_frame(&encoded, 0).unwrap();
477 assert!(decoded.drop_frame);
478 }
479
480 #[test]
481 fn test_build_ltc_word_length() {
482 let word = build_ltc_word(1, 2, 3, 4, false, 25);
483 assert_eq!(word.len(), 80);
484 }
485
486 #[test]
487 fn test_decode_frame_bit_offset() {
488 let parser = make_parser();
489 let tc = Timecode {
490 hours: 0,
491 minutes: 0,
492 seconds: 0,
493 frames: 0,
494 frame_rate: crate::FrameRateInfo {
495 fps: 25,
496 drop_frame: false,
497 },
498 user_bits: 0,
499 };
500 let bits = parser.encode_frame(&tc);
501 let decoded = parser.decode_frame(&bits, 0).unwrap();
502 assert_eq!(decoded.bit_offset, 0);
503 }
504
505 #[test]
506 fn test_ltcframe_color_frame_default_false() {
507 let parser = make_parser();
508 let tc = Timecode {
509 hours: 0,
510 minutes: 0,
511 seconds: 0,
512 frames: 0,
513 frame_rate: crate::FrameRateInfo {
514 fps: 25,
515 drop_frame: false,
516 },
517 user_bits: 0,
518 };
519 let encoded = parser.encode_frame(&tc);
520 let decoded = parser.decode_frame(&encoded, 0).unwrap();
521 assert!(!decoded.color_frame);
522 }
523}