//! 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 //! TODO: Make tone onsets less snappy use std::f64; use std::io; extern crate bwavfile; use bwavfile::{Error, WaveFmt, WaveWriter}; #[macro_use] extern crate clap; use clap::{App, Arg}; 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: u16) -> i32 { let full_code: i32 = (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 (hz) for .1 duration (ms) at .2 dBfs Tone(f32, u64, f32), /// Silence of .0 Duration (ms) 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: u16) -> 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: u16) -> 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 create_blits_file(file_name: &str, sample_rate: u32, bits_per_sample: u16) -> Result<(), Error> { // 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 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); 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, 0b111111); let file = WaveWriter::create(file_name, format)?; let mut fw = file.audio_frame_writer()?; for frame in frames { let buf = vec![frame.0, frame.1, frame.2, frame.3, frame.4, frame.5]; fw.write_frames(&buf)?; } fw.end()?; Ok(()) } fn main() -> io::Result<()> { let matches = App::new("blits") .version(crate_version!()) .author(crate_authors!()) .about("Generate a BLITS 5.1 alignment tone.") .arg( Arg::with_name("sample_rate") .long("sample-rate") .short("s") .help("Sample rate of output") .default_value("48000"), ) .arg( Arg::with_name("bit_depth") .long("bit-depth") .short("b") .help("Bit depth of output") .default_value("24"), ) .arg( Arg::with_name("OUTPUT") .help("Output wave file") .default_value("blits.wav"), ) .get_matches(); let sample_rate = matches .value_of("sample_rate") .unwrap() .parse::() .expect("Failed to read sample rate"); let bits_per_sample = matches .value_of("bit_depth") .unwrap() .parse::() .expect("Failed to read bit depth"); let filename = matches.value_of("OUTPUT").unwrap(); match create_blits_file(&filename, sample_rate, bits_per_sample) { Err(Error::IOError(x)) => panic!("IO Error: {:?}", x), Err(err) => panic!("Error: {:?}", err), Ok(()) => Ok(()), } }