1#![allow(dead_code)]
9#![allow(clippy::cast_precision_loss)]
10#![allow(clippy::cast_possible_truncation)]
11#![allow(clippy::cast_sign_loss)]
12
13use crate::Timecode;
14
15#[derive(Debug, Clone, Copy, PartialEq)]
19pub struct LtcSignalParams {
20 pub sample_rate: u32,
22 pub amplitude: f32,
24 pub fps: u8,
26}
27
28impl LtcSignalParams {
29 pub fn default_25fps() -> Self {
31 Self {
32 sample_rate: 48000,
33 amplitude: 0.5,
34 fps: 25,
35 }
36 }
37
38 pub fn default_30fps() -> Self {
40 Self {
41 sample_rate: 48000,
42 amplitude: 0.5,
43 fps: 30,
44 }
45 }
46
47 pub fn samples_per_bit(&self) -> f64 {
51 self.sample_rate as f64 / (self.fps as f64 * 80.0)
52 }
53
54 pub fn samples_per_frame(&self) -> u32 {
56 (self.sample_rate as f64 / self.fps as f64).round() as u32
57 }
58}
59
60#[derive(Debug, Clone)]
66pub struct LtcBitEncoder;
67
68impl LtcBitEncoder {
69 const SYNC_WORD: [u8; 16] = [0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1];
71
72 pub fn encode(tc: &Timecode) -> [u8; 80] {
74 let mut word = [0u8; 80];
75
76 let fu = tc.frames % 10;
78 for i in 0..4u8 {
79 word[i as usize] = (fu >> i) & 1;
80 }
81
82 let ft = tc.frames / 10;
84 word[8] = ft & 1;
85 word[9] = (ft >> 1) & 1;
86 word[10] = u8::from(tc.frame_rate.drop_frame);
87
88 let su = tc.seconds % 10;
90 for i in 0..4u8 {
91 word[16 + i as usize] = (su >> i) & 1;
92 }
93
94 let st = tc.seconds / 10;
96 for i in 0..3u8 {
97 word[24 + i as usize] = (st >> i) & 1;
98 }
99
100 let mu = tc.minutes % 10;
102 for i in 0..4u8 {
103 word[32 + i as usize] = (mu >> i) & 1;
104 }
105
106 let mt = tc.minutes / 10;
108 for i in 0..3u8 {
109 word[40 + i as usize] = (mt >> i) & 1;
110 }
111
112 let hu = tc.hours % 10;
114 for i in 0..4u8 {
115 word[48 + i as usize] = (hu >> i) & 1;
116 }
117
118 let ht = tc.hours / 10;
120 for i in 0..2u8 {
121 word[56 + i as usize] = (ht >> i) & 1;
122 }
123
124 word[64..80].copy_from_slice(&Self::SYNC_WORD);
126
127 word
128 }
129
130 pub fn decode(word: &[u8; 80]) -> (u8, u8, u8, u8, bool) {
132 let nibble = |positions: &[usize]| -> u8 {
133 positions
134 .iter()
135 .enumerate()
136 .map(|(shift, &pos)| word[pos] << shift)
137 .sum()
138 };
139
140 let frame_units = nibble(&[0, 1, 2, 3]);
141 let frame_tens = nibble(&[8, 9]) & 0x03;
142 let drop_frame = word[10] != 0;
143
144 let sec_units = nibble(&[16, 17, 18, 19]);
145 let sec_tens = nibble(&[24, 25, 26]) & 0x07;
146
147 let min_units = nibble(&[32, 33, 34, 35]);
148 let min_tens = nibble(&[40, 41, 42]) & 0x07;
149
150 let hr_units = nibble(&[48, 49, 50, 51]);
151 let hr_tens = nibble(&[56, 57]) & 0x03;
152
153 let frames = frame_tens * 10 + frame_units;
154 let seconds = sec_tens * 10 + sec_units;
155 let minutes = min_tens * 10 + min_units;
156 let hours = hr_tens * 10 + hr_units;
157
158 (hours, minutes, seconds, frames, drop_frame)
159 }
160}
161
162#[derive(Debug, Clone)]
186pub struct LtcAudioEncoder {
187 params: LtcSignalParams,
189 polarity: f32,
191}
192
193impl LtcAudioEncoder {
194 pub fn new(params: LtcSignalParams) -> Self {
196 Self {
197 params,
198 polarity: 1.0,
199 }
200 }
201
202 pub fn encode_frame(&mut self, bits: &[u8; 80]) -> Vec<f32> {
206 let spb = self.params.samples_per_bit();
207 let amplitude = self.params.amplitude;
208 let mut samples = Vec::with_capacity(self.params.samples_per_frame() as usize);
209
210 for &bit in bits.iter() {
211 let num_samples = spb.round() as usize;
212 let half = num_samples / 2;
213
214 if bit == 1 {
215 for _ in 0..half {
217 samples.push(self.polarity * amplitude);
218 }
219 self.polarity = -self.polarity;
220 for _ in half..num_samples {
221 samples.push(self.polarity * amplitude);
222 }
223 self.polarity = -self.polarity;
224 } else {
225 for _ in 0..num_samples {
227 samples.push(self.polarity * amplitude);
228 }
229 self.polarity = -self.polarity;
230 }
231 }
232
233 samples
234 }
235
236 pub fn encode_sequence(&mut self, timecodes: &[Timecode]) -> Vec<f32> {
238 let mut all_samples = Vec::new();
239 for tc in timecodes {
240 let bits = LtcBitEncoder::encode(tc);
241 let frame_samples = self.encode_frame(&bits);
242 all_samples.extend_from_slice(&frame_samples);
243 }
244 all_samples
245 }
246
247 pub fn polarity(&self) -> f32 {
249 self.polarity
250 }
251
252 pub fn reset_polarity(&mut self) {
254 self.polarity = 1.0;
255 }
256
257 pub fn params(&self) -> &LtcSignalParams {
259 &self.params
260 }
261}
262
263#[cfg(test)]
266mod tests {
267 use super::*;
268
269 fn make_tc(h: u8, m: u8, s: u8, f: u8, fps: u8, df: bool) -> Timecode {
270 Timecode::from_raw_fields(h, m, s, f, fps, df, 0)
271 }
272
273 fn default_params() -> LtcSignalParams {
274 LtcSignalParams::default_25fps()
275 }
276
277 #[test]
278 fn test_signal_params_samples_per_bit_25fps() {
279 let p = default_params();
280 let spb = p.samples_per_bit();
281 assert!((spb - 24.0).abs() < 1e-6);
283 }
284
285 #[test]
286 fn test_signal_params_samples_per_frame_25fps() {
287 let p = default_params();
288 assert_eq!(p.samples_per_frame(), 1920); }
290
291 #[test]
292 fn test_signal_params_30fps() {
293 let p = LtcSignalParams::default_30fps();
294 assert_eq!(p.fps, 30);
295 assert_eq!(p.samples_per_frame(), 1600); }
297
298 #[test]
299 fn test_bit_encoder_roundtrip() {
300 let tc = make_tc(12, 34, 56, 7, 25, false);
301 let bits = LtcBitEncoder::encode(&tc);
302 let (h, m, s, f, df) = LtcBitEncoder::decode(&bits);
303 assert_eq!(h, 12);
304 assert_eq!(m, 34);
305 assert_eq!(s, 56);
306 assert_eq!(f, 7);
307 assert!(!df);
308 }
309
310 #[test]
311 fn test_bit_encoder_drop_frame_flag() {
312 let tc = make_tc(0, 0, 0, 2, 30, true);
313 let bits = LtcBitEncoder::encode(&tc);
314 let (_, _, _, _, df) = LtcBitEncoder::decode(&bits);
315 assert!(df);
316 }
317
318 #[test]
319 fn test_bit_encoder_midnight() {
320 let tc = make_tc(0, 0, 0, 0, 25, false);
321 let bits = LtcBitEncoder::encode(&tc);
322 let (h, m, s, f, _) = LtcBitEncoder::decode(&bits);
323 assert_eq!((h, m, s, f), (0, 0, 0, 0));
324 }
325
326 #[test]
327 fn test_bit_encoder_max_values() {
328 let tc = make_tc(23, 59, 59, 24, 25, false);
329 let bits = LtcBitEncoder::encode(&tc);
330 let (h, m, s, f, _) = LtcBitEncoder::decode(&bits);
331 assert_eq!(h, 23);
332 assert_eq!(m, 59);
333 assert_eq!(s, 59);
334 assert_eq!(f, 24);
335 }
336
337 #[test]
338 fn test_bit_encoder_sync_word_present() {
339 let tc = make_tc(0, 0, 0, 0, 25, false);
340 let bits = LtcBitEncoder::encode(&tc);
341 assert_eq!(&bits[64..80], &LtcBitEncoder::SYNC_WORD);
342 }
343
344 #[test]
345 fn test_audio_encoder_output_length() {
346 let params = default_params();
347 let mut enc = LtcAudioEncoder::new(params);
348 let tc = make_tc(0, 0, 0, 0, 25, false);
349 let bits = LtcBitEncoder::encode(&tc);
350 let samples = enc.encode_frame(&bits);
351 assert!(!samples.is_empty());
353 assert!(samples.len() >= 1900 && samples.len() <= 1940);
354 }
355
356 #[test]
357 fn test_audio_encoder_amplitude_bounds() {
358 let params = LtcSignalParams {
359 sample_rate: 48000,
360 amplitude: 0.8,
361 fps: 25,
362 };
363 let mut enc = LtcAudioEncoder::new(params);
364 let tc = make_tc(1, 0, 0, 0, 25, false);
365 let bits = LtcBitEncoder::encode(&tc);
366 let samples = enc.encode_frame(&bits);
367 for &s in &samples {
368 assert!(s.abs() <= 0.8 + 1e-6);
369 }
370 }
371
372 #[test]
373 fn test_audio_encoder_polarity_reset() {
374 let mut enc = LtcAudioEncoder::new(default_params());
375 assert!((enc.polarity() - 1.0).abs() < 1e-6);
376 enc.polarity = -1.0;
377 enc.reset_polarity();
378 assert!((enc.polarity() - 1.0).abs() < 1e-6);
379 }
380
381 #[test]
382 fn test_audio_encoder_sequence() {
383 let mut enc = LtcAudioEncoder::new(default_params());
384 let tcs = vec![
385 make_tc(0, 0, 0, 0, 25, false),
386 make_tc(0, 0, 0, 1, 25, false),
387 ];
388 let samples = enc.encode_sequence(&tcs);
389 assert!(samples.len() >= 3800);
391 }
392
393 #[test]
394 fn test_audio_encoder_params_accessor() {
395 let params = default_params();
396 let enc = LtcAudioEncoder::new(params);
397 assert_eq!(enc.params().fps, 25);
398 assert_eq!(enc.params().sample_rate, 48000);
399 }
400}