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
398 .decode_frame(&encoded, 0)
399 .expect("decode should succeed");
400 assert_eq!(decoded.timecode.hours, 1);
401 assert_eq!(decoded.timecode.minutes, 2);
402 assert_eq!(decoded.timecode.seconds, 3);
403 assert_eq!(decoded.timecode.frames, 4);
404 }
405
406 #[test]
407 fn test_encode_decode_midnight() {
408 let parser = make_parser();
409 let tc = Timecode {
410 hours: 0,
411 minutes: 0,
412 seconds: 0,
413 frames: 0,
414 frame_rate: crate::FrameRateInfo {
415 fps: 25,
416 drop_frame: false,
417 },
418 user_bits: 0,
419 };
420 let encoded = parser.encode_frame(&tc);
421 let decoded = parser
422 .decode_frame(&encoded, 0)
423 .expect("decode should succeed");
424 assert_eq!(decoded.timecode.hours, 0);
425 assert_eq!(decoded.timecode.seconds, 0);
426 }
427
428 #[test]
429 fn test_decode_bits_finds_one_frame() {
430 let parser = make_parser();
431 let tc = Timecode {
432 hours: 0,
433 minutes: 1,
434 seconds: 2,
435 frames: 3,
436 frame_rate: crate::FrameRateInfo {
437 fps: 25,
438 drop_frame: false,
439 },
440 user_bits: 0,
441 };
442 let bits = parser.encode_frame(&tc);
443 let frames = parser.decode_bits(&bits);
444 assert_eq!(frames.len(), 1);
445 }
446
447 #[test]
448 fn test_decode_bits_empty() {
449 assert!(make_parser().decode_bits(&[]).is_empty());
450 }
451
452 #[test]
453 fn test_decode_bits_too_short() {
454 let bits = vec![LtcBit::Zero; 40];
455 assert!(make_parser().decode_bits(&bits).is_empty());
456 }
457
458 #[test]
459 fn test_decode_frame_buffer_too_small() {
460 let bits = vec![LtcBit::Zero; 79];
461 let err = make_parser().decode_frame(&bits, 0);
462 assert_eq!(err, Err(TimecodeError::BufferTooSmall));
463 }
464
465 #[test]
466 fn test_decode_drop_frame_flag() {
467 let parser = LtcParser::new(30, true);
468 let tc = Timecode {
469 hours: 0,
470 minutes: 0,
471 seconds: 5,
472 frames: 0,
473 frame_rate: crate::FrameRateInfo {
474 fps: 30,
475 drop_frame: true,
476 },
477 user_bits: 0,
478 };
479 let encoded = parser.encode_frame(&tc);
480 let decoded = parser
481 .decode_frame(&encoded, 0)
482 .expect("decode should succeed");
483 assert!(decoded.drop_frame);
484 }
485
486 #[test]
487 fn test_build_ltc_word_length() {
488 let word = build_ltc_word(1, 2, 3, 4, false, 25);
489 assert_eq!(word.len(), 80);
490 }
491
492 #[test]
493 fn test_decode_frame_bit_offset() {
494 let parser = make_parser();
495 let tc = Timecode {
496 hours: 0,
497 minutes: 0,
498 seconds: 0,
499 frames: 0,
500 frame_rate: crate::FrameRateInfo {
501 fps: 25,
502 drop_frame: false,
503 },
504 user_bits: 0,
505 };
506 let bits = parser.encode_frame(&tc);
507 let decoded = parser
508 .decode_frame(&bits, 0)
509 .expect("decode should succeed");
510 assert_eq!(decoded.bit_offset, 0);
511 }
512
513 #[test]
514 fn test_ltcframe_color_frame_default_false() {
515 let parser = make_parser();
516 let tc = Timecode {
517 hours: 0,
518 minutes: 0,
519 seconds: 0,
520 frames: 0,
521 frame_rate: crate::FrameRateInfo {
522 fps: 25,
523 drop_frame: false,
524 },
525 user_bits: 0,
526 };
527 let encoded = parser.encode_frame(&tc);
528 let decoded = parser
529 .decode_frame(&encoded, 0)
530 .expect("decode should succeed");
531 assert!(!decoded.color_frame);
532 }
533}