diff --git a/examples/blits.rs b/examples/blits.rs index ea7c778..ca70489 100644 --- a/examples/blits.rs +++ b/examples/blits.rs @@ -1,3 +1,222 @@ -fn main() -> () { +//! bilts.rs +//! (c) 2021 Jamie Hardt. All rights reserved. +//! +//! This program demonstrates the creation of a wave file with a BLITS +//! ("Black and Lanes' Ident Tones for Surround") channel identification and +//! alignment signal. +//! +//! TODO: Pre-calculate the sine waves to speed up generation + +use std::f64; +use bwavfile::{WaveWriter, WaveFmt}; + +fn sine_wave(t: u64, amplitude : i32, wavelength : u32) -> i32 { + //I did it this way because I'm weird + Some(t).map(|i| (i as f64) * 2f64 * f64::consts::PI / wavelength as f64 ) + .map(|f| f.sin() ) + .map(|s| (s * amplitude as f64) as i32) + .unwrap() +} + +/// Return the corresponding f32 gain for a dbfs. +/// +/// Retval will always be positive +fn dbfs_to_f32(dbfs : f32) -> f32 { + 10f32.powf(dbfs / 20f32) +} + +fn dbfs_to_signed_int(dbfs: f32, bit_depth: u32) -> i32 { + let full_code = (1i32 << (bit_depth + 1)) - 1; + ((full_code as f32) * dbfs_to_f32(dbfs)) as i32 +} + + +#[derive(Clone, Copy, PartialEq)] +enum ToneBurst { + /// Tone of .0 frequency for .1 duration at .2 dBfs + Tone(f32, u64, f32), + /// Silence of .0 Duration + Silence(u64), +} + +impl ToneBurst { + fn duration(&self, sample_rate : u32) -> u64 { + match self { + Self::Tone(_, dur, _) => *dur * sample_rate as u64 / 1000, + Self::Silence(dur) => *dur * sample_rate as u64 / 1000 + } + } +} + +trait ToneBurstSignal { + + fn duration(&self, sample_rate: u32) -> u64; + + fn signal(&self, t: u64, sample_rate: u32, bit_depth: u32) -> i32; +} + +impl ToneBurstSignal for Vec { + fn duration(&self, sample_rate: u32) -> u64 { + self.iter().fold(0u64, |accum, &item| { + accum + &item.duration(sample_rate) + }) + } + + fn signal(&self, t: u64, sample_rate: u32, bit_depth: u32) -> i32 { + self.iter() + .scan(0u64, |accum, &item| { + let dur = item.duration(sample_rate); + let this_time_range = *accum..(*accum + dur); + *accum = *accum + dur; + Some( (this_time_range, item) ) + }) + .find(|(range, _)| range.contains(&t)) + .map(|(_, item)| { + match item { + ToneBurst::Tone(freq, _, dbfs) => { + let gain = dbfs_to_signed_int(dbfs, bit_depth); + sine_wave(t, gain, (sample_rate as f32 / freq) as u32) + }, + ToneBurst::Silence(_) => { + 0 + } + } + }).unwrap_or(0i32) + } +} + + +fn main() -> () { + + // BLITS Tone signal format + // From EBU Tech 3304 ยง4 - https://tech.ebu.ch/docs/tech/tech3304.pdf + let left_channel_sequence : Vec = vec![ + // channel ident + ToneBurst::Tone(880.0, 600, -18.0), + ToneBurst::Silence(200), + ToneBurst::Silence(4000), + + // LR ident + ToneBurst::Tone(1000.0, 1000, -18.0), + ToneBurst::Silence(300), + ToneBurst::Tone(1000.0, 300, -18.0), + ToneBurst::Silence(300), + ToneBurst::Tone(1000.0, 300, -18.0), + ToneBurst::Silence(300), + ToneBurst::Tone(1000.0, 300, -18.0), + ToneBurst::Silence(300), + ToneBurst::Tone(1000.0, 2000, -18.0), + ToneBurst::Silence(300), + + // Phase check, + ToneBurst::Tone(2000.0, 3000, -24.0), + ToneBurst::Silence(200) + ]; + + let right_channel_sequence : Vec = vec![ + // channel ident + ToneBurst::Silence(800), + ToneBurst::Tone(880.0, 600, -18.0), + ToneBurst::Silence(200), + ToneBurst::Silence(3200), + + // LR ident + ToneBurst::Tone(1000.0, 5100, -18.0), + ToneBurst::Silence(300), + + // Phase check, + ToneBurst::Tone(2000.0, 3000, -24.0), + ToneBurst::Silence(200) + ]; + + let center_channel_sequence : Vec = vec![ + // channel ident + ToneBurst::Silence(1600), + ToneBurst::Tone(1320.0, 600, -18.0), + ToneBurst::Silence(200), + ToneBurst::Silence(2400), + + // LR ident + ToneBurst::Silence(5400), + + // Phase check, + ToneBurst::Tone(2000.0, 3000, -24.0), + ToneBurst::Silence(200) + ]; + + let lfe_channel_sequence : Vec = vec![ + // channel ident + ToneBurst::Silence(2400), + ToneBurst::Tone(82.5, 600, -18.0), + ToneBurst::Silence(200), + ToneBurst::Silence(1600), + + // LR ident + ToneBurst::Silence(5400), + + // Phase check, + ToneBurst::Tone(2000.0, 3000, -24.0), + ToneBurst::Silence(200) + ]; + + let ls_channel_sequence : Vec = vec![ + // channel ident + ToneBurst::Silence(3200), + ToneBurst::Tone(660.0, 600, -18.0), + ToneBurst::Silence(200), + ToneBurst::Silence(800), + + // LR ident + ToneBurst::Silence(5400), + + // Phase check, + ToneBurst::Tone(2000.0, 3000, -24.0), + ToneBurst::Silence(200) + ]; + + let rs_channel_sequence : Vec = vec![ + // channel ident + ToneBurst::Silence(4000), + ToneBurst::Tone(660.0, 600, -18.0), + ToneBurst::Silence(200), + + // LR ident + ToneBurst::Silence(5400), + + // Phase check, + ToneBurst::Tone(2000.0, 3000, -24.0), + ToneBurst::Silence(200) + ]; + + + let sample_rate = 48000; + let bits_per_sample = 24; + + let length = [&left_channel_sequence, &right_channel_sequence, + ¢er_channel_sequence, &lfe_channel_sequence, + &ls_channel_sequence, &rs_channel_sequence].iter() + .map(|i| i.duration(sample_rate)).max().unwrap_or(0); + + println!("Will generate tone of length {} frames", &length); + + let frames = (0..=length).map(|frame| { + (left_channel_sequence.signal(frame, sample_rate, bits_per_sample), + right_channel_sequence.signal(frame, sample_rate, bits_per_sample), + center_channel_sequence.signal(frame, sample_rate, bits_per_sample), + lfe_channel_sequence.signal(frame, sample_rate, bits_per_sample), + ls_channel_sequence.signal(frame, sample_rate, bits_per_sample), + rs_channel_sequence.signal(frame, sample_rate, bits_per_sample)) + }); + + let format = WaveFmt::new_pcm_multichannel(sample_rate, bits_per_sample as u16, 0b111111); + + let file = WaveWriter::create("blits.wav", format).expect("Failed to create file"); + let mut fw = file.audio_frame_writer().expect("Failed to open audio writer"); + for frame in frames { + let buf = vec![frame.0, frame.1, frame.2, frame.3, frame.4, frame.5]; + fw.write_integer_frames(&buf).expect("Failed to write audio frame"); + } + fw.end().expect("Failed to close frame writer"); + } \ No newline at end of file