use super::constants::*;
use crate::{FrameRate, Timecode, TimecodeError};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[allow(dead_code)]
enum DecoderState {
Searching,
Locked,
LostSync,
}
pub struct LtcDecoder {
#[allow(dead_code)]
sample_rate: u32,
frame_rate: FrameRate,
min_amplitude: f32,
state: DecoderState,
bit_buffer: [bool; BITS_PER_FRAME],
bit_position: usize,
zero_crossing: ZeroCrossingDetector,
bit_sync: BitSynchronizer,
last_timecode: Option<Timecode>,
sync_confidence: u32,
error_count: u32,
}
impl LtcDecoder {
pub fn new(sample_rate: u32, frame_rate: FrameRate, min_amplitude: f32) -> Self {
LtcDecoder {
sample_rate,
frame_rate,
min_amplitude,
state: DecoderState::Searching,
bit_buffer: [false; BITS_PER_FRAME],
bit_position: 0,
zero_crossing: ZeroCrossingDetector::new(sample_rate, frame_rate),
bit_sync: BitSynchronizer::new(sample_rate, frame_rate),
last_timecode: None,
sync_confidence: 0,
error_count: 0,
}
}
pub fn process_samples(&mut self, samples: &[f32]) -> Result<Option<Timecode>, TimecodeError> {
let mut result = None;
for &sample in samples {
if let Some(transition) = self
.zero_crossing
.process_sample(sample, self.min_amplitude)
{
if let Some(bit) = self.bit_sync.process_transition(transition) {
if let Some(tc) = self.process_bit(bit)? {
result = Some(tc);
}
}
}
}
Ok(result)
}
fn process_bit(&mut self, bit: bool) -> Result<Option<Timecode>, TimecodeError> {
self.bit_buffer[self.bit_position] = bit;
self.bit_position += 1;
if self.bit_position >= BITS_PER_FRAME {
self.bit_position = 0;
match self.decode_frame() {
Ok(timecode) => {
self.state = DecoderState::Locked;
self.sync_confidence = self.sync_confidence.saturating_add(1).min(100);
self.error_count = 0;
self.last_timecode = Some(timecode);
return Ok(Some(timecode));
}
Err(_) => {
self.error_count += 1;
if self.error_count > 10 {
self.state = DecoderState::LostSync;
self.sync_confidence = 0;
}
}
}
}
Ok(None)
}
fn decode_frame(&self) -> Result<Timecode, TimecodeError> {
let sync_pos = self.find_sync_word()?;
let mut data_bits = [false; DATA_BITS];
for (i, data_bit) in data_bits.iter_mut().enumerate().take(DATA_BITS) {
let pos = (sync_pos + BITS_PER_FRAME - SYNC_BITS - DATA_BITS + i) % BITS_PER_FRAME;
*data_bit = self.bit_buffer[pos];
}
self.decode_timecode_from_bits(&data_bits)
}
fn find_sync_word(&self) -> Result<usize, TimecodeError> {
let sync_bits = self.u16_to_bits(SYNC_WORD);
for start_pos in 0..BITS_PER_FRAME {
let mut match_count = 0;
for (i, &sync_bit) in sync_bits.iter().enumerate().take(SYNC_BITS) {
let pos = (start_pos + i) % BITS_PER_FRAME;
if self.bit_buffer[pos] == sync_bit {
match_count += 1;
}
}
if match_count >= SYNC_BITS - 2 {
return Ok(start_pos);
}
}
Err(TimecodeError::SyncNotFound)
}
fn u16_to_bits(&self, value: u16) -> [bool; 16] {
let mut bits = [false; 16];
for (i, bit) in bits.iter_mut().enumerate() {
*bit = (value & (1 << i)) != 0;
}
bits
}
fn decode_timecode_from_bits(
&self,
bits: &[bool; DATA_BITS],
) -> Result<Timecode, TimecodeError> {
let frame_units = self.bits_to_u8(&bits[0..4]);
let frame_tens = self.bits_to_u8(&bits[8..10]);
let frames = frame_tens * 10 + frame_units;
let second_units = self.bits_to_u8(&bits[16..20]);
let second_tens = self.bits_to_u8(&bits[24..27]);
let seconds = second_tens * 10 + second_units;
let minute_units = self.bits_to_u8(&bits[32..36]);
let minute_tens = self.bits_to_u8(&bits[40..43]);
let minutes = minute_tens * 10 + minute_units;
let hour_units = self.bits_to_u8(&bits[48..52]);
let hour_tens = self.bits_to_u8(&bits[56..58]);
let hours = hour_tens * 10 + hour_units;
let drop_frame = bits[10];
let user_bits = self.extract_user_bits(bits);
let frame_rate = if drop_frame && self.frame_rate == FrameRate::Fps2997NDF {
FrameRate::Fps2997DF
} else {
self.frame_rate
};
let mut timecode = Timecode::new(hours, minutes, seconds, frames, frame_rate)?;
timecode.user_bits = user_bits;
Ok(timecode)
}
fn bits_to_u8(&self, bits: &[bool]) -> u8 {
let mut value = 0u8;
for (i, &bit) in bits.iter().enumerate() {
if bit {
value |= 1 << i;
}
}
value
}
fn extract_user_bits(&self, bits: &[bool; DATA_BITS]) -> u32 {
let mut user_bits = 0u32;
user_bits |= self.bits_to_u8(&bits[4..8]) as u32;
user_bits |= (self.bits_to_u8(&bits[12..16]) as u32) << 4;
user_bits |= (self.bits_to_u8(&bits[20..24]) as u32) << 8;
user_bits |= (self.bits_to_u8(&bits[28..32]) as u32) << 12;
user_bits |= (self.bits_to_u8(&bits[36..40]) as u32) << 16;
user_bits |= (self.bits_to_u8(&bits[44..48]) as u32) << 20;
user_bits |= (self.bits_to_u8(&bits[52..56]) as u32) << 24;
user_bits |= (self.bits_to_u8(&bits[59..63]) as u32) << 28;
user_bits
}
pub fn reset(&mut self) {
self.state = DecoderState::Searching;
self.bit_position = 0;
self.sync_confidence = 0;
self.error_count = 0;
self.zero_crossing.reset();
self.bit_sync.reset();
}
pub fn last_decoded_timecode(&self) -> Option<Timecode> {
self.last_timecode
}
pub fn is_synchronized(&self) -> bool {
self.state == DecoderState::Locked && self.sync_confidence >= 10
}
pub fn sync_confidence(&self) -> f32 {
(self.sync_confidence as f32) / 100.0
}
}
#[allow(dead_code)]
struct ZeroCrossingDetector {
prev_sample: f32,
sample_count: u64,
samples_per_bit: f32,
}
impl ZeroCrossingDetector {
fn new(sample_rate: u32, frame_rate: FrameRate) -> Self {
let fps = frame_rate.as_float();
let bits_per_second = fps * BITS_PER_FRAME as f64;
let samples_per_bit = sample_rate as f64 / bits_per_second;
ZeroCrossingDetector {
prev_sample: 0.0,
sample_count: 0,
samples_per_bit: samples_per_bit as f32,
}
}
fn process_sample(&mut self, sample: f32, min_amplitude: f32) -> Option<Transition> {
self.sample_count += 1;
let transition = if self.prev_sample < -min_amplitude && sample >= min_amplitude {
Some(Transition {
sample_index: self.sample_count,
rising: true,
})
} else if self.prev_sample > min_amplitude && sample <= -min_amplitude {
Some(Transition {
sample_index: self.sample_count,
rising: false,
})
} else {
None
};
self.prev_sample = sample;
transition
}
fn reset(&mut self) {
self.prev_sample = 0.0;
self.sample_count = 0;
}
}
#[derive(Debug, Clone, Copy)]
#[allow(dead_code)]
struct Transition {
sample_index: u64,
rising: bool,
}
struct BitSynchronizer {
last_transition: Option<u64>,
samples_per_bit: f32,
bit_phase: f32,
bit_clock: f32,
pll_filter: PllFilter,
}
impl BitSynchronizer {
fn new(sample_rate: u32, frame_rate: FrameRate) -> Self {
let fps = frame_rate.as_float();
let bits_per_second = fps * BITS_PER_FRAME as f64;
let samples_per_bit = sample_rate as f64 / bits_per_second;
BitSynchronizer {
last_transition: None,
samples_per_bit: samples_per_bit as f32,
bit_phase: 0.0,
bit_clock: 0.0,
pll_filter: PllFilter::new(0.1),
}
}
fn process_transition(&mut self, transition: Transition) -> Option<bool> {
let sample_index = transition.sample_index;
if let Some(last_idx) = self.last_transition {
let samples_since_last = (sample_index - last_idx) as f32;
let phase_error = samples_since_last - self.samples_per_bit;
let correction = self.pll_filter.update(phase_error);
self.samples_per_bit += correction;
let is_half_bit = samples_since_last < (self.samples_per_bit * 0.75);
self.last_transition = Some(sample_index);
if is_half_bit {
self.bit_clock = 0.5;
return Some(true);
} else {
self.bit_clock = 0.0;
return Some(false);
}
}
self.last_transition = Some(sample_index);
None
}
fn reset(&mut self) {
self.last_transition = None;
self.bit_phase = 0.0;
self.bit_clock = 0.0;
self.pll_filter.reset();
}
}
struct PllFilter {
gain: f32,
integrator: f32,
}
impl PllFilter {
fn new(gain: f32) -> Self {
PllFilter {
gain,
integrator: 0.0,
}
}
fn update(&mut self, phase_error: f32) -> f32 {
let proportional = phase_error * self.gain;
self.integrator += phase_error * self.gain * 0.01;
self.integrator = self.integrator.clamp(-10.0, 10.0);
proportional + self.integrator
}
fn reset(&mut self) {
self.integrator = 0.0;
}
}
#[allow(dead_code)]
struct SignalFilter {
alpha: f32,
prev_value: f32,
}
impl SignalFilter {
#[allow(dead_code)]
fn new(cutoff_freq: f32, sample_rate: f32) -> Self {
let rc = 1.0 / (2.0 * std::f32::consts::PI * cutoff_freq);
let dt = 1.0 / sample_rate;
let alpha = dt / (rc + dt);
SignalFilter {
alpha,
prev_value: 0.0,
}
}
#[allow(dead_code)]
fn process(&mut self, sample: f32) -> f32 {
let filtered = self.alpha * sample + (1.0 - self.alpha) * self.prev_value;
self.prev_value = filtered;
filtered
}
#[allow(dead_code)]
fn reset(&mut self) {
self.prev_value = 0.0;
}
}
#[allow(dead_code)]
struct WaveformAnalyzer {
rms_accumulator: f32,
rms_count: u32,
peak_positive: f32,
peak_negative: f32,
}
impl WaveformAnalyzer {
#[allow(dead_code)]
fn new() -> Self {
WaveformAnalyzer {
rms_accumulator: 0.0,
rms_count: 0,
peak_positive: 0.0,
peak_negative: 0.0,
}
}
#[allow(dead_code)]
fn process_sample(&mut self, sample: f32) {
self.rms_accumulator += sample * sample;
self.rms_count += 1;
if sample > self.peak_positive {
self.peak_positive = sample;
}
if sample < self.peak_negative {
self.peak_negative = sample;
}
}
#[allow(dead_code)]
fn get_rms(&self) -> f32 {
if self.rms_count > 0 {
(self.rms_accumulator / self.rms_count as f32).sqrt()
} else {
0.0
}
}
#[allow(dead_code)]
fn get_peak_to_peak(&self) -> f32 {
self.peak_positive - self.peak_negative
}
#[allow(dead_code)]
fn reset(&mut self) {
self.rms_accumulator = 0.0;
self.rms_count = 0;
self.peak_positive = 0.0;
self.peak_negative = 0.0;
}
}
#[allow(dead_code)]
struct DropFrameCalculator;
impl DropFrameCalculator {
#[allow(dead_code)]
fn is_valid_drop_frame(minutes: u8, seconds: u8, frames: u8) -> bool {
if seconds == 0 && frames < 2 && !minutes.is_multiple_of(10) {
return false;
}
true
}
#[allow(dead_code)]
fn adjust_for_drop_frame(minutes: u8, seconds: u8, frames: u8) -> (u8, u8, u8) {
if seconds == 0 && frames < 2 && !minutes.is_multiple_of(10) {
(minutes, seconds, 2)
} else {
(minutes, seconds, frames)
}
}
}
#[allow(dead_code)]
struct ErrorCorrector {
history: Vec<Timecode>,
max_history: usize,
}
impl ErrorCorrector {
#[allow(dead_code)]
fn new(max_history: usize) -> Self {
ErrorCorrector {
history: Vec::with_capacity(max_history),
max_history,
}
}
#[allow(dead_code)]
fn add_timecode(&mut self, timecode: Timecode) {
self.history.push(timecode);
if self.history.len() > self.max_history {
self.history.remove(0);
}
}
#[allow(dead_code)]
fn correct_timecode(&self, timecode: &Timecode) -> Option<Timecode> {
if self.history.is_empty() {
return Some(*timecode);
}
if let Some(last) = self.history.last() {
let mut expected = *last;
if expected.increment().is_ok() {
if Self::is_close(timecode, &expected) {
return Some(*timecode);
}
}
}
Some(*timecode)
}
#[allow(dead_code)]
fn is_close(tc1: &Timecode, tc2: &Timecode) -> bool {
let diff = (tc1.to_frames() as i64 - tc2.to_frames() as i64).abs();
diff <= 5
}
#[allow(dead_code)]
fn reset(&mut self) {
self.history.clear();
}
}
#[allow(dead_code)]
struct BitPatternValidator;
impl BitPatternValidator {
#[allow(dead_code)]
fn validate_timecode_bits(bits: &[bool; DATA_BITS]) -> bool {
let frame_tens = Self::bits_to_u8(&bits[8..10]);
let second_tens = Self::bits_to_u8(&bits[24..27]);
let minute_tens = Self::bits_to_u8(&bits[40..43]);
let hour_tens = Self::bits_to_u8(&bits[56..58]);
if frame_tens > 5 {
return false;
}
if second_tens > 5 {
return false;
}
if minute_tens > 5 {
return false;
}
if hour_tens > 2 {
return false;
}
true
}
#[allow(dead_code)]
fn bits_to_u8(bits: &[bool]) -> u8 {
let mut value = 0u8;
for (i, &bit) in bits.iter().enumerate() {
if bit {
value |= 1 << i;
}
}
value
}
}
#[allow(dead_code)]
struct SpeedDetector {
bit_periods: Vec<f32>,
max_history: usize,
}
impl SpeedDetector {
#[allow(dead_code)]
fn new(max_history: usize) -> Self {
SpeedDetector {
bit_periods: Vec::with_capacity(max_history),
max_history,
}
}
#[allow(dead_code)]
fn add_period(&mut self, period: f32) {
self.bit_periods.push(period);
if self.bit_periods.len() > self.max_history {
self.bit_periods.remove(0);
}
}
#[allow(dead_code)]
fn get_average_period(&self) -> Option<f32> {
if self.bit_periods.is_empty() {
return None;
}
let sum: f32 = self.bit_periods.iter().sum();
Some(sum / self.bit_periods.len() as f32)
}
#[allow(dead_code)]
fn get_speed_ratio(&self, nominal_period: f32) -> f32 {
if let Some(avg) = self.get_average_period() {
nominal_period / avg
} else {
1.0
}
}
#[allow(dead_code)]
fn reset(&mut self) {
self.bit_periods.clear();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_decoder_creation() {
let decoder = LtcDecoder::new(48000, FrameRate::Fps25, 0.1);
assert!(!decoder.is_synchronized());
}
#[test]
fn test_zero_crossing_detector() {
let mut detector = ZeroCrossingDetector::new(48000, FrameRate::Fps25);
let t1 = detector.process_sample(-0.5, 0.1);
assert!(t1.is_none());
let t2 = detector.process_sample(0.5, 0.1);
assert!(t2.is_some());
}
#[test]
fn test_bits_to_u8() {
let decoder = LtcDecoder::new(48000, FrameRate::Fps25, 0.1);
let bits = [true, false, true, false]; assert_eq!(decoder.bits_to_u8(&bits), 5);
}
#[test]
fn test_drop_frame_validator() {
assert!(!DropFrameCalculator::is_valid_drop_frame(1, 0, 0));
assert!(!DropFrameCalculator::is_valid_drop_frame(1, 0, 1));
assert!(DropFrameCalculator::is_valid_drop_frame(1, 0, 2));
assert!(DropFrameCalculator::is_valid_drop_frame(10, 0, 0));
}
}