Skip to main content

ltc/
lib.rs

1//! This library allows for decoding linear time code audio into individual timecode frames. Encoding support is not currently offered.
2//! 
3//! Decoding centers around the [`LTCDecoder`] struct, representing the state of a single decoding process
4//! that produces a series of [`LTCFrame`] into an internal queue. Frames can be extracted from this
5//! queue using [`LTCDecoder`]'s [`IntoIterator`](https://doc.rust-lang.org/std/iter/trait.IntoIterator.html) implementation.
6//! 
7//! # Examples
8//! 
9//! ```
10//! use ltc::LTCDecoder;
11//! 
12//! // First argument specifies "samples per frame" which should
13//! // be the approximate number of audio samples that a single
14//! // frame takes up. Automatically adjusted to the rate of the
15//! // data internally with an exponential smoothing function.
16//! let decoder = LTCDecoder::with_capacity(48000.0 / 30.0, 10);
17//! 
18//! let buffer: &[f32] = ...;
19//! 
20//! if decoder.write_samples(buffer) {
21//!     println!("Decoded LTC frames!")
22//! 
23//!     for frame in &mut decoder {
24//!         println!("Got frame with time {}", frame.format_time())
25//!     }
26//! }
27//! 
28//! ```
29//! 
30//! # License
31//! 
32//! ```text
33//! Copyright (c) 2020, Danny Wensley
34//! All rights reserved.
35//! 
36//! Redistribution and use in source and binary forms, with or without
37//! modification, are permitted provided that the following conditions are met:
38//!     * Redistributions of source code must retain the above copyright
39//!       notice, this list of conditions and the following disclaimer.
40//!     * Redistributions in binary form must reproduce the above copyright
41//!       notice, this list of conditions and the following disclaimer in the
42//!       documentation and/or other materials provided with the distribution.
43//!     * Neither the name of the the copyright holder nor the
44//!       names of its contributors may be used to endorse or promote products
45//!       derived from this software without specific prior written permission.
46//! 
47//! THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
48//! ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
49//! WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
50//! DISCLAIMED. IN NO EVENT SHALL Danny Wensley BE LIABLE FOR ANY
51//! DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
52//! (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
53//! LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
54//! ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
55//! (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
56//! SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
57//! ```
58
59use std::collections::VecDeque;
60use std::fmt;
61
62/// Represents a single LTC frame obtained from an [`LTCDecoder`].
63/// 
64/// This struct represents the encoded data almost 1:1, that is, no extra processing is done. The one exception is that
65/// the hour, minute, second and frame are all decode for ease of use.
66/// 
67/// # Examples
68/// 
69/// ```
70/// let frame: LTCFrame = ...;
71/// 
72/// println!("Frame time: {}", frame.format_time());
73/// ```
74#[derive(Debug, PartialEq, Eq)]
75pub struct LTCFrame {
76    pub frame: u8,
77    pub second: u8,
78    pub minute: u8,
79    pub hour: u8,
80
81    pub user1: u8,
82    pub user2: u8,
83    pub user3: u8,
84    pub user4: u8,
85    pub user5: u8,
86    pub user6: u8,
87    pub user7: u8,
88    pub user8: u8,
89
90    pub drop_frame: bool,
91    pub color_frame: bool,
92
93    pub flag27: bool,
94    pub flag43: bool,
95    pub flag59: bool,
96
97    pub synchronised_externally: bool,
98
99    pub samples_length: u32,
100}
101
102impl LTCFrame {
103    /// Format the time of the frame as a string.
104    pub fn format_time(&self) -> String {
105        format!("{}:{}:{}:{}", self.hour, self.minute, self.second, self.frame)
106    }
107
108    /// Estimate the framerate of the frame.
109    /// 
110    /// By taking in a sample rate, this function uses the recorded length of the frame in samples
111    /// in order to make an estimation as to the framerate that this frame was part of. Does not
112    /// currently take into account drop rate timecode.
113    /// 
114    /// # Arguments
115    /// 
116    /// * `sample_rate` - The rate (in samples per second) that samples were fed to the decoder that
117    ///   produced this frame. Used to make an estimation of the framerate.
118    pub fn estimate_framerate(&self, sample_rate: f32) -> FramerateEstimate {
119        use FramerateEstimate::*;
120
121        match (sample_rate) / (self.samples_length as f32) {
122            r if 20.0 <= r && r < 24.5 => F24,
123            r if 24.5 <= r && r < 27.5 => F25,
124            r if 27.5 <= r && r < 35.0 => F30,
125            r => Unknown(r)
126        }
127    }
128}
129
130/// Represents an estimation of a framerate as produced by [`LTCFrame::estimate_framerate`]
131#[derive(Debug, PartialEq)]
132pub enum FramerateEstimate {
133    /// 24 frames per second (film, ATSC, 2k, 4k, 6k)
134    F24,
135    /// 25 frames per second (PAL, SECAM, DVB, ATSC)
136    F25,
137    /// 30 frames per second (ATSC, lighting, audio)
138    F30,
139
140    /// The estimated frame rate was not close enough to any known rates
141    Unknown(f32)
142}
143
144impl fmt::Display for FramerateEstimate {
145    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
146        use FramerateEstimate::*;
147
148        match self {
149            F24 => write!(f, "24"),
150            F25 => write!(f, "25"),
151            F30 => write!(f, "30"),
152            Unknown(r) => write!(f, "Unknown (~{})", r)
153        }
154    }
155}
156
157/// A decoder of LTC data. See top-level documentation for more information.
158pub struct LTCDecoder {
159    queue: VecDeque<LTCFrame>,
160
161    samples_per_period: f32,
162    change_rate_boundary: u32,
163
164    current_state: bool,
165    samples_since_change: u32,
166    samples_decoded: u32,
167
168    recognised_half_cycle: bool,
169
170    bit_buffer: u128,
171
172    lock_in: LockIn
173}
174
175enum LockIn {
176    None,
177    Forward,
178}
179
180const BITS_PER_FRAME: u8 = 80;
181const SIGNAL_THRESHOLD: f32 = 0.0;
182
183const SYNC_WORD_FORWARD: u16 = 0b0011_1111_1111_1101;
184
185impl LTCDecoder {
186    /// Initialises a new decoder.
187    /// 
188    /// # Arguments
189    /// 
190    /// * `samples_per_frame` - An approximate length of each frame based on the sample rate
191    ///   and the expected framerate. Should theoretically be fine as long as it is within
192    ///   30% of the actual value, as it is continuously adjusted with an exponential
193    ///   smoothing function at the end of each state-change.
194    /// * `queue` - An initial value for the internal queue. You likely want to supply an
195    ///   empty `VecDeque` here.
196    pub fn new(samples_per_frame: f32, queue: VecDeque<LTCFrame>) -> LTCDecoder {
197        let samples_per_period = samples_per_frame / BITS_PER_FRAME as f32;
198
199        LTCDecoder {
200            queue: queue,
201            
202            samples_per_period: samples_per_period,
203            change_rate_boundary: ((samples_per_period * 3.0) / 4.0) as u32,
204
205            current_state: false,
206            samples_since_change: std::u32::MAX,
207            samples_decoded: 0,
208
209            recognised_half_cycle: false,
210
211            bit_buffer: 0,
212
213            lock_in: LockIn::None,
214        }
215    }
216
217    /// Initialises a decoder with a specific queue capacity.
218    pub fn with_capacity(samples_per_frame: f32, capacity: usize) -> LTCDecoder {
219        Self::new(samples_per_frame, VecDeque::with_capacity(capacity))
220    }
221
222    /// Writes a new block of samples to the decoder.
223    /// 
224    /// Return value indicates whether the invocation has caused new frames to be added to the internal queue.
225    /// These can be then extracted by using `LTCDecoder`'s `IntoIterator` implementation to iterate over the
226    /// decoder.
227    /// 
228    /// This does not ever store a reference *or* copy of any of the samples.
229    pub fn write_samples(&mut self, samples: &[f32]) -> bool {
230        let mut parsed_frames = false;
231        
232        for sample in samples {
233            let new_state = *sample >= SIGNAL_THRESHOLD;
234
235            if self.current_state == new_state {
236                // We've not had a state change
237                // Increase time since last change
238                if self.samples_since_change < std::u32::MAX / 2 {
239                    self.samples_since_change += 1;
240                }
241            }else{
242                // We've had a state change
243                // Store the new state
244                self.current_state = new_state;
245
246                // If it's been more than four times a cycle length, ignore the change and reset the decoder, assuming dropped data
247                if self.samples_since_change > (self.samples_per_period * 4.0) as u32 {
248                    self.samples_since_change = 0;
249                    self.samples_decoded = 0;
250                    self.recognised_half_cycle = false;
251                    self.bit_buffer = 0;
252                    self.lock_in = LockIn::None;
253
254                    continue;
255                }
256
257                // Decide if it's been a half-bit cycle or a full-bit cycle since the last change
258                let is_half_cycle = self.samples_since_change < self.change_rate_boundary;
259
260                // ---- Update the change_rate_boundary
261
262                // First calculate the implied cycle length based on number of samples since previous state change
263                let current_cycle_length =  if is_half_cycle {
264                    self.samples_since_change * 2
265                }else{
266                    self.samples_since_change
267                };
268
269                // Then update the samples_per_period based on an exponential smoothing function
270                self.samples_per_period = (self.samples_per_period * 3.0 + current_cycle_length as f32) / 4.0;
271
272                // Finally update change_rate_boundary based on the new value of samples_per_period
273                self.change_rate_boundary = ((self.samples_per_period * 3.0) / 4.0) as u32;
274
275                // Make note of how many samples we just decoded
276                self.samples_decoded += self.samples_since_change;
277
278                // Also reset the counter so that we know we're at the start of a new state
279                self.samples_since_change = 0;
280
281                // ---- Interpret the state transition
282
283                if is_half_cycle {
284                    // The just-detected state-change represents the end of a half-cycle
285                    // Is this already halfway through a cycle
286                    if self.recognised_half_cycle {
287                        // The just-detected state-change is the second in a pair representing a 1
288                        // Shift the buffer and add the one
289                        self.bit_buffer = (self.bit_buffer << 1) + 1;
290                        // Reset the flag
291                        self.recognised_half_cycle = false;
292                    }else{
293                        // The just-detected state-change must be through the middle of a bit. Set a flag to remember it.
294                        self.recognised_half_cycle = true;
295
296                        // We don't want to try and parse the buffer, so continue
297                        continue;
298                    }
299                }else{
300                    // The just detected state-change represents the end of a full cycle
301                    // Sanity check if we were halfway through a cycle
302                    if self.recognised_half_cycle {
303                        // We thought we were halfway through a cycle, but we must've been off! Ignore the change and reset the decoder.
304
305                        self.samples_decoded = 0;
306                        self.recognised_half_cycle = false;
307                        self.bit_buffer = 0;
308                        self.lock_in = LockIn::None;
309
310                        continue;
311                    }else{
312                        // We've just recognised a full-cycle state period, meaning we should decode a 0
313                        // Shift buffer to the left by 1 bit, automatically filling in a zero
314                        self.bit_buffer <<= 1;
315                    }
316                }
317
318                // If we've got to here, then we've just successfully decoded a bit
319                // Delegate in order to attempt parsing an LTC frame!
320
321                parsed_frames |= self.try_parse_frame();
322            }
323        }
324
325        parsed_frames
326    }
327
328    fn try_parse_frame(&mut self) -> bool {
329        let latest_word = (self.bit_buffer & 0xFF_FF) as u16;
330        if latest_word == SYNC_WORD_FORWARD {
331            // We've just seen the sync word moving forwards
332            // Check whether we're locked in
333            match self.lock_in {
334                LockIn::Forward => {
335                    // We're locked in, meaning we can successfully decode a full LTC frame!
336
337                    let hour =
338                        if self.bit_buffer >> 22 & 0x1 > 0 { 20 } else { 0 } +
339                        if self.bit_buffer >> 23 & 0x1 > 0 { 10 } else { 0 } +
340                        if self.bit_buffer >> 28 & 0x1 > 0 { 8 } else { 0 } +
341                        if self.bit_buffer >> 29 & 0x1 > 0 { 4 } else { 0 } +
342                        if self.bit_buffer >> 30 & 0x1 > 0 { 2 } else { 0 } +
343                        (self.bit_buffer >> 31 & 0x1) as u8;
344                    let minute =
345                        if self.bit_buffer >> 37 & 0x1 > 0 { 40 } else { 0 } +
346                        if self.bit_buffer >> 38 & 0x1 > 0 { 20 } else { 0 } +
347                        if self.bit_buffer >> 39 & 0x1 > 0 { 10 } else { 0 } +
348                        if self.bit_buffer >> 44 & 0x1 > 0 { 8 } else { 0 } +
349                        if self.bit_buffer >> 45 & 0x1 > 0 { 4 } else { 0 } +
350                        if self.bit_buffer >> 46 & 0x1 > 0 { 2 } else { 0 } +
351                        (self.bit_buffer >> 47 & 0x1) as u8;
352                    let second =
353                        if self.bit_buffer >> 53 & 0x1 > 0 { 40 } else { 0 } +
354                        if self.bit_buffer >> 54 & 0x1 > 0 { 20 } else { 0 } +
355                        if self.bit_buffer >> 55 & 0x1 > 0 { 10 } else { 0 } +
356                        if self.bit_buffer >> 60 & 0x1 > 0 { 8 } else { 0 } +
357                        if self.bit_buffer >> 61 & 0x1 > 0 { 4 } else { 0 } +
358                        if self.bit_buffer >> 62 & 0x1 > 0 { 2 } else { 0 } +
359                        (self.bit_buffer >> 63 & 0x1) as u8;
360                    let frame =
361                        if self.bit_buffer >> 70 & 0x1 > 0 { 20 } else { 0 } +
362                        if self.bit_buffer >> 71 & 0x1 > 0 { 10 } else { 0 } +
363                        if self.bit_buffer >> 76 & 0x1 > 0 { 8 } else { 0 } +
364                        if self.bit_buffer >> 77 & 0x1 > 0 { 4 } else { 0 } +
365                        if self.bit_buffer >> 78 & 0x1 > 0 { 2 } else { 0 } +
366                        (self.bit_buffer >> 79 & 0x1) as u8;
367
368                    self.queue.push_back(LTCFrame {
369                        hour, minute, second, frame,
370
371                        user1: Self::reverse_user_data((self.bit_buffer >> 72) as u8 & 0xF),
372
373                        drop_frame: self.bit_buffer >> 69 & 0x1 > 0,
374                        color_frame: self.bit_buffer >> 68 & 0x1 > 0,
375
376                        user2: Self::reverse_user_data((self.bit_buffer >> 64) as u8 & 0xF),
377                        user3: Self::reverse_user_data((self.bit_buffer >> 56) as u8 & 0xF),
378
379                        flag27: self.bit_buffer >> 52 & 0x1 > 0,
380
381                        user4: Self::reverse_user_data((self.bit_buffer >> 48) as u8 & 0xF),
382                        user5: Self::reverse_user_data((self.bit_buffer >> 40) as u8 & 0xF),
383
384                        flag43: self.bit_buffer >> 36 & 0x1 > 0,
385
386                        user6: Self::reverse_user_data((self.bit_buffer >> 32) as u8 & 0xF),
387                        user7: Self::reverse_user_data((self.bit_buffer >> 24) as u8 & 0xF),
388                        
389                        synchronised_externally: self.bit_buffer >> 21 & 0x1 > 0,
390                        flag59: self.bit_buffer >> 20 & 0x1 > 0,
391
392                        user8: Self::reverse_user_data((self.bit_buffer >> 16) as u8 & 0xF),
393
394                        samples_length: self.samples_decoded
395                    });
396
397                    // We've just decoded a frame, reset number of samples decoded
398                    self.samples_decoded = 0;
399
400                    return true
401                },
402                _ => {
403                    // We're not currently locked in, but we can lock in forwards as we've just seen the sync word
404                    self.lock_in = LockIn::Forward;
405
406                    // We've not decoded anything, but we've essentially discarded all current data,
407                    // so we should still reset the counter
408                    self.samples_decoded = 0;
409
410                    return false
411                }
412            }
413        }else{
414            // We can't see the sync word. Likely just means we're in the middle of a frame.
415            // TODO: Potentially check for a backward sync word?
416            return false
417        }
418    }
419
420    fn reverse_user_data(data: u8) -> u8 {
421        return if data & 0b0001 > 0 { 0b1000 } else { 0 } +
422            if data & 0b0010 > 0 { 0b0100 } else { 0 } +
423            if data & 0b0100 > 0 { 0b0010 } else { 0 } +
424            if data & 0b1000 > 0 { 0b0001 } else { 0 }
425    }
426}
427
428/// Converts a mutable reference to a decoder into an iterator.
429/// 
430/// Mutability is needed as any items returned from the iterator are removed
431/// from the internal queue.
432impl<'a> IntoIterator for &'a mut LTCDecoder {
433    type Item = LTCFrame;
434    type IntoIter = LTCDecoderIter<'a>;
435
436    fn into_iter(self) -> LTCDecoderIter<'a> {
437        LTCDecoderIter {
438            queue: &mut self.queue
439        }
440    }
441}
442
443/// Represents an iterator of the internal queue of an [`LTCDecoder`].
444/// 
445/// Any items returned from iterating over this are removed from the queue
446/// of the parent LTCDecoder.
447/// 
448/// The lifetime `'a` represents the decoder this iterator is derived from.
449pub struct LTCDecoderIter<'a> {
450    queue: &'a mut VecDeque<LTCFrame>
451}
452
453impl<'a> Iterator for LTCDecoderIter<'a> {
454    type Item = LTCFrame;
455
456    fn next(&mut self) -> Option<LTCFrame> {
457        self.queue.pop_front()
458    }
459}