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)]
182pub struct LtcAudioEncoder {
183 params: LtcSignalParams,
185 polarity: f32,
187}
188
189impl LtcAudioEncoder {
190 pub fn new(params: LtcSignalParams) -> Self {
192 Self {
193 params,
194 polarity: 1.0,
195 }
196 }
197
198 pub fn encode_frame(&mut self, bits: &[u8; 80]) -> Vec<f32> {
202 let spb = self.params.samples_per_bit();
203 let amplitude = self.params.amplitude;
204 let mut samples = Vec::with_capacity(self.params.samples_per_frame() as usize);
205
206 for &bit in bits.iter() {
207 let num_samples = spb.round() as usize;
208 let half = num_samples / 2;
209
210 if bit == 1 {
211 for _ in 0..half {
213 samples.push(self.polarity * amplitude);
214 }
215 self.polarity = -self.polarity;
216 for _ in half..num_samples {
217 samples.push(self.polarity * amplitude);
218 }
219 self.polarity = -self.polarity;
220 } else {
221 for _ in 0..num_samples {
223 samples.push(self.polarity * amplitude);
224 }
225 self.polarity = -self.polarity;
226 }
227 }
228
229 samples
230 }
231
232 pub fn encode_sequence(&mut self, timecodes: &[Timecode]) -> Vec<f32> {
234 let mut all_samples = Vec::new();
235 for tc in timecodes {
236 let bits = LtcBitEncoder::encode(tc);
237 let frame_samples = self.encode_frame(&bits);
238 all_samples.extend_from_slice(&frame_samples);
239 }
240 all_samples
241 }
242
243 pub fn polarity(&self) -> f32 {
245 self.polarity
246 }
247
248 pub fn reset_polarity(&mut self) {
250 self.polarity = 1.0;
251 }
252
253 pub fn params(&self) -> &LtcSignalParams {
255 &self.params
256 }
257}
258
259#[cfg(test)]
262mod tests {
263 use super::*;
264
265 fn make_tc(h: u8, m: u8, s: u8, f: u8, fps: u8, df: bool) -> Timecode {
266 Timecode::from_raw_fields(h, m, s, f, fps, df, 0)
267 }
268
269 fn default_params() -> LtcSignalParams {
270 LtcSignalParams::default_25fps()
271 }
272
273 #[test]
274 fn test_signal_params_samples_per_bit_25fps() {
275 let p = default_params();
276 let spb = p.samples_per_bit();
277 assert!((spb - 24.0).abs() < 1e-6);
279 }
280
281 #[test]
282 fn test_signal_params_samples_per_frame_25fps() {
283 let p = default_params();
284 assert_eq!(p.samples_per_frame(), 1920); }
286
287 #[test]
288 fn test_signal_params_30fps() {
289 let p = LtcSignalParams::default_30fps();
290 assert_eq!(p.fps, 30);
291 assert_eq!(p.samples_per_frame(), 1600); }
293
294 #[test]
295 fn test_bit_encoder_roundtrip() {
296 let tc = make_tc(12, 34, 56, 7, 25, false);
297 let bits = LtcBitEncoder::encode(&tc);
298 let (h, m, s, f, df) = LtcBitEncoder::decode(&bits);
299 assert_eq!(h, 12);
300 assert_eq!(m, 34);
301 assert_eq!(s, 56);
302 assert_eq!(f, 7);
303 assert!(!df);
304 }
305
306 #[test]
307 fn test_bit_encoder_drop_frame_flag() {
308 let tc = make_tc(0, 0, 0, 2, 30, true);
309 let bits = LtcBitEncoder::encode(&tc);
310 let (_, _, _, _, df) = LtcBitEncoder::decode(&bits);
311 assert!(df);
312 }
313
314 #[test]
315 fn test_bit_encoder_midnight() {
316 let tc = make_tc(0, 0, 0, 0, 25, false);
317 let bits = LtcBitEncoder::encode(&tc);
318 let (h, m, s, f, _) = LtcBitEncoder::decode(&bits);
319 assert_eq!((h, m, s, f), (0, 0, 0, 0));
320 }
321
322 #[test]
323 fn test_bit_encoder_max_values() {
324 let tc = make_tc(23, 59, 59, 24, 25, false);
325 let bits = LtcBitEncoder::encode(&tc);
326 let (h, m, s, f, _) = LtcBitEncoder::decode(&bits);
327 assert_eq!(h, 23);
328 assert_eq!(m, 59);
329 assert_eq!(s, 59);
330 assert_eq!(f, 24);
331 }
332
333 #[test]
334 fn test_bit_encoder_sync_word_present() {
335 let tc = make_tc(0, 0, 0, 0, 25, false);
336 let bits = LtcBitEncoder::encode(&tc);
337 assert_eq!(&bits[64..80], &LtcBitEncoder::SYNC_WORD);
338 }
339
340 #[test]
341 fn test_audio_encoder_output_length() {
342 let params = default_params();
343 let mut enc = LtcAudioEncoder::new(params);
344 let tc = make_tc(0, 0, 0, 0, 25, false);
345 let bits = LtcBitEncoder::encode(&tc);
346 let samples = enc.encode_frame(&bits);
347 assert!(!samples.is_empty());
349 assert!(samples.len() >= 1900 && samples.len() <= 1940);
350 }
351
352 #[test]
353 fn test_audio_encoder_amplitude_bounds() {
354 let params = LtcSignalParams {
355 sample_rate: 48000,
356 amplitude: 0.8,
357 fps: 25,
358 };
359 let mut enc = LtcAudioEncoder::new(params);
360 let tc = make_tc(1, 0, 0, 0, 25, false);
361 let bits = LtcBitEncoder::encode(&tc);
362 let samples = enc.encode_frame(&bits);
363 for &s in &samples {
364 assert!(s.abs() <= 0.8 + 1e-6);
365 }
366 }
367
368 #[test]
369 fn test_audio_encoder_polarity_reset() {
370 let mut enc = LtcAudioEncoder::new(default_params());
371 assert!((enc.polarity() - 1.0).abs() < 1e-6);
372 enc.polarity = -1.0;
373 enc.reset_polarity();
374 assert!((enc.polarity() - 1.0).abs() < 1e-6);
375 }
376
377 #[test]
378 fn test_audio_encoder_sequence() {
379 let mut enc = LtcAudioEncoder::new(default_params());
380 let tcs = vec![
381 make_tc(0, 0, 0, 0, 25, false),
382 make_tc(0, 0, 0, 1, 25, false),
383 ];
384 let samples = enc.encode_sequence(&tcs);
385 assert!(samples.len() >= 3800);
387 }
388
389 #[test]
390 fn test_audio_encoder_params_accessor() {
391 let params = default_params();
392 let enc = LtcAudioEncoder::new(params);
393 assert_eq!(enc.params().fps, 25);
394 assert_eq!(enc.params().sample_rate, 48000);
395 }
396}