Skip to main content

oximedia_timecode/ltc/
encoder.rs

1//! LTC Encoder - Biphase Mark Code encoding to audio
2//!
3//! This module implements a complete LTC encoder that:
4//! - Encodes timecode and user bits to 80-bit LTC frames
5//! - Generates biphase mark code audio waveforms
6//! - Inserts SMPTE sync words
7//! - Handles drop frame encoding
8//! - Supports variable amplitude and sample rates
9
10use super::constants::*;
11use crate::{FrameRate, Timecode, TimecodeError};
12
13/// LTC encoder
14pub struct LtcEncoder {
15    /// Sample rate
16    #[allow(dead_code)]
17    sample_rate: u32,
18    /// Frame rate
19    #[allow(dead_code)]
20    frame_rate: FrameRate,
21    /// Output amplitude (0.0 to 1.0)
22    amplitude: f32,
23    /// Samples per bit (for nominal speed)
24    samples_per_bit: f32,
25    /// Current phase (0.0 to 1.0)
26    #[allow(dead_code)]
27    phase: f32,
28    /// Current waveform polarity
29    polarity: bool,
30}
31
32impl LtcEncoder {
33    /// Create a new LTC encoder
34    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    /// Encode a timecode frame to audio samples
50    pub fn encode_frame(&mut self, timecode: &Timecode) -> Result<Vec<f32>, TimecodeError> {
51        // Create bit array
52        let bits = self.timecode_to_bits(timecode)?;
53
54        // Encode bits to audio
55        let samples = self.bits_to_audio(&bits);
56
57        Ok(samples)
58    }
59
60    /// Convert timecode to 80-bit LTC frame
61    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        // Decompose timecode
68        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        // Encode frame units (bits 0-3)
78        self.encode_bcd(&mut bits, 0, frame_units);
79
80        // User bits 1 (bits 4-7)
81        self.encode_nibble(&mut bits, 4, (timecode.user_bits & 0xF) as u8);
82
83        // Frame tens (bits 8-9)
84        self.encode_bcd(&mut bits, 8, frame_tens);
85
86        // Drop frame flag (bit 10)
87        bits[10] = timecode.frame_rate.drop_frame;
88
89        // Color frame flag (bit 11) - assume 0
90        bits[11] = false;
91
92        // User bits 2 (bits 12-15)
93        self.encode_nibble(&mut bits, 12, ((timecode.user_bits >> 4) & 0xF) as u8);
94
95        // Second units (bits 16-19)
96        self.encode_bcd(&mut bits, 16, second_units);
97
98        // User bits 3 (bits 20-23)
99        self.encode_nibble(&mut bits, 20, ((timecode.user_bits >> 8) & 0xF) as u8);
100
101        // Second tens (bits 24-26)
102        self.encode_bcd(&mut bits, 24, second_tens);
103
104        // Even parity (bit 27)
105        bits[27] = self.calculate_even_parity(&bits[0..27]);
106
107        // User bits 4 (bits 28-31)
108        self.encode_nibble(&mut bits, 28, ((timecode.user_bits >> 12) & 0xF) as u8);
109
110        // Minute units (bits 32-35)
111        self.encode_bcd(&mut bits, 32, minute_units);
112
113        // User bits 5 (bits 36-39)
114        self.encode_nibble(&mut bits, 36, ((timecode.user_bits >> 16) & 0xF) as u8);
115
116        // Minute tens (bits 40-42)
117        self.encode_bcd(&mut bits, 40, minute_tens);
118
119        // Binary group flag (bit 43)
120        bits[43] = false;
121
122        // User bits 6 (bits 44-47)
123        self.encode_nibble(&mut bits, 44, ((timecode.user_bits >> 20) & 0xF) as u8);
124
125        // Hour units (bits 48-51)
126        self.encode_bcd(&mut bits, 48, hour_units);
127
128        // User bits 7 (bits 52-55)
129        self.encode_nibble(&mut bits, 52, ((timecode.user_bits >> 24) & 0xF) as u8);
130
131        // Hour tens (bits 56-57)
132        self.encode_bcd(&mut bits, 56, hour_tens);
133
134        // Reserved bits (58)
135        bits[58] = false;
136
137        // User bits 8 (bits 59-62)
138        self.encode_nibble(&mut bits, 59, ((timecode.user_bits >> 28) & 0xF) as u8);
139
140        // Reserved bit (63)
141        bits[63] = false;
142
143        // Sync word (bits 64-79)
144        self.encode_sync_word(&mut bits);
145
146        Ok(bits)
147    }
148
149    /// Encode a BCD digit (4 bits, but may use fewer)
150    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    /// Encode a 4-bit nibble
159    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    /// Calculate even parity
168    fn calculate_even_parity(&self, bits: &[bool]) -> bool {
169        let count = bits.iter().filter(|&&b| b).count();
170        count % 2 != 0
171    }
172
173    /// Encode sync word (0x3FFD)
174    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    /// Convert bit array to audio samples using biphase mark code
182    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            // Generate audio for one bit using biphase mark code
188            let bit_samples = self.encode_bit_bmc(bit);
189            samples.extend_from_slice(&bit_samples);
190        }
191
192        samples
193    }
194
195    /// Encode a single bit using biphase mark code
196    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            // Bit 1: Transition at start and middle
202            // First half
203            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            // Second half
213            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            // Bit 0: Transition only at start
223            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    /// Reset encoder state
237    pub fn reset(&mut self) {
238        self.phase = 0.0;
239        self.polarity = false;
240    }
241
242    /// Set output amplitude
243    pub fn set_amplitude(&mut self, amplitude: f32) {
244        self.amplitude = amplitude.clamp(0.0, 1.0);
245    }
246
247    /// Get current amplitude
248    pub fn amplitude(&self) -> f32 {
249        self.amplitude
250    }
251}
252
253/// Waveform shaper for improved signal quality
254#[allow(dead_code)]
255struct WaveformShaper {
256    /// Rise time (in samples)
257    rise_time: usize,
258    /// Current transition progress
259    transition_progress: usize,
260    /// Target level
261    target_level: f32,
262    /// Current level
263    current_level: f32,
264}
265
266impl WaveformShaper {
267    #[allow(dead_code)]
268    fn new(sample_rate: u32, rise_time_us: f32) -> Self {
269        let rise_time = ((rise_time_us / 1_000_000.0) * sample_rate as f32) as usize;
270
271        WaveformShaper {
272            rise_time: rise_time.max(1),
273            transition_progress: 0,
274            target_level: 0.0,
275            current_level: 0.0,
276        }
277    }
278
279    /// Set target level for transition
280    #[allow(dead_code)]
281    fn set_target(&mut self, level: f32) {
282        if (level - self.target_level).abs() > 0.001 {
283            self.target_level = level;
284            self.transition_progress = 0;
285        }
286    }
287
288    /// Get next shaped sample
289    #[allow(dead_code)]
290    fn next_sample(&mut self) -> f32 {
291        if self.transition_progress < self.rise_time {
292            // Linear interpolation during transition
293            let progress = self.transition_progress as f32 / self.rise_time as f32;
294            self.current_level =
295                self.current_level * (1.0 - progress) + self.target_level * progress;
296            self.transition_progress += 1;
297        } else {
298            self.current_level = self.target_level;
299        }
300
301        self.current_level
302    }
303
304    #[allow(dead_code)]
305    fn reset(&mut self) {
306        self.transition_progress = 0;
307        self.current_level = 0.0;
308    }
309}
310
311/// Pre-emphasis filter for tape recording
312#[allow(dead_code)]
313struct PreEmphasisFilter {
314    /// Filter coefficient
315    alpha: f32,
316    /// Previous input
317    prev_input: f32,
318    /// Previous output
319    prev_output: f32,
320}
321
322impl PreEmphasisFilter {
323    #[allow(dead_code)]
324    fn new(time_constant_us: f32, sample_rate: u32) -> Self {
325        let tc = time_constant_us / 1_000_000.0;
326        let dt = 1.0 / sample_rate as f32;
327        let alpha = tc / (tc + dt);
328
329        PreEmphasisFilter {
330            alpha,
331            prev_input: 0.0,
332            prev_output: 0.0,
333        }
334    }
335
336    /// Apply pre-emphasis to sample
337    #[allow(dead_code)]
338    fn process(&mut self, input: f32) -> f32 {
339        let output = self.alpha * (self.prev_output + input - self.prev_input);
340        self.prev_input = input;
341        self.prev_output = output;
342        output
343    }
344
345    #[allow(dead_code)]
346    fn reset(&mut self) {
347        self.prev_input = 0.0;
348        self.prev_output = 0.0;
349    }
350}
351
352/// DC offset remover
353#[allow(dead_code)]
354struct DcBlocker {
355    /// Filter coefficient
356    alpha: f32,
357    /// Previous input
358    prev_input: f32,
359    /// Previous output
360    prev_output: f32,
361}
362
363impl DcBlocker {
364    #[allow(dead_code)]
365    fn new(cutoff_hz: f32, sample_rate: u32) -> Self {
366        let rc = 1.0 / (2.0 * std::f32::consts::PI * cutoff_hz);
367        let dt = 1.0 / sample_rate as f32;
368        let alpha = rc / (rc + dt);
369
370        DcBlocker {
371            alpha,
372            prev_input: 0.0,
373            prev_output: 0.0,
374        }
375    }
376
377    /// Remove DC offset from sample
378    #[allow(dead_code)]
379    fn process(&mut self, input: f32) -> f32 {
380        let output = self.alpha * (self.prev_output + input - self.prev_input);
381        self.prev_input = input;
382        self.prev_output = output;
383        output
384    }
385
386    #[allow(dead_code)]
387    fn reset(&mut self) {
388        self.prev_input = 0.0;
389        self.prev_output = 0.0;
390    }
391}
392
393/// Amplitude limiter
394#[allow(dead_code)]
395struct Limiter {
396    /// Threshold (0.0 to 1.0)
397    threshold: f32,
398    /// Attack time (in samples)
399    attack_samples: usize,
400    /// Release time (in samples)
401    release_samples: usize,
402    /// Current gain reduction
403    gain_reduction: f32,
404}
405
406impl Limiter {
407    #[allow(dead_code)]
408    fn new(threshold: f32, attack_ms: f32, release_ms: f32, sample_rate: u32) -> Self {
409        let attack_samples = ((attack_ms / 1000.0) * sample_rate as f32) as usize;
410        let release_samples = ((release_ms / 1000.0) * sample_rate as f32) as usize;
411
412        Limiter {
413            threshold,
414            attack_samples: attack_samples.max(1),
415            release_samples: release_samples.max(1),
416            gain_reduction: 1.0,
417        }
418    }
419
420    /// Apply limiting to sample
421    #[allow(dead_code)]
422    fn process(&mut self, input: f32) -> f32 {
423        let abs_input = input.abs();
424
425        if abs_input > self.threshold {
426            // Attack: reduce gain quickly
427            let target_gain = self.threshold / abs_input;
428            let attack_coefficient = 1.0 / self.attack_samples as f32;
429            self.gain_reduction += (target_gain - self.gain_reduction) * attack_coefficient;
430        } else {
431            // Release: increase gain slowly
432            let release_coefficient = 1.0 / self.release_samples as f32;
433            self.gain_reduction += (1.0 - self.gain_reduction) * release_coefficient;
434        }
435
436        input * self.gain_reduction
437    }
438
439    #[allow(dead_code)]
440    fn reset(&mut self) {
441        self.gain_reduction = 1.0;
442    }
443}
444
445/// LTC frame buffer for continuous encoding
446pub struct LtcFrameBuffer {
447    /// Sample rate
448    sample_rate: u32,
449    /// Frame rate
450    frame_rate: FrameRate,
451    /// Amplitude
452    amplitude: f32,
453    /// Buffered samples
454    buffer: Vec<f32>,
455    /// Current timecode
456    current_timecode: Option<Timecode>,
457}
458
459impl LtcFrameBuffer {
460    /// Create a new frame buffer
461    pub fn new(sample_rate: u32, frame_rate: FrameRate, amplitude: f32) -> Self {
462        LtcFrameBuffer {
463            sample_rate,
464            frame_rate,
465            amplitude,
466            buffer: Vec::new(),
467            current_timecode: None,
468        }
469    }
470
471    /// Set the starting timecode
472    pub fn set_timecode(&mut self, timecode: Timecode) {
473        self.current_timecode = Some(timecode);
474    }
475
476    /// Generate samples for the next frame
477    pub fn generate_frame(&mut self) -> Result<Vec<f32>, TimecodeError> {
478        if let Some(ref mut tc) = self.current_timecode {
479            let mut encoder = LtcEncoder::new(self.sample_rate, self.frame_rate, self.amplitude);
480            let samples = encoder.encode_frame(tc)?;
481
482            // Increment timecode for next frame
483            tc.increment()?;
484
485            Ok(samples)
486        } else {
487            Err(TimecodeError::InvalidConfiguration)
488        }
489    }
490
491    /// Fill buffer with samples up to a target duration
492    pub fn fill_buffer(&mut self, target_samples: usize) -> Result<(), TimecodeError> {
493        while self.buffer.len() < target_samples {
494            let frame_samples = self.generate_frame()?;
495            self.buffer.extend_from_slice(&frame_samples);
496        }
497        Ok(())
498    }
499
500    /// Read samples from buffer
501    pub fn read_samples(&mut self, count: usize) -> Vec<f32> {
502        let available = self.buffer.len().min(count);
503        let samples: Vec<f32> = self.buffer.drain(..available).collect();
504        samples
505    }
506
507    /// Get buffer level
508    pub fn buffer_level(&self) -> usize {
509        self.buffer.len()
510    }
511}
512
513/// User bits encoder helpers
514pub struct UserBitsEncoder;
515
516impl UserBitsEncoder {
517    /// Encode ASCII string to user bits (8 characters max)
518    pub fn encode_ascii(text: &str) -> u32 {
519        let bytes = text.as_bytes();
520        let mut user_bits = 0u32;
521
522        for (i, &byte) in bytes.iter().take(4).enumerate() {
523            user_bits |= (byte as u32) << (i * 8);
524        }
525
526        user_bits
527    }
528
529    /// Encode timecode date (MMDDYYYY format, packed BCD)
530    pub fn encode_date(month: u8, day: u8, year: u16) -> u32 {
531        let mut user_bits = 0u32;
532
533        // Month (MM)
534        user_bits |= (month / 10) as u32;
535        user_bits |= ((month % 10) as u32) << 4;
536
537        // Day (DD)
538        user_bits |= ((day / 10) as u32) << 8;
539        user_bits |= ((day % 10) as u32) << 12;
540
541        // Year (YYYY) - last two digits
542        let year_short = (year % 100) as u8;
543        user_bits |= ((year_short / 10) as u32) << 16;
544        user_bits |= ((year_short % 10) as u32) << 20;
545
546        user_bits
547    }
548
549    /// Encode binary data directly
550    pub fn encode_binary(data: u32) -> u32 {
551        data
552    }
553}
554
555/// Signal quality metrics
556pub struct SignalQualityMetrics {
557    /// Peak amplitude
558    pub peak_amplitude: f32,
559    /// RMS amplitude
560    pub rms_amplitude: f32,
561    /// Crest factor (peak/RMS)
562    pub crest_factor: f32,
563    /// DC offset
564    pub dc_offset: f32,
565}
566
567impl SignalQualityMetrics {
568    /// Calculate metrics from samples
569    pub fn from_samples(samples: &[f32]) -> Self {
570        let mut sum = 0.0;
571        let mut sum_squared = 0.0;
572        let mut peak: f32 = 0.0;
573
574        for &sample in samples {
575            sum += sample;
576            sum_squared += sample * sample;
577            peak = peak.max(sample.abs());
578        }
579
580        let dc_offset = sum / samples.len() as f32;
581        let rms = (sum_squared / samples.len() as f32).sqrt();
582        let crest_factor = if rms > 0.0 { peak / rms } else { 0.0 };
583
584        SignalQualityMetrics {
585            peak_amplitude: peak,
586            rms_amplitude: rms,
587            crest_factor,
588            dc_offset,
589        }
590    }
591}
592
593#[cfg(test)]
594mod tests {
595    use super::*;
596
597    #[test]
598    fn test_encoder_creation() {
599        let encoder = LtcEncoder::new(48000, FrameRate::Fps25, 0.5);
600        assert_eq!(encoder.amplitude(), 0.5);
601    }
602
603    #[test]
604    fn test_encode_frame() {
605        let mut encoder = LtcEncoder::new(48000, FrameRate::Fps25, 0.5);
606        let timecode = Timecode::new(1, 2, 3, 4, FrameRate::Fps25).expect("valid timecode");
607        let samples = encoder
608            .encode_frame(&timecode)
609            .expect("encode should succeed");
610        assert!(!samples.is_empty());
611    }
612
613    #[test]
614    fn test_user_bits_ascii() {
615        let user_bits = UserBitsEncoder::encode_ascii("TEST");
616        assert_ne!(user_bits, 0);
617    }
618
619    #[test]
620    fn test_user_bits_date() {
621        let user_bits = UserBitsEncoder::encode_date(12, 31, 2023);
622        assert_ne!(user_bits, 0);
623    }
624
625    #[test]
626    fn test_even_parity() {
627        let encoder = LtcEncoder::new(48000, FrameRate::Fps25, 0.5);
628        let bits = [true, false, true]; // 2 true bits = even
629        assert!(!encoder.calculate_even_parity(&bits));
630
631        let bits = [true, false, false]; // 1 true bit = odd
632        assert!(encoder.calculate_even_parity(&bits));
633    }
634}