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}