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::from_raw_fields(
221 hours, minutes, seconds, frames, self.fps, drop_frame, user_bits,
222 );
223
224 Ok(LtcFrame {
225 timecode,
226 user_bits,
227 drop_frame,
228 color_frame,
229 biphase_polarity,
230 bit_offset: offset,
231 })
232 }
233
234 pub fn check_sync(&self, bits: &[LtcBit], offset: usize) -> bool {
236 if offset + 16 > bits.len() {
237 return false;
238 }
239 Self::SYNC_BITS
240 .iter()
241 .enumerate()
242 .all(|(i, &expected)| bits[offset + i].as_u8() == expected)
243 }
244
245 pub fn encode_frame(&self, tc: &Timecode) -> Vec<LtcBit> {
249 let mut word = vec![LtcBit::Zero; 80];
250
251 let set_bit = |word: &mut Vec<LtcBit>, pos: usize, val: u8| {
252 word[pos] = LtcBit::from(val);
253 };
254
255 let fu = tc.frames % 10;
257 let ft = tc.frames / 10;
258 for i in 0..4 {
259 set_bit(&mut word, i, (fu >> i) & 1);
260 }
261 set_bit(&mut word, 8, ft & 1);
262 set_bit(&mut word, 9, (ft >> 1) & 1);
263 set_bit(&mut word, 10, tc.frame_rate.drop_frame as u8);
264
265 let su = tc.seconds % 10;
267 let st = tc.seconds / 10;
268 for i in 0..4 {
269 set_bit(&mut word, 16 + i, (su >> i) & 1);
270 }
271 for i in 0..3 {
272 set_bit(&mut word, 24 + i, (st >> i) & 1);
273 }
274
275 let mu = tc.minutes % 10;
277 let mt = tc.minutes / 10;
278 for i in 0..4 {
279 set_bit(&mut word, 32 + i, (mu >> i) & 1);
280 }
281 for i in 0..3 {
282 set_bit(&mut word, 40 + i, (mt >> i) & 1);
283 }
284
285 let hu = tc.hours % 10;
287 let ht = tc.hours / 10;
288 for i in 0..4 {
289 set_bit(&mut word, 48 + i, (hu >> i) & 1);
290 }
291 for i in 0..2 {
292 set_bit(&mut word, 56 + i, (ht >> i) & 1);
293 }
294
295 for (i, &b) in Self::SYNC_BITS.iter().enumerate() {
297 set_bit(&mut word, 64 + i, b);
298 }
299
300 word
301 }
302}
303
304pub fn build_ltc_word(
306 hours: u8,
307 minutes: u8,
308 seconds: u8,
309 frames: u8,
310 drop_frame: bool,
311 fps: u8,
312) -> Vec<LtcBit> {
313 let tc = Timecode::from_raw_fields(hours, minutes, seconds, frames, fps, drop_frame, 0);
314 let parser = LtcParser::new(fps, drop_frame);
315 parser.encode_frame(&tc)
316}
317
318#[cfg(test)]
321mod tests {
322 use super::*;
323
324 fn make_parser() -> LtcParser {
325 LtcParser::new(25, false)
326 }
327
328 #[test]
329 fn test_ltcbit_from_bool() {
330 assert_eq!(LtcBit::from(true), LtcBit::One);
331 assert_eq!(LtcBit::from(false), LtcBit::Zero);
332 }
333
334 #[test]
335 fn test_ltcbit_from_u8() {
336 assert_eq!(LtcBit::from(1u8), LtcBit::One);
337 assert_eq!(LtcBit::from(0u8), LtcBit::Zero);
338 assert_eq!(LtcBit::from(255u8), LtcBit::One);
339 }
340
341 #[test]
342 fn test_ltcbit_as_u8() {
343 assert_eq!(LtcBit::One.as_u8(), 1);
344 assert_eq!(LtcBit::Zero.as_u8(), 0);
345 }
346
347 #[test]
348 fn test_check_sync_valid() {
349 let mut bits = vec![LtcBit::Zero; 80];
350 for (i, &b) in LtcParser::SYNC_BITS.iter().enumerate() {
351 bits[64 + i] = LtcBit::from(b);
352 }
353 assert!(make_parser().check_sync(&bits, 64));
354 }
355
356 #[test]
357 fn test_check_sync_invalid() {
358 let bits = vec![LtcBit::Zero; 80];
359 assert!(!make_parser().check_sync(&bits, 64));
360 }
361
362 #[test]
363 fn test_check_sync_too_short() {
364 let bits = vec![LtcBit::Zero; 10];
365 assert!(!make_parser().check_sync(&bits, 0));
366 }
367
368 #[test]
369 fn test_encode_decode_roundtrip_simple() {
370 let parser = make_parser();
371 let tc = Timecode::from_raw_fields(1, 2, 3, 4, 25, false, 0);
372 let encoded = parser.encode_frame(&tc);
373 assert_eq!(encoded.len(), 80);
374 let decoded = parser
375 .decode_frame(&encoded, 0)
376 .expect("decode should succeed");
377 assert_eq!(decoded.timecode.hours, 1);
378 assert_eq!(decoded.timecode.minutes, 2);
379 assert_eq!(decoded.timecode.seconds, 3);
380 assert_eq!(decoded.timecode.frames, 4);
381 }
382
383 #[test]
384 fn test_encode_decode_midnight() {
385 let parser = make_parser();
386 let tc = Timecode::from_raw_fields(0, 0, 0, 0, 25, false, 0);
387 let encoded = parser.encode_frame(&tc);
388 let decoded = parser
389 .decode_frame(&encoded, 0)
390 .expect("decode should succeed");
391 assert_eq!(decoded.timecode.hours, 0);
392 assert_eq!(decoded.timecode.seconds, 0);
393 }
394
395 #[test]
396 fn test_decode_bits_finds_one_frame() {
397 let parser = make_parser();
398 let tc = Timecode::from_raw_fields(0, 1, 2, 3, 25, false, 0);
399 let bits = parser.encode_frame(&tc);
400 let frames = parser.decode_bits(&bits);
401 assert_eq!(frames.len(), 1);
402 }
403
404 #[test]
405 fn test_decode_bits_empty() {
406 assert!(make_parser().decode_bits(&[]).is_empty());
407 }
408
409 #[test]
410 fn test_decode_bits_too_short() {
411 let bits = vec![LtcBit::Zero; 40];
412 assert!(make_parser().decode_bits(&bits).is_empty());
413 }
414
415 #[test]
416 fn test_decode_frame_buffer_too_small() {
417 let bits = vec![LtcBit::Zero; 79];
418 let err = make_parser().decode_frame(&bits, 0);
419 assert_eq!(err, Err(TimecodeError::BufferTooSmall));
420 }
421
422 #[test]
423 fn test_decode_drop_frame_flag() {
424 let parser = LtcParser::new(30, true);
425 let tc = Timecode::from_raw_fields(0, 0, 5, 0, 30, true, 0);
426 let encoded = parser.encode_frame(&tc);
427 let decoded = parser
428 .decode_frame(&encoded, 0)
429 .expect("decode should succeed");
430 assert!(decoded.drop_frame);
431 }
432
433 #[test]
434 fn test_build_ltc_word_length() {
435 let word = build_ltc_word(1, 2, 3, 4, false, 25);
436 assert_eq!(word.len(), 80);
437 }
438
439 #[test]
440 fn test_decode_frame_bit_offset() {
441 let parser = make_parser();
442 let tc = Timecode::from_raw_fields(0, 0, 0, 0, 25, false, 0);
443 let bits = parser.encode_frame(&tc);
444 let decoded = parser
445 .decode_frame(&bits, 0)
446 .expect("decode should succeed");
447 assert_eq!(decoded.bit_offset, 0);
448 }
449
450 #[test]
451 fn test_ltcframe_color_frame_default_false() {
452 let parser = make_parser();
453 let tc = Timecode::from_raw_fields(0, 0, 0, 0, 25, false, 0);
454 let encoded = parser.encode_frame(&tc);
455 let decoded = parser
456 .decode_frame(&encoded, 0)
457 .expect("decode should succeed");
458 assert!(!decoded.color_frame);
459 }
460}