1use super::constants::*;
11use crate::{FrameRate, Timecode, TimecodeError};
12
13pub struct LtcEncoder {
15 #[allow(dead_code)]
17 sample_rate: u32,
18 #[allow(dead_code)]
20 frame_rate: FrameRate,
21 amplitude: f32,
23 samples_per_bit: f32,
25 #[allow(dead_code)]
27 phase: f32,
28 polarity: bool,
30}
31
32impl LtcEncoder {
33 pub fn new(sample_rate: u32, frame_rate: FrameRate, amplitude: f32) -> Self {
35 let fps = frame_rate.as_float();
36 let bits_per_second = fps * BITS_PER_FRAME as f64;
37 let samples_per_bit = sample_rate as f64 / bits_per_second;
38
39 LtcEncoder {
40 sample_rate,
41 frame_rate,
42 amplitude: amplitude.clamp(0.0, 1.0),
43 samples_per_bit: samples_per_bit as f32,
44 phase: 0.0,
45 polarity: false,
46 }
47 }
48
49 pub fn encode_frame(&mut self, timecode: &Timecode) -> Result<Vec<f32>, TimecodeError> {
51 let bits = self.timecode_to_bits(timecode)?;
53
54 let samples = self.bits_to_audio(&bits);
56
57 Ok(samples)
58 }
59
60 fn timecode_to_bits(
62 &self,
63 timecode: &Timecode,
64 ) -> Result<[bool; BITS_PER_FRAME], TimecodeError> {
65 let mut bits = [false; BITS_PER_FRAME];
66
67 let frame_units = timecode.frames % 10;
69 let frame_tens = timecode.frames / 10;
70 let second_units = timecode.seconds % 10;
71 let second_tens = timecode.seconds / 10;
72 let minute_units = timecode.minutes % 10;
73 let minute_tens = timecode.minutes / 10;
74 let hour_units = timecode.hours % 10;
75 let hour_tens = timecode.hours / 10;
76
77 self.encode_bcd(&mut bits, 0, frame_units);
79
80 self.encode_nibble(&mut bits, 4, (timecode.user_bits & 0xF) as u8);
82
83 self.encode_bcd(&mut bits, 8, frame_tens);
85
86 bits[10] = timecode.frame_rate.drop_frame;
88
89 bits[11] = false;
91
92 self.encode_nibble(&mut bits, 12, ((timecode.user_bits >> 4) & 0xF) as u8);
94
95 self.encode_bcd(&mut bits, 16, second_units);
97
98 self.encode_nibble(&mut bits, 20, ((timecode.user_bits >> 8) & 0xF) as u8);
100
101 self.encode_bcd(&mut bits, 24, second_tens);
103
104 bits[27] = self.calculate_even_parity(&bits[0..27]);
106
107 self.encode_nibble(&mut bits, 28, ((timecode.user_bits >> 12) & 0xF) as u8);
109
110 self.encode_bcd(&mut bits, 32, minute_units);
112
113 self.encode_nibble(&mut bits, 36, ((timecode.user_bits >> 16) & 0xF) as u8);
115
116 self.encode_bcd(&mut bits, 40, minute_tens);
118
119 bits[43] = false;
121
122 self.encode_nibble(&mut bits, 44, ((timecode.user_bits >> 20) & 0xF) as u8);
124
125 self.encode_bcd(&mut bits, 48, hour_units);
127
128 self.encode_nibble(&mut bits, 52, ((timecode.user_bits >> 24) & 0xF) as u8);
130
131 self.encode_bcd(&mut bits, 56, hour_tens);
133
134 bits[58] = false;
136
137 self.encode_nibble(&mut bits, 59, ((timecode.user_bits >> 28) & 0xF) as u8);
139
140 bits[63] = false;
142
143 self.encode_sync_word(&mut bits);
145
146 Ok(bits)
147 }
148
149 fn encode_bcd(&self, bits: &mut [bool; BITS_PER_FRAME], start: usize, value: u8) {
151 for i in 0..4 {
152 if start + i < BITS_PER_FRAME {
153 bits[start + i] = (value & (1 << i)) != 0;
154 }
155 }
156 }
157
158 fn encode_nibble(&self, bits: &mut [bool; BITS_PER_FRAME], start: usize, value: u8) {
160 for i in 0..4 {
161 if start + i < BITS_PER_FRAME {
162 bits[start + i] = (value & (1 << i)) != 0;
163 }
164 }
165 }
166
167 fn calculate_even_parity(&self, bits: &[bool]) -> bool {
169 let count = bits.iter().filter(|&&b| b).count();
170 count % 2 != 0
171 }
172
173 fn encode_sync_word(&self, bits: &mut [bool; BITS_PER_FRAME]) {
175 let sync_word = SYNC_WORD;
176 for i in 0..SYNC_BITS {
177 bits[DATA_BITS + i] = (sync_word & (1 << i)) != 0;
178 }
179 }
180
181 fn bits_to_audio(&mut self, bits: &[bool; BITS_PER_FRAME]) -> Vec<f32> {
183 let total_samples = (self.samples_per_bit * BITS_PER_FRAME as f32) as usize;
184 let mut samples = Vec::with_capacity(total_samples);
185
186 for &bit in bits.iter() {
187 let bit_samples = self.encode_bit_bmc(bit);
189 samples.extend_from_slice(&bit_samples);
190 }
191
192 samples
193 }
194
195 fn encode_bit_bmc(&mut self, bit: bool) -> Vec<f32> {
197 let samples_per_bit = self.samples_per_bit as usize;
198 let mut samples = Vec::with_capacity(samples_per_bit);
199
200 if bit {
201 for _ in 0..(samples_per_bit / 2) {
204 samples.push(if self.polarity {
205 self.amplitude
206 } else {
207 -self.amplitude
208 });
209 }
210 self.polarity = !self.polarity;
211
212 for _ in (samples_per_bit / 2)..samples_per_bit {
214 samples.push(if self.polarity {
215 self.amplitude
216 } else {
217 -self.amplitude
218 });
219 }
220 self.polarity = !self.polarity;
221 } else {
222 for _ in 0..samples_per_bit {
224 samples.push(if self.polarity {
225 self.amplitude
226 } else {
227 -self.amplitude
228 });
229 }
230 self.polarity = !self.polarity;
231 }
232
233 samples
234 }
235
236 pub fn encode_batch(timecodes: &[Timecode], sample_rate: u32) -> Vec<Vec<i16>> {
246 timecodes
247 .iter()
248 .map(|tc| {
249 let frame_rate = crate::frame_rate_from_info(&tc.frame_rate);
250 let mut enc = LtcEncoder::new(sample_rate, frame_rate, 1.0);
251 let f32_samples = enc.encode_frame(tc).unwrap_or_default();
252 f32_to_i16_samples(&f32_samples)
253 })
254 .collect()
255 }
256
257 pub fn encode_batch_interleaved(timecodes: &[Timecode], sample_rate: u32) -> Vec<i16> {
263 let mut out = Vec::new();
264 for tc in timecodes {
265 let frame_rate = crate::frame_rate_from_info(&tc.frame_rate);
266 let mut enc = LtcEncoder::new(sample_rate, frame_rate, 1.0);
267 let f32_samples = enc.encode_frame(tc).unwrap_or_default();
268 out.extend(f32_to_i16_samples(&f32_samples));
269 }
270 out
271 }
272
273 pub fn reset(&mut self) {
275 self.phase = 0.0;
276 self.polarity = false;
277 }
278
279 pub fn set_amplitude(&mut self, amplitude: f32) {
281 self.amplitude = amplitude.clamp(0.0, 1.0);
282 }
283
284 pub fn amplitude(&self) -> f32 {
286 self.amplitude
287 }
288}
289
290fn f32_to_i16_samples(samples: &[f32]) -> Vec<i16> {
292 samples
293 .iter()
294 .map(|&s| {
295 let clamped = s.clamp(-1.0, 1.0);
296 (clamped * i16::MAX as f32) as i16
297 })
298 .collect()
299}
300
301#[allow(dead_code)]
303struct WaveformShaper {
304 rise_time: usize,
306 transition_progress: usize,
308 target_level: f32,
310 current_level: f32,
312}
313
314impl WaveformShaper {
315 #[allow(dead_code)]
316 fn new(sample_rate: u32, rise_time_us: f32) -> Self {
317 let rise_time = ((rise_time_us / 1_000_000.0) * sample_rate as f32) as usize;
318
319 WaveformShaper {
320 rise_time: rise_time.max(1),
321 transition_progress: 0,
322 target_level: 0.0,
323 current_level: 0.0,
324 }
325 }
326
327 #[allow(dead_code)]
329 fn set_target(&mut self, level: f32) {
330 if (level - self.target_level).abs() > 0.001 {
331 self.target_level = level;
332 self.transition_progress = 0;
333 }
334 }
335
336 #[allow(dead_code)]
338 fn next_sample(&mut self) -> f32 {
339 if self.transition_progress < self.rise_time {
340 let progress = self.transition_progress as f32 / self.rise_time as f32;
342 self.current_level =
343 self.current_level * (1.0 - progress) + self.target_level * progress;
344 self.transition_progress += 1;
345 } else {
346 self.current_level = self.target_level;
347 }
348
349 self.current_level
350 }
351
352 #[allow(dead_code)]
353 fn reset(&mut self) {
354 self.transition_progress = 0;
355 self.current_level = 0.0;
356 }
357}
358
359#[allow(dead_code)]
361struct PreEmphasisFilter {
362 alpha: f32,
364 prev_input: f32,
366 prev_output: f32,
368}
369
370impl PreEmphasisFilter {
371 #[allow(dead_code)]
372 fn new(time_constant_us: f32, sample_rate: u32) -> Self {
373 let tc = time_constant_us / 1_000_000.0;
374 let dt = 1.0 / sample_rate as f32;
375 let alpha = tc / (tc + dt);
376
377 PreEmphasisFilter {
378 alpha,
379 prev_input: 0.0,
380 prev_output: 0.0,
381 }
382 }
383
384 #[allow(dead_code)]
386 fn process(&mut self, input: f32) -> f32 {
387 let output = self.alpha * (self.prev_output + input - self.prev_input);
388 self.prev_input = input;
389 self.prev_output = output;
390 output
391 }
392
393 #[allow(dead_code)]
394 fn reset(&mut self) {
395 self.prev_input = 0.0;
396 self.prev_output = 0.0;
397 }
398}
399
400#[allow(dead_code)]
402struct DcBlocker {
403 alpha: f32,
405 prev_input: f32,
407 prev_output: f32,
409}
410
411impl DcBlocker {
412 #[allow(dead_code)]
413 fn new(cutoff_hz: f32, sample_rate: u32) -> Self {
414 let rc = 1.0 / (2.0 * std::f32::consts::PI * cutoff_hz);
415 let dt = 1.0 / sample_rate as f32;
416 let alpha = rc / (rc + dt);
417
418 DcBlocker {
419 alpha,
420 prev_input: 0.0,
421 prev_output: 0.0,
422 }
423 }
424
425 #[allow(dead_code)]
427 fn process(&mut self, input: f32) -> f32 {
428 let output = self.alpha * (self.prev_output + input - self.prev_input);
429 self.prev_input = input;
430 self.prev_output = output;
431 output
432 }
433
434 #[allow(dead_code)]
435 fn reset(&mut self) {
436 self.prev_input = 0.0;
437 self.prev_output = 0.0;
438 }
439}
440
441#[allow(dead_code)]
443struct Limiter {
444 threshold: f32,
446 attack_samples: usize,
448 release_samples: usize,
450 gain_reduction: f32,
452}
453
454impl Limiter {
455 #[allow(dead_code)]
456 fn new(threshold: f32, attack_ms: f32, release_ms: f32, sample_rate: u32) -> Self {
457 let attack_samples = ((attack_ms / 1000.0) * sample_rate as f32) as usize;
458 let release_samples = ((release_ms / 1000.0) * sample_rate as f32) as usize;
459
460 Limiter {
461 threshold,
462 attack_samples: attack_samples.max(1),
463 release_samples: release_samples.max(1),
464 gain_reduction: 1.0,
465 }
466 }
467
468 #[allow(dead_code)]
470 fn process(&mut self, input: f32) -> f32 {
471 let abs_input = input.abs();
472
473 if abs_input > self.threshold {
474 let target_gain = self.threshold / abs_input;
476 let attack_coefficient = 1.0 / self.attack_samples as f32;
477 self.gain_reduction += (target_gain - self.gain_reduction) * attack_coefficient;
478 } else {
479 let release_coefficient = 1.0 / self.release_samples as f32;
481 self.gain_reduction += (1.0 - self.gain_reduction) * release_coefficient;
482 }
483
484 input * self.gain_reduction
485 }
486
487 #[allow(dead_code)]
488 fn reset(&mut self) {
489 self.gain_reduction = 1.0;
490 }
491}
492
493pub struct LtcFrameBuffer {
495 sample_rate: u32,
497 frame_rate: FrameRate,
499 amplitude: f32,
501 buffer: Vec<f32>,
503 current_timecode: Option<Timecode>,
505}
506
507impl LtcFrameBuffer {
508 pub fn new(sample_rate: u32, frame_rate: FrameRate, amplitude: f32) -> Self {
510 LtcFrameBuffer {
511 sample_rate,
512 frame_rate,
513 amplitude,
514 buffer: Vec::new(),
515 current_timecode: None,
516 }
517 }
518
519 pub fn set_timecode(&mut self, timecode: Timecode) {
521 self.current_timecode = Some(timecode);
522 }
523
524 pub fn generate_frame(&mut self) -> Result<Vec<f32>, TimecodeError> {
526 if let Some(ref mut tc) = self.current_timecode {
527 let mut encoder = LtcEncoder::new(self.sample_rate, self.frame_rate, self.amplitude);
528 let samples = encoder.encode_frame(tc)?;
529
530 tc.increment()?;
532
533 Ok(samples)
534 } else {
535 Err(TimecodeError::InvalidConfiguration)
536 }
537 }
538
539 pub fn fill_buffer(&mut self, target_samples: usize) -> Result<(), TimecodeError> {
541 while self.buffer.len() < target_samples {
542 let frame_samples = self.generate_frame()?;
543 self.buffer.extend_from_slice(&frame_samples);
544 }
545 Ok(())
546 }
547
548 pub fn read_samples(&mut self, count: usize) -> Vec<f32> {
550 let available = self.buffer.len().min(count);
551 let samples: Vec<f32> = self.buffer.drain(..available).collect();
552 samples
553 }
554
555 pub fn buffer_level(&self) -> usize {
557 self.buffer.len()
558 }
559}
560
561pub struct UserBitsEncoder;
563
564impl UserBitsEncoder {
565 pub fn encode_ascii(text: &str) -> u32 {
567 let bytes = text.as_bytes();
568 let mut user_bits = 0u32;
569
570 for (i, &byte) in bytes.iter().take(4).enumerate() {
571 user_bits |= (byte as u32) << (i * 8);
572 }
573
574 user_bits
575 }
576
577 pub fn encode_date(month: u8, day: u8, year: u16) -> u32 {
579 let mut user_bits = 0u32;
580
581 user_bits |= (month / 10) as u32;
583 user_bits |= ((month % 10) as u32) << 4;
584
585 user_bits |= ((day / 10) as u32) << 8;
587 user_bits |= ((day % 10) as u32) << 12;
588
589 let year_short = (year % 100) as u8;
591 user_bits |= ((year_short / 10) as u32) << 16;
592 user_bits |= ((year_short % 10) as u32) << 20;
593
594 user_bits
595 }
596
597 pub fn encode_binary(data: u32) -> u32 {
599 data
600 }
601}
602
603pub struct SignalQualityMetrics {
605 pub peak_amplitude: f32,
607 pub rms_amplitude: f32,
609 pub crest_factor: f32,
611 pub dc_offset: f32,
613}
614
615impl SignalQualityMetrics {
616 pub fn from_samples(samples: &[f32]) -> Self {
618 let mut sum = 0.0;
619 let mut sum_squared = 0.0;
620 let mut peak: f32 = 0.0;
621
622 for &sample in samples {
623 sum += sample;
624 sum_squared += sample * sample;
625 peak = peak.max(sample.abs());
626 }
627
628 let dc_offset = sum / samples.len() as f32;
629 let rms = (sum_squared / samples.len() as f32).sqrt();
630 let crest_factor = if rms > 0.0 { peak / rms } else { 0.0 };
631
632 SignalQualityMetrics {
633 peak_amplitude: peak,
634 rms_amplitude: rms,
635 crest_factor,
636 dc_offset,
637 }
638 }
639}
640
641#[cfg(test)]
642mod tests {
643 use super::*;
644
645 #[test]
646 fn test_encoder_creation() {
647 let encoder = LtcEncoder::new(48000, FrameRate::Fps25, 0.5);
648 assert_eq!(encoder.amplitude(), 0.5);
649 }
650
651 #[test]
652 fn test_encode_frame() {
653 let mut encoder = LtcEncoder::new(48000, FrameRate::Fps25, 0.5);
654 let timecode = Timecode::new(1, 2, 3, 4, FrameRate::Fps25).expect("valid timecode");
655 let samples = encoder
656 .encode_frame(&timecode)
657 .expect("encode should succeed");
658 assert!(!samples.is_empty());
659 }
660
661 #[test]
662 fn test_user_bits_ascii() {
663 let user_bits = UserBitsEncoder::encode_ascii("TEST");
664 assert_ne!(user_bits, 0);
665 }
666
667 #[test]
668 fn test_user_bits_date() {
669 let user_bits = UserBitsEncoder::encode_date(12, 31, 2023);
670 assert_ne!(user_bits, 0);
671 }
672
673 #[test]
674 fn test_even_parity() {
675 let encoder = LtcEncoder::new(48000, FrameRate::Fps25, 0.5);
676 let bits = [true, false, true]; assert!(!encoder.calculate_even_parity(&bits));
678
679 let bits = [true, false, false]; assert!(encoder.calculate_even_parity(&bits));
681 }
682
683 #[test]
684 fn test_encode_batch_25_frames_count() {
685 let timecodes: Vec<Timecode> = (0u8..25)
687 .map(|f| Timecode::new(0, 0, 0, f, FrameRate::Fps25).expect("valid"))
688 .collect();
689 let frames = LtcEncoder::encode_batch(&timecodes, 48000);
690 assert_eq!(frames.len(), 25, "batch must yield one buffer per timecode");
691 }
692
693 #[test]
694 fn test_encode_batch_frame_length_correct() {
695 let tc = Timecode::new(0, 0, 0, 0, FrameRate::Fps25).expect("valid");
698 let frames = LtcEncoder::encode_batch(&[tc], 48000);
699 let expected_samples = 48000usize / 25; assert_eq!(
701 frames[0].len(),
702 expected_samples,
703 "frame audio length must equal sample_rate / fps"
704 );
705 }
706
707 #[test]
708 fn test_encode_batch_interleaved_length() {
709 let timecodes: Vec<Timecode> = (0u8..25)
710 .map(|f| Timecode::new(0, 0, 0, f, FrameRate::Fps25).expect("valid"))
711 .collect();
712 let flat = LtcEncoder::encode_batch_interleaved(&timecodes, 48000);
713 assert_eq!(flat.len(), 48000);
715 }
716
717 #[test]
718 fn test_encode_batch_matches_interleaved() {
719 let timecodes: Vec<Timecode> = (0u8..5)
720 .map(|f| Timecode::new(0, 0, 0, f, FrameRate::Fps25).expect("valid"))
721 .collect();
722 let batched = LtcEncoder::encode_batch(&timecodes, 48000);
723 let flat: Vec<i16> = batched.iter().flatten().copied().collect();
724 let interleaved = LtcEncoder::encode_batch_interleaved(&timecodes, 48000);
725 assert_eq!(
726 flat, interleaved,
727 "batch and interleaved must produce identical output"
728 );
729 }
730}