From 5e563cddf82232f2f2b837b4159e26ad89f2a0e6 Mon Sep 17 00:00:00 2001 From: Wuelle Date: Thu, 30 Dec 2021 21:59:17 +0100 Subject: [PATCH 1/2] allow more types to be used as paths --- src/wavereader.rs | 8 +++++--- src/wavewriter.rs | 7 ++++--- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/wavereader.rs b/src/wavereader.rs index 5fa7b2e..e15614d 100644 --- a/src/wavereader.rs +++ b/src/wavereader.rs @@ -1,6 +1,8 @@ use std::fs::File; +use std::path::Path; + use std::io::SeekFrom; use std::io::Cursor; use std::io::{Read, Seek, BufReader}; @@ -198,7 +200,7 @@ pub struct WaveReader { impl WaveReader> { - pub fn open(path: &str) -> Result { + pub fn open>(path: P) -> Result { let f = File::open(path)?; let inner = BufReader::new(f); Ok( Self::new(inner)? ) @@ -211,7 +213,7 @@ impl WaveReader { /// /// A convenience that opens `path` and calls `Self::new()` - pub fn open_unbuffered(path: &str) -> Result { + pub fn open_unbuffered>(path: P) -> Result { let inner = File::open(path)?; return Ok( Self::new(inner)? ) } @@ -600,4 +602,4 @@ fn test_list_form() { assert_ne!(buf.len(), 0); -} \ No newline at end of file +} diff --git a/src/wavewriter.rs b/src/wavewriter.rs index bae5f5d..8f2520d 100644 --- a/src/wavewriter.rs +++ b/src/wavewriter.rs @@ -1,4 +1,5 @@ use std::fs::File; +use std::path::Path; use std::io::{Write,Seek,SeekFrom,Cursor,BufWriter}; use super::Error; @@ -213,7 +214,7 @@ const DS64_RESERVATION_LENGTH : u32 = 96; impl WaveWriter> { /// Create a new Wave file at `path`. - pub fn create(path : &str, format : WaveFmt) -> Result { + pub fn create>(path : P, format : WaveFmt) -> Result { let f = File::create(path)?; let b = BufWriter::new(f); Ok( Self::new(b, format)? ) @@ -222,7 +223,7 @@ impl WaveWriter> { impl WaveWriter { /// Creare a new Wave file with unbuffered IO at `path` - pub fn create_unbuffered(path : &str, format : WaveFmt) -> Result { + pub fn create_unbuffered>(path : P, format : WaveFmt) -> Result { let f = File::create(path)?; Ok( Self::new(f, format)? ) } @@ -521,4 +522,4 @@ fn test_create_rf64() { cursor.seek(SeekFrom::Current(data_size as i64)).unwrap(); assert_eq!(4 + 8 + ds64_size as u64 + 8 + data_size + 8 + fmt_size as u64 + 8 + elm1_size as u64, form_size) -} \ No newline at end of file +} From 9a010ca0c44dc16a5609b64f70248f7911cca043 Mon Sep 17 00:00:00 2001 From: Wuelle Date: Thu, 30 Dec 2021 21:59:29 +0100 Subject: [PATCH 2/2] prettify code --- examples/blits.rs | 205 +++++++++---------- examples/wave-deinter.rs | 79 ++++--- examples/wave-inter.rs | 6 +- src/bext.rs | 28 ++- src/chunks.rs | 239 +++++++++++++-------- src/common_format.rs | 66 +++--- src/cue.rs | 351 +++++++++++++++---------------- src/errors.rs | 22 +- src/fmt.rs | 299 +++++++++++++++------------ src/fourcc.rs | 79 ++++--- src/lib.rs | 38 ++-- src/list_form.rs | 26 +-- src/parser.rs | 168 ++++++++------- src/wavereader.rs | 387 +++++++++++++++++++---------------- src/wavewriter.rs | 275 +++++++++++++++---------- tests/ffprobe_media_tests.rs | 40 ++-- tests/integration_test.rs | 64 +++--- 17 files changed, 1312 insertions(+), 1060 deletions(-) diff --git a/examples/blits.rs b/examples/blits.rs index 5d13375..ad0e67d 100644 --- a/examples/blits.rs +++ b/examples/blits.rs @@ -1,10 +1,10 @@ //! 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 +//! ("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 @@ -12,34 +12,33 @@ use std::f64; use std::io; extern crate bwavfile; -use bwavfile::{WaveWriter, WaveFmt, Error}; +use bwavfile::{Error, WaveFmt, WaveWriter}; #[macro_use] extern crate clap; -use clap::{Arg, App}; +use clap::{App, Arg}; - -fn sine_wave(t: u64, amplitude : i32, wavelength : u32) -> i32 { +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() + 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 { +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; + 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 @@ -49,62 +48,54 @@ enum ToneBurst { } impl ToneBurst { - fn duration(&self, sample_rate : u32) -> u64 { + 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 + 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) - }) + self.iter() + .fold(0u64, |accum, &item| accum + &item.duration(sample_rate)) } - fn signal(&self, t: u64, sample_rate: u32, bit_depth: u16) -> i32 { + 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) ) + 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 - } + .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) } - }).unwrap_or(0i32) + ToneBurst::Silence(_) => 0, + }) + .unwrap_or(0i32) } } -fn create_blits_file(file_name: &str, sample_rate : u32, bits_per_sample : u16) -> Result<(),Error> { - +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 + 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), @@ -116,100 +107,98 @@ fn create_blits_file(file_name: &str, sample_rate : u32, bits_per_sample : u16) 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) + ToneBurst::Silence(200), ]; - let right_channel_sequence : Vec = vec![ + let right_channel_sequence: Vec = vec![ // channel ident - ToneBurst::Silence(800), + 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) + ToneBurst::Silence(200), ]; - let center_channel_sequence : Vec = vec![ + let center_channel_sequence: Vec = vec![ // channel ident - ToneBurst::Silence(1600), + 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) + ToneBurst::Silence(200), ]; - let lfe_channel_sequence : Vec = vec![ + let lfe_channel_sequence: Vec = vec![ // channel ident - ToneBurst::Silence(2400), + 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) + ToneBurst::Silence(200), ]; - let ls_channel_sequence : Vec = vec![ + let ls_channel_sequence: Vec = vec![ // channel ident - ToneBurst::Silence(3200), + 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) + ToneBurst::Silence(200), ]; - let rs_channel_sequence : Vec = vec![ + let rs_channel_sequence: Vec = vec![ // channel ident - ToneBurst::Silence(4000), + 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) + 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 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)) + ( + 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); @@ -227,40 +216,48 @@ fn create_blits_file(file_name: &str, sample_rate : u32, bits_per_sample : u16) } 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(); + .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::() + 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::() + 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(()) + Err(Error::IOError(x)) => panic!("IO Error: {:?}", x), + Err(err) => panic!("Error: {:?}", err), + Ok(()) => Ok(()), } } diff --git a/examples/wave-deinter.rs b/examples/wave-deinter.rs index 25ece63..29fc8f2 100644 --- a/examples/wave-deinter.rs +++ b/examples/wave-deinter.rs @@ -1,6 +1,6 @@ //! wave-inter.rs //! (c) 2021 Jamie Hardt. All rights reserved. -//! +//! //! This program demonstrats combining several wave files into a single //! polyphonic wave file. @@ -8,13 +8,18 @@ use std::io; use std::path::Path; extern crate bwavfile; -use bwavfile::{Error,WaveReader, WaveWriter, ChannelDescriptor, ChannelMask, WaveFmt}; +use bwavfile::{ChannelDescriptor, ChannelMask, Error, WaveFmt, WaveReader, WaveWriter}; #[macro_use] extern crate clap; -use clap::{Arg, App}; +use clap::{App, Arg}; -fn name_suffix(force_numeric : bool, delim : &str, index: usize, channel_descriptor : &ChannelDescriptor) -> String { +fn name_suffix( + force_numeric: bool, + delim: &str, + index: usize, + channel_descriptor: &ChannelDescriptor, +) -> String { if force_numeric || channel_descriptor.speaker == ChannelMask::DirectOut { format!("{}A{:02}", delim, index) } else { @@ -37,13 +42,13 @@ fn name_suffix(force_numeric : bool, delim : &str, index: usize, channel_descrip ChannelMask::TopBackLeft => "Ltb", ChannelMask::TopBackCenter => "Ctb", ChannelMask::TopBackRight => "Rtb", - ChannelMask::DirectOut => panic!("Error, can't get here") + ChannelMask::DirectOut => panic!("Error, can't get here"), }; format!("{}{}", delim, chan_name) } } -fn process_file(infile: &str, delim : &str, numeric_channel_names : bool) -> Result<(), Error> { +fn process_file(infile: &str, delim: &str, numeric_channel_names: bool) -> Result<(), Error> { let mut input_file = WaveReader::open(infile)?; let channel_desc = input_file.channels()?; let input_format = input_file.format()?; @@ -54,21 +59,32 @@ fn process_file(infile: &str, delim : &str, numeric_channel_names : bool) -> Res } let infile_path = Path::new(infile); - let basename = infile_path.file_stem().expect("Unable to extract file basename").to_str().unwrap(); - let output_dir = infile_path.parent().expect("Unable to derive parent directory"); + let basename = infile_path + .file_stem() + .expect("Unable to extract file basename") + .to_str() + .unwrap(); + let output_dir = infile_path + .parent() + .expect("Unable to derive parent directory"); - let ouptut_format = WaveFmt::new_pcm_mono(input_format.sample_rate, input_format.bits_per_sample); + let ouptut_format = + WaveFmt::new_pcm_mono(input_format.sample_rate, input_format.bits_per_sample); let mut input_wave_reader = input_file.audio_frame_reader()?; for (n, channel) in channel_desc.iter().enumerate() { let suffix = name_suffix(numeric_channel_names, delim, n + 1, channel); - let outfile_name = output_dir.join(format!("{}{}.wav", basename, suffix)) - .into_os_string().into_string().unwrap(); + let outfile_name = output_dir + .join(format!("{}{}.wav", basename, suffix)) + .into_os_string() + .into_string() + .unwrap(); println!("Will create file {}", outfile_name); - let output_file = WaveWriter::create(&outfile_name, ouptut_format).expect("Failed to create new file"); - + let output_file = + WaveWriter::create(&outfile_name, ouptut_format).expect("Failed to create new file"); + let mut output_wave_writer = output_file.audio_frame_writer()?; let mut buffer = input_format.create_frame_buffer(1); @@ -88,25 +104,28 @@ fn main() -> io::Result<()> { .version(crate_version!()) .author(crate_authors!()) .about("Extract each channel of a polyphonic wave file as a new monoaural wave file.") - .arg(Arg::with_name("numeric_names") - .long("numeric") - .short("n") - .help("Use numeric channel names \"01\" \"02\" etc.") - .takes_value(false) + .arg( + Arg::with_name("numeric_names") + .long("numeric") + .short("n") + .help("Use numeric channel names \"01\" \"02\" etc.") + .takes_value(false), ) - .arg(Arg::with_name("channel_delimiter") - .long("delim") - .short("d") - .help("Channel label delimiter.") - .default_value(".") + .arg( + Arg::with_name("channel_delimiter") + .long("delim") + .short("d") + .help("Channel label delimiter.") + .default_value("."), ) - .arg(Arg::with_name("INPUT") - .help("Input wave file") - .required(true) - .multiple(true) + .arg( + Arg::with_name("INPUT") + .help("Input wave file") + .required(true) + .multiple(true), ) .get_matches(); - + let delimiter = matches.value_of("channel_delimiter").unwrap(); let use_numeric_names = matches.is_present("numeric_names"); let infile = matches.value_of("INPUT").unwrap(); @@ -114,6 +133,6 @@ fn main() -> io::Result<()> { match process_file(infile, delimiter, use_numeric_names) { Err(Error::IOError(io)) => Err(io), Err(e) => panic!("Error: {:?}", e), - Ok(()) => Ok(()) + Ok(()) => Ok(()), } -} \ No newline at end of file +} diff --git a/examples/wave-inter.rs b/examples/wave-inter.rs index 52e32a6..2337b30 100644 --- a/examples/wave-inter.rs +++ b/examples/wave-inter.rs @@ -1,6 +1,6 @@ //! wave-inter.rs //! (c) 2021 Jamie Hardt. All rights reserved. -//! +//! //! This program demonstrates combining several wave files into a single //! polyphonic wave file. @@ -10,7 +10,7 @@ extern crate bwavfile; #[macro_use] extern crate clap; -use clap::{Arg, App}; +use clap::{App, Arg}; fn main() -> io::Result<()> { let matches = App::new("wave-inter") @@ -32,4 +32,4 @@ fn main() -> io::Result<()> { println!("Command line opts: {:?}", matches); todo!("Finish implementation"); -} \ No newline at end of file +} diff --git a/src/bext.rs b/src/bext.rs index 6928878..5c0fdb1 100644 --- a/src/bext.rs +++ b/src/bext.rs @@ -1,15 +1,13 @@ - pub type LU = f32; pub type LUFS = f32; pub type Decibels = f32; - /// Broadcast-WAV metadata record. /// -/// The `bext` record contains information about the original recording of the -/// Wave file, including a longish (256 ASCII chars) description field, -/// originator identification fields, creation calendar date and time, a -/// sample-accurate recording time field, and a SMPTE UMID. +/// The `bext` record contains information about the original recording of the +/// Wave file, including a longish (256 ASCII chars) description field, +/// originator identification fields, creation calendar date and time, a +/// sample-accurate recording time field, and a SMPTE UMID. /// /// For a Wave file to be a complaint "Broadcast-WAV" file, it must contain /// a `bext` metadata record. @@ -17,7 +15,7 @@ pub type Decibels = f32; /// ## Resources /// - [EBU Tech 3285](https://tech.ebu.ch/docs/tech/tech3285.pdf). /// - [EBU Tech R098](https://tech.ebu.ch/docs/r/r098.pdf) (1999) "Format for the <CodingHistory> field in Broadcast Wave Format files, BWF" -/// - [EBU Tech R099](https://tech.ebu.ch/docs/r/r099.pdf) (October 2011) "‘Unique’ Source Identifier (USID) for use in the +/// - [EBU Tech R099](https://tech.ebu.ch/docs/r/r099.pdf) (October 2011) "‘Unique’ Source Identifier (USID) for use in the /// <OriginatorReference> field of the Broadcast Wave Format" // Note for me later: @@ -26,7 +24,6 @@ pub type Decibels = f32; #[derive(Debug)] pub struct Bext { - /// 256 ASCII character field with free text. pub description: String, @@ -47,23 +44,23 @@ pub struct Bext { pub time_reference: u64, /// Bext chunk version. - /// - /// Version 1 contains a UMID, version 2 contains a UMID and + /// + /// Version 1 contains a UMID, version 2 contains a UMID and /// loudness metadata. pub version: u16, /// SMPTE 330M UMID - /// + /// /// This field is `None` if the version is less than 1. pub umid: Option<[u8; 64]>, /// Integrated loudness in LUFS. - /// + /// /// This field is `None` if the version is less than 2. pub loudness_value: Option, /// Loudness range in LU. - /// + /// /// This field is `None` if the version is less than 2. pub loudness_range: Option, @@ -78,11 +75,10 @@ pub struct Bext { pub max_momentary_loudness: Option, /// Maximum short-term loudness in LUFS. - /// + /// /// This field is `None` if the version is less than 2. pub max_short_term_loudness: Option, // 180 bytes of nothing - /// Coding History. - pub coding_history: String + pub coding_history: String, } diff --git a/src/chunks.rs b/src/chunks.rs index 671bc7d..1e78802 100644 --- a/src/chunks.rs +++ b/src/chunks.rs @@ -1,33 +1,40 @@ use std::io::{Read, Write}; -use encoding::{DecoderTrap, EncoderTrap}; -use encoding::{Encoding}; use encoding::all::ASCII; +use encoding::Encoding; +use encoding::{DecoderTrap, EncoderTrap}; use byteorder::LittleEndian; use byteorder::{ReadBytesExt, WriteBytesExt}; use uuid::Uuid; +use super::bext::Bext; use super::errors::Error as ParserError; use super::fmt::{WaveFmt, WaveFmtExtended}; -use super::bext::Bext; pub trait ReadBWaveChunks: Read { fn read_bext(&mut self) -> Result; - fn read_bext_string_field(&mut self, length: usize) -> Result; + fn read_bext_string_field(&mut self, length: usize) -> Result; fn read_wave_fmt(&mut self) -> Result; } pub trait WriteBWaveChunks: Write { - fn write_wave_fmt(&mut self, format : &WaveFmt) -> Result<(), ParserError>; - fn write_bext_string_field(&mut self, string: &String, length: usize) -> Result<(),ParserError>; - fn write_bext(&mut self, bext: &Bext) -> Result<(),ParserError>; + fn write_wave_fmt(&mut self, format: &WaveFmt) -> Result<(), ParserError>; + fn write_bext_string_field( + &mut self, + string: &String, + length: usize, + ) -> Result<(), ParserError>; + fn write_bext(&mut self, bext: &Bext) -> Result<(), ParserError>; } -impl WriteBWaveChunks for T where T: Write { - fn write_wave_fmt(&mut self, format : &WaveFmt) -> Result<(), ParserError> { - self.write_u16::(format.tag as u16 )?; +impl WriteBWaveChunks for T +where + T: Write, +{ + fn write_wave_fmt(&mut self, format: &WaveFmt) -> Result<(), ParserError> { + self.write_u16::(format.tag as u16)?; self.write_u16::(format.channel_count)?; self.write_u32::(format.sample_rate)?; self.write_u32::(format.bytes_per_second)?; @@ -44,12 +51,18 @@ impl WriteBWaveChunks for T where T: Write { Ok(()) } - fn write_bext_string_field(&mut self, string: &String, length: usize) -> Result<(),ParserError> { - let mut buf = ASCII.encode(&string, EncoderTrap::Ignore).expect("Error encoding text"); + fn write_bext_string_field( + &mut self, + string: &String, + length: usize, + ) -> Result<(), ParserError> { + let mut buf = ASCII + .encode(&string, EncoderTrap::Ignore) + .expect("Error encoding text"); buf.truncate(length); let filler_length = length - buf.len(); - if filler_length > 0{ - let mut filler = vec![0u8; filler_length ]; + if filler_length > 0 { + let mut filler = vec![0u8; filler_length]; buf.append(&mut filler); } @@ -57,7 +70,7 @@ impl WriteBWaveChunks for T where T: Write { Ok(()) } - fn write_bext(&mut self, bext: &Bext) -> Result<(),ParserError> { + fn write_bext(&mut self, bext: &Bext) -> Result<(), ParserError> { self.write_bext_string_field(&bext.description, 256)?; self.write_bext_string_field(&bext.originator, 32)?; self.write_bext_string_field(&bext.originator_reference, 32)?; @@ -69,21 +82,21 @@ impl WriteBWaveChunks for T where T: Write { let buf = bext.umid.unwrap_or([0u8; 64]); self.write_all(&buf)?; - self.write_i16::( - (bext.loudness_value.unwrap_or(0.0) * 100.0) as i16 )?; - self.write_i16::( - (bext.loudness_range.unwrap_or(0.0) * 100.0) as i16 )?; - self.write_i16::( - (bext.max_true_peak_level.unwrap_or(0.0) * 100.0) as i16 )?; - self.write_i16::( - (bext.max_momentary_loudness.unwrap_or(0.0) * 100.0) as i16 )?; - self.write_i16::( - (bext.max_short_term_loudness.unwrap_or(0.0) * 100.0) as i16 )?; - + self.write_i16::((bext.loudness_value.unwrap_or(0.0) * 100.0) as i16)?; + self.write_i16::((bext.loudness_range.unwrap_or(0.0) * 100.0) as i16)?; + self.write_i16::((bext.max_true_peak_level.unwrap_or(0.0) * 100.0) as i16)?; + self.write_i16::( + (bext.max_momentary_loudness.unwrap_or(0.0) * 100.0) as i16, + )?; + self.write_i16::( + (bext.max_short_term_loudness.unwrap_or(0.0) * 100.0) as i16, + )?; + let padding = [0u8; 180]; self.write_all(&padding)?; - - let coding = ASCII.encode(&bext.coding_history, EncoderTrap::Ignore) + + let coding = ASCII + .encode(&bext.coding_history, EncoderTrap::Ignore) .expect("Error"); self.write_all(&coding)?; @@ -91,20 +104,22 @@ impl WriteBWaveChunks for T where T: Write { } } -impl ReadBWaveChunks for T where T: Read { - +impl ReadBWaveChunks for T +where + T: Read, +{ fn read_wave_fmt(&mut self) -> Result { - let tag_value : u16; + let tag_value: u16; Ok(WaveFmt { tag: { tag_value = self.read_u16::()?; tag_value }, - channel_count: self.read_u16::()?, - sample_rate: self.read_u32::()?, - bytes_per_second: self.read_u32::()?, - block_alignment: self.read_u16::()?, - bits_per_sample: self.read_u16::()?, + channel_count: self.read_u16::()?, + sample_rate: self.read_u32::()?, + bytes_per_second: self.read_u32::()?, + block_alignment: self.read_u16::()?, + bits_per_sample: self.read_u16::()?, extended_format: { if tag_value == 0xFFFE { let cb_size = self.read_u16::()?; @@ -113,77 +128,111 @@ impl ReadBWaveChunks for T where T: Read { valid_bits_per_sample: self.read_u16::()?, channel_mask: self.read_u32::()?, type_guid: { - let mut buf : [u8; 16] = [0; 16]; + let mut buf: [u8; 16] = [0; 16]; self.read_exact(&mut buf)?; Uuid::from_slice(&buf)? - } + }, }) } else { None } - } + }, }) } - fn read_bext_string_field(&mut self, length: usize) -> Result { - let mut buffer : Vec = vec![0; length]; + fn read_bext_string_field(&mut self, length: usize) -> Result { + let mut buffer: Vec = vec![0; length]; self.read(&mut buffer)?; - let trimmed : Vec = buffer.iter().take_while(|c| **c != 0 as u8).cloned().collect(); - Ok(ASCII.decode(&trimmed, DecoderTrap::Ignore).expect("Error decoding text")) + let trimmed: Vec = buffer + .iter() + .take_while(|c| **c != 0 as u8) + .cloned() + .collect(); + Ok(ASCII + .decode(&trimmed, DecoderTrap::Ignore) + .expect("Error decoding text")) } fn read_bext(&mut self) -> Result { - let version : u16; - Ok( Bext { - description: self.read_bext_string_field(256)?, - originator: self.read_bext_string_field(32)?, - originator_reference : self.read_bext_string_field(32)?, - origination_date : self.read_bext_string_field(10)?, - origination_time : self.read_bext_string_field(8)?, - time_reference: self.read_u64::()?, - version: { - version = self.read_u16::()?; - version - }, - umid: { - let mut buf = [0u8 ; 64]; - self.read(&mut buf)?; - if version > 0 { Some(buf) } else { None } - }, - loudness_value: { - let val = (self.read_i16::()? as f32) / 100f32; - if version > 1 { Some(val) } else { None } - }, - loudness_range: { - let val = self.read_i16::()? as f32 / 100f32; - if version > 1 { Some(val) } else { None } - }, - max_true_peak_level: { - let val = self.read_i16::()? as f32 / 100f32; - if version > 1 { Some(val) } else { None } - }, - max_momentary_loudness: { - let val = self.read_i16::()? as f32 / 100f32; - if version > 1 { Some(val) } else { None } - }, - max_short_term_loudness: { - let val = self.read_i16::()? as f32 / 100f32; - if version > 1 { Some(val) } else { None } - }, - coding_history: { - for _ in 0..180 { self.read_u8()?; } - let mut buf = vec![]; - self.read_to_end(&mut buf)?; - ASCII.decode(&buf, DecoderTrap::Ignore).expect("Error decoding text") + let version: u16; + Ok(Bext { + description: self.read_bext_string_field(256)?, + originator: self.read_bext_string_field(32)?, + originator_reference: self.read_bext_string_field(32)?, + origination_date: self.read_bext_string_field(10)?, + origination_time: self.read_bext_string_field(8)?, + time_reference: self.read_u64::()?, + version: { + version = self.read_u16::()?; + version + }, + umid: { + let mut buf = [0u8; 64]; + self.read(&mut buf)?; + if version > 0 { + Some(buf) + } else { + None } + }, + loudness_value: { + let val = (self.read_i16::()? as f32) / 100f32; + if version > 1 { + Some(val) + } else { + None + } + }, + loudness_range: { + let val = self.read_i16::()? as f32 / 100f32; + if version > 1 { + Some(val) + } else { + None + } + }, + max_true_peak_level: { + let val = self.read_i16::()? as f32 / 100f32; + if version > 1 { + Some(val) + } else { + None + } + }, + max_momentary_loudness: { + let val = self.read_i16::()? as f32 / 100f32; + if version > 1 { + Some(val) + } else { + None + } + }, + max_short_term_loudness: { + let val = self.read_i16::()? as f32 / 100f32; + if version > 1 { + Some(val) + } else { + None + } + }, + coding_history: { + for _ in 0..180 { + self.read_u8()?; + } + let mut buf = vec![]; + self.read_to_end(&mut buf)?; + ASCII + .decode(&buf, DecoderTrap::Ignore) + .expect("Error decoding text") + }, }) - } + } } #[test] fn test_read_51_wav() { - use super::fmt::ChannelMask; use super::common_format::CommonFormat; + use super::fmt::ChannelMask; let path = "tests/media/pt_24bit_51.wav"; @@ -198,9 +247,17 @@ fn test_read_51_wav() { let channels = ChannelMask::channels(extended.channel_mask, format.channel_count); - assert_eq!(channels, [ChannelMask::FrontLeft, ChannelMask::FrontRight, - ChannelMask::FrontCenter, ChannelMask::LowFrequency, - ChannelMask::BackLeft, ChannelMask::BackRight]); + assert_eq!( + channels, + [ + ChannelMask::FrontLeft, + ChannelMask::FrontRight, + ChannelMask::FrontCenter, + ChannelMask::LowFrequency, + ChannelMask::BackLeft, + ChannelMask::BackRight + ] + ); assert_eq!(format.common_format(), CommonFormat::IntegerPCM); -} \ No newline at end of file +} diff --git a/src/common_format.rs b/src/common_format.rs index 823eb67..1f6e2d0 100644 --- a/src/common_format.rs +++ b/src/common_format.rs @@ -1,9 +1,9 @@ use uuid::Uuid; -const BASIC_PCM: u16 = 0x0001; -const BASIC_FLOAT: u16 = 0x0003; -const BASIC_MPEG: u16 = 0x0050; -const BASIC_EXTENDED: u16 = 0xFFFE; +const BASIC_PCM: u16 = 0x0001; +const BASIC_FLOAT: u16 = 0x0003; +const BASIC_MPEG: u16 = 0x0050; +const BASIC_EXTENDED: u16 = 0xFFFE; /* RC 2361 §4: @@ -15,50 +15,54 @@ const BASIC_EXTENDED: u16 = 0xFFFE; */ -pub const UUID_PCM: Uuid = Uuid::from_bytes([0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, - 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71]); +pub const UUID_PCM: Uuid = Uuid::from_bytes([ + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71, +]); -pub const UUID_FLOAT: Uuid = Uuid::from_bytes([0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, - 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71]); +pub const UUID_FLOAT: Uuid = Uuid::from_bytes([ + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71, +]); -pub const UUID_MPEG: Uuid = Uuid::from_bytes([0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, - 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71]); +pub const UUID_MPEG: Uuid = Uuid::from_bytes([ + 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71, +]); -pub const UUID_BFORMAT_PCM: Uuid = Uuid::from_bytes([0x01, 0x00, 0x00, 0x00, 0x21, 0x07, 0xd3, 0x11, - 0x86, 0x44, 0xc8, 0xc1, 0xca, 0x00, 0x00, 0x00]); - -pub const UUID_BFORMAT_FLOAT: Uuid = Uuid::from_bytes([0x03, 0x00, 0x00, 0x00, 0x21, 0x07, 0xd3, 0x11, - 0x86, 0x44, 0xc8, 0xc1, 0xca, 0x00, 0x00, 0x00]); +pub const UUID_BFORMAT_PCM: Uuid = Uuid::from_bytes([ + 0x01, 0x00, 0x00, 0x00, 0x21, 0x07, 0xd3, 0x11, 0x86, 0x44, 0xc8, 0xc1, 0xca, 0x00, 0x00, 0x00, +]); +pub const UUID_BFORMAT_FLOAT: Uuid = Uuid::from_bytes([ + 0x03, 0x00, 0x00, 0x00, 0x21, 0x07, 0xd3, 0x11, 0x86, 0x44, 0xc8, 0xc1, 0xca, 0x00, 0x00, 0x00, +]); fn uuid_from_basic_tag(tag: u16) -> Uuid { - let tail : [u8; 6] = [0x00,0xaa,0x00,0x38,0x9b,0x71]; + let tail: [u8; 6] = [0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71]; Uuid::from_fields_le(tag as u32, 0x0000, 0x0010, &tail).unwrap() } /// Sample format of the Wave file. -/// -/// +/// +/// #[derive(Debug, Copy, Clone, PartialEq)] pub enum CommonFormat { /// Integer linear PCM IntegerPCM, - + /// IEEE Floating-point Linear PCM IeeeFloatPCM, - + /// MPEG Mpeg, - + /// Ambisonic B-Format Linear PCM AmbisonicBFormatIntegerPCM, - + /// Ambisonic B-Format Float PCM AmbisonicBFormatIeeeFloatPCM, - + /// An unknown format identified by a basic format tag. UnknownBasic(u16), - + /// An unknown format identified by an extension UUID. UnknownExtended(Uuid), } @@ -70,18 +74,18 @@ impl CommonFormat { (BASIC_PCM, _) => Self::IntegerPCM, (BASIC_FLOAT, _) => Self::IeeeFloatPCM, (BASIC_MPEG, _) => Self::Mpeg, - (BASIC_EXTENDED, Some(UUID_PCM)) => Self::IntegerPCM, - (BASIC_EXTENDED, Some(UUID_FLOAT))=> Self::IeeeFloatPCM, + (BASIC_EXTENDED, Some(UUID_PCM)) => Self::IntegerPCM, + (BASIC_EXTENDED, Some(UUID_FLOAT)) => Self::IeeeFloatPCM, (BASIC_EXTENDED, Some(UUID_BFORMAT_PCM)) => Self::AmbisonicBFormatIntegerPCM, (BASIC_EXTENDED, Some(UUID_BFORMAT_FLOAT)) => Self::AmbisonicBFormatIeeeFloatPCM, (BASIC_EXTENDED, Some(x)) => CommonFormat::UnknownExtended(x), - (x, _) => CommonFormat::UnknownBasic(x) + (x, _) => CommonFormat::UnknownBasic(x), } } /// Get the appropriate tag and `Uuid` for the callee. - /// - /// If there is no appropriate tag for the format of the callee, the + /// + /// If there is no appropriate tag for the format of the callee, the /// returned tag will be 0xFFFE and the `Uuid` will describe the format. pub fn take(self) -> (u16, Uuid) { match self { @@ -90,8 +94,8 @@ impl CommonFormat { Self::Mpeg => (BASIC_MPEG, UUID_MPEG), Self::AmbisonicBFormatIntegerPCM => (BASIC_EXTENDED, UUID_BFORMAT_PCM), Self::AmbisonicBFormatIeeeFloatPCM => (BASIC_EXTENDED, UUID_BFORMAT_FLOAT), - Self::UnknownBasic(x) => ( x, uuid_from_basic_tag(x) ), - Self::UnknownExtended(x) => ( BASIC_EXTENDED, x) + Self::UnknownBasic(x) => (x, uuid_from_basic_tag(x)), + Self::UnknownExtended(x) => (BASIC_EXTENDED, x), } } } diff --git a/src/cue.rs b/src/cue.rs index 74b1aec..580a7a7 100644 --- a/src/cue.rs +++ b/src/cue.rs @@ -1,30 +1,30 @@ -use super::fourcc::{FourCC,ReadFourCC, WriteFourCC, LABL_SIG, NOTE_SIG, - ADTL_SIG, LTXT_SIG, DATA_SIG}; +use super::fourcc::{ + FourCC, ReadFourCC, WriteFourCC, ADTL_SIG, DATA_SIG, LABL_SIG, LTXT_SIG, NOTE_SIG, +}; use super::list_form::collect_list_form; -use byteorder::{WriteBytesExt, ReadBytesExt, LittleEndian}; +use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; -use encoding::{DecoderTrap,EncoderTrap}; -use encoding::{Encoding}; use encoding::all::ASCII; +use encoding::Encoding; +use encoding::{DecoderTrap, EncoderTrap}; use std::io::{Cursor, Error, Read, Write}; -#[derive(Copy,Clone, Debug)] +#[derive(Copy, Clone, Debug)] struct RawCue { - cue_point_id : u32, - frame : u32, - chunk_id : FourCC, - chunk_start : u32, - block_start : u32, - frame_offset : u32 + cue_point_id: u32, + frame: u32, + chunk_id: FourCC, + chunk_start: u32, + block_start: u32, + frame_offset: u32, } impl RawCue { - - fn write_to(cues : Vec) -> Vec { + fn write_to(cues: Vec) -> Vec { let mut writer = Cursor::new(vec![0u8; 0]); - + writer.write_u32::(cues.len() as u32).unwrap(); for cue in cues.iter() { writer.write_u32::(cue.cue_point_id).unwrap(); @@ -38,64 +38,64 @@ impl RawCue { writer.into_inner() } - fn read_from(data : &[u8]) -> Result,Error> { + fn read_from(data: &[u8]) -> Result, Error> { let mut rdr = Cursor::new(data); let count = rdr.read_u32::()?; - let mut retval : Vec = vec![]; + let mut retval: Vec = vec![]; for _ in 0..count { - retval.push( Self { - cue_point_id : rdr.read_u32::()?, - frame : rdr.read_u32::()?, - chunk_id : rdr.read_fourcc()?, - chunk_start : rdr.read_u32::()?, - block_start : rdr.read_u32::()?, - frame_offset : rdr.read_u32::()? + retval.push(Self { + cue_point_id: rdr.read_u32::()?, + frame: rdr.read_u32::()?, + chunk_id: rdr.read_fourcc()?, + chunk_start: rdr.read_u32::()?, + block_start: rdr.read_u32::()?, + frame_offset: rdr.read_u32::()?, }) } - Ok( retval ) + Ok(retval) } } #[derive(Clone, Debug)] struct RawLabel { - cue_point_id : u32, - text : Vec + cue_point_id: u32, + text: Vec, } impl RawLabel { - fn write_to(&self) -> Vec { - let mut writer = Cursor::new(vec![0u8;0]); - writer.write_u32::(self.cue_point_id as u32).unwrap(); + let mut writer = Cursor::new(vec![0u8; 0]); + writer + .write_u32::(self.cue_point_id as u32) + .unwrap(); writer.write(&self.text).unwrap(); writer.into_inner() } - fn read_from(data : &[u8]) -> Result { + fn read_from(data: &[u8]) -> Result { let mut rdr = Cursor::new(data); let length = data.len(); - Ok( Self { - cue_point_id : rdr.read_u32::()?, - text : { - let mut buf = vec![0u8; (length - 4) as usize ]; + Ok(Self { + cue_point_id: rdr.read_u32::()?, + text: { + let mut buf = vec![0u8; (length - 4) as usize]; rdr.read_exact(&mut buf)?; buf - } + }, }) } } #[derive(Clone, Debug)] struct RawNote { - cue_point_id : u32, - text : Vec + cue_point_id: u32, + text: Vec, } impl RawNote { - fn write_to(&self) -> Vec { let mut writer = Cursor::new(vec![0u8; 0]); writer.write_u32::(self.cue_point_id).unwrap(); @@ -103,35 +103,34 @@ impl RawNote { writer.into_inner() } - fn read_from(data : &[u8]) -> Result { + fn read_from(data: &[u8]) -> Result { let mut rdr = Cursor::new(data); let length = data.len(); - Ok( Self { - cue_point_id : rdr.read_u32::()?, - text : { - let mut buf = vec![0u8; (length - 4) as usize ]; + Ok(Self { + cue_point_id: rdr.read_u32::()?, + text: { + let mut buf = vec![0u8; (length - 4) as usize]; rdr.read_exact(&mut buf)?; buf - } + }, }) } } #[derive(Clone, Debug)] -struct RawLtxt { - cue_point_id : u32, - frame_length : u32, - purpose : FourCC, - country : u16, - language : u16, - dialect : u16, - code_page : u16, - text: Option> +struct RawLtxt { + cue_point_id: u32, + frame_length: u32, + purpose: FourCC, + country: u16, + language: u16, + dialect: u16, + code_page: u16, + text: Option>, } impl RawLtxt { - fn write_to(&self) -> Vec { let mut writer = Cursor::new(vec![0u8; 0]); writer.write_u32::(self.cue_point_id).unwrap(); @@ -147,27 +146,27 @@ impl RawLtxt { writer.into_inner() } - fn read_from(data : &[u8]) -> Result { + fn read_from(data: &[u8]) -> Result { let mut rdr = Cursor::new(data); let length = data.len(); - Ok( Self { - cue_point_id : rdr.read_u32::()?, - frame_length : rdr.read_u32::()?, - purpose : rdr.read_fourcc()?, - country : rdr.read_u16::()?, - language : rdr.read_u16::()?, - dialect : rdr.read_u16::()?, - code_page : rdr.read_u16::()?, - text : { + Ok(Self { + cue_point_id: rdr.read_u32::()?, + frame_length: rdr.read_u32::()?, + purpose: rdr.read_fourcc()?, + country: rdr.read_u16::()?, + language: rdr.read_u16::()?, + dialect: rdr.read_u16::()?, + code_page: rdr.read_u16::()?, + text: { if length - 20 > 0 { let mut buf = vec![0u8; (length - 20) as usize]; rdr.read_exact(&mut buf)?; - Some( buf ) + Some(buf) } else { None } - } + }, }) } } @@ -177,223 +176,227 @@ enum RawAdtlMember { Label(RawLabel), Note(RawNote), LabeledText(RawLtxt), - Unrecognized(FourCC) + Unrecognized(FourCC), } impl RawAdtlMember { - fn compile_adtl(members : &[Self]) -> Vec { + fn compile_adtl(members: &[Self]) -> Vec { let mut w = Cursor::new(vec![0u8; 0]); // It seems like all this casing could be done with traits for member in members.iter() { let (fcc, buf) = match member { - RawAdtlMember::Label(l) => ( (LABL_SIG, l.write_to()) ), - RawAdtlMember::Note(n) => ( (NOTE_SIG, n.write_to()) ), - RawAdtlMember::LabeledText(t) => ( (LTXT_SIG, t.write_to()) ), - RawAdtlMember::Unrecognized(f) => (*f, vec![0u8;0] ) // <-- this is a dopey case but here for completeness + RawAdtlMember::Label(l) => ((LABL_SIG, l.write_to())), + RawAdtlMember::Note(n) => ((NOTE_SIG, n.write_to())), + RawAdtlMember::LabeledText(t) => ((LTXT_SIG, t.write_to())), + RawAdtlMember::Unrecognized(f) => (*f, vec![0u8; 0]), // <-- this is a dopey case but here for completeness }; w.write_fourcc(fcc).unwrap(); w.write_u32::(buf.len() as u32).unwrap(); w.write(&buf).unwrap(); - if buf.len() % 2 == 1 { w.write_u8(0).unwrap(); } + if buf.len() % 2 == 1 { + w.write_u8(0).unwrap(); + } } let chunk_content = w.into_inner(); let mut writer = Cursor::new(vec![0u8; 0]); writer.write_fourcc(ADTL_SIG).unwrap(); - writer.write_u32::(chunk_content.len() as u32).unwrap(); + writer + .write_u32::(chunk_content.len() as u32) + .unwrap(); writer.write(&chunk_content).unwrap(); writer.into_inner() } - fn collect_from(chunk : &[u8]) -> Result,Error> { + fn collect_from(chunk: &[u8]) -> Result, Error> { let chunks = collect_list_form(chunk)?; - let mut retval : Vec = vec![]; + let mut retval: Vec = vec![]; for chunk in chunks.iter() { - retval.push( - match chunk.signature { - LABL_SIG => RawAdtlMember::Label( RawLabel::read_from(&chunk.contents)? ), - NOTE_SIG => RawAdtlMember::Note( RawNote::read_from(&chunk.contents)? ), - LTXT_SIG => RawAdtlMember::LabeledText( RawLtxt::read_from(&chunk.contents)? ), - x => RawAdtlMember::Unrecognized(x) - } - ) + retval.push(match chunk.signature { + LABL_SIG => RawAdtlMember::Label(RawLabel::read_from(&chunk.contents)?), + NOTE_SIG => RawAdtlMember::Note(RawNote::read_from(&chunk.contents)?), + LTXT_SIG => RawAdtlMember::LabeledText(RawLtxt::read_from(&chunk.contents)?), + x => RawAdtlMember::Unrecognized(x), + }) } - Ok( retval ) + Ok(retval) } } trait AdtlMemberSearch { fn labels_for_cue_point(&self, id: u32) -> Vec<&RawLabel>; - fn notes_for_cue_point(&self, id : u32) -> Vec<&RawNote>; + fn notes_for_cue_point(&self, id: u32) -> Vec<&RawNote>; fn ltxt_for_cue_point(&self, id: u32) -> Vec<&RawLtxt>; } impl AdtlMemberSearch for Vec { - fn labels_for_cue_point(&self, id: u32) -> Vec<&RawLabel> { - self.iter().filter_map(|item| { - match item { + self.iter() + .filter_map(|item| match item { RawAdtlMember::Label(x) if x.cue_point_id == id => Some(x), - _ => None - } - }) - .collect() + _ => None, + }) + .collect() } - - fn notes_for_cue_point(&self, id: u32) -> Vec<&RawNote> { - self.iter().filter_map(|item| { - match item { + + fn notes_for_cue_point(&self, id: u32) -> Vec<&RawNote> { + self.iter() + .filter_map(|item| match item { RawAdtlMember::Note(x) if x.cue_point_id == id => Some(x), - _ => None - } - }) - .collect() + _ => None, + }) + .collect() } fn ltxt_for_cue_point(&self, id: u32) -> Vec<&RawLtxt> { - self.iter().filter_map(|item| { - match item { + self.iter() + .filter_map(|item| match item { RawAdtlMember::LabeledText(x) if x.cue_point_id == id => Some(x), - _ => None - } - }) - .collect() + _ => None, + }) + .collect() } } /// A cue point recorded in the `cue` and `adtl` metadata. -/// +/// /// ## Resources /// - [Cue list, label and other metadata](https://sites.google.com/site/musicgapi/technical-documents/wav-file-format#smpl) -/// +/// /// ### Not Implemented /// - [EBU 3285 Supplement 2](https://tech.ebu.ch/docs/tech/tech3285s2.pdf) (July 2001): Quality chunk and cuesheet pub struct Cue { - /// The time of this marker - pub frame : u32, + pub frame: u32, /// The length of this marker, if it is a range - pub length : Option, + pub length: Option, /// The text "label"/name of this marker if provided - pub label : Option, + pub label: Option, /// The text "note"/comment of this marker if provided - pub note : Option, - + pub note: Option, + /// The offser of this marker - /// + /// /// **Note:** Applications use the `frame` and `offset` fields /// in different ways. iZotope RX Audio Editor writes the /// marker position to *both* fields, while a Sound Devices /// recorder writes the marker position to *only* the `offset` /// field. - pub offset : u32 + pub offset: u32, } - -fn convert_to_cue_string(buffer : &[u8]) -> String { - let trimmed : Vec = buffer.iter().take_while(|c| **c != 0 as u8).cloned().collect(); - ASCII.decode(&trimmed, DecoderTrap::Ignore).expect("Error decoding text") +fn convert_to_cue_string(buffer: &[u8]) -> String { + let trimmed: Vec = buffer + .iter() + .take_while(|c| **c != 0 as u8) + .cloned() + .collect(); + ASCII + .decode(&trimmed, DecoderTrap::Ignore) + .expect("Error decoding text") } -fn convert_from_cue_string(val : &str) -> Vec { - ASCII.encode(&val, EncoderTrap::Ignore).expect("Error encoding text") +fn convert_from_cue_string(val: &str) -> Vec { + ASCII + .encode(&val, EncoderTrap::Ignore) + .expect("Error encoding text") } impl Cue { - /// Take a list of `Cue`s and convert it into `RawCue` and `RawAdtlMember`s - fn compile_to(cues : &[Cue]) -> (Vec, Vec) { - cues.iter().enumerate() + fn compile_to(cues: &[Cue]) -> (Vec, Vec) { + cues.iter() + .enumerate() .map(|(n, cue)| { let raw_cue = RawCue { cue_point_id: n as u32, - frame : cue.frame, + frame: cue.frame, chunk_id: DATA_SIG, chunk_start: 0, block_start: 0, - frame_offset: cue.offset + frame_offset: cue.offset, }; - let raw_label = cue.label.as_ref().map(|val| { - RawLabel { - cue_point_id : n as u32, - text: convert_from_cue_string(&val) - } + let raw_label = cue.label.as_ref().map(|val| RawLabel { + cue_point_id: n as u32, + text: convert_from_cue_string(&val), }); - let raw_note = cue.note.as_ref().map(|val| { - RawNote { - cue_point_id: n as u32, - text : convert_from_cue_string(&val) - } + let raw_note = cue.note.as_ref().map(|val| RawNote { + cue_point_id: n as u32, + text: convert_from_cue_string(&val), }); - let raw_ltxt = cue.length.map(|val| { - RawLtxt { - cue_point_id : n as u32, - frame_length : val, - purpose : FourCC::make(b"rgn "), - country : 0, - language : 0, - dialect : 0, - code_page : 0, - text : None - } + let raw_ltxt = cue.length.map(|val| RawLtxt { + cue_point_id: n as u32, + frame_length: val, + purpose: FourCC::make(b"rgn "), + country: 0, + language: 0, + dialect: 0, + code_page: 0, + text: None, }); (raw_cue, raw_label, raw_note, raw_ltxt) }) - .fold((Vec::::new(), Vec::::new()), - |(mut cues, mut adtls) , (cue, label, note, ltxt)| { - cues.push(cue); - label.map(|l| adtls.push( RawAdtlMember::Label(l))); - note.map(|n| adtls.push(RawAdtlMember::Note(n))); - ltxt.map(|m| adtls.push(RawAdtlMember::LabeledText(m))); - (cues, adtls) - }) + .fold( + (Vec::::new(), Vec::::new()), + |(mut cues, mut adtls), (cue, label, note, ltxt)| { + cues.push(cue); + label.map(|l| adtls.push(RawAdtlMember::Label(l))); + note.map(|n| adtls.push(RawAdtlMember::Note(n))); + ltxt.map(|m| adtls.push(RawAdtlMember::LabeledText(m))); + (cues, adtls) + }, + ) } - pub fn collect_from(cue_chunk : &[u8], adtl_chunk : Option<&[u8]>) -> Result, Error> { + pub fn collect_from(cue_chunk: &[u8], adtl_chunk: Option<&[u8]>) -> Result, Error> { let raw_cues = RawCue::read_from(cue_chunk)?; - let raw_adtl : Vec; + let raw_adtl: Vec; if let Some(adtl) = adtl_chunk { raw_adtl = RawAdtlMember::collect_from(adtl)?; } else { raw_adtl = vec![]; } - - Ok( - raw_cues.iter() + Ok(raw_cues + .iter() .map(|i| { Cue { //ident : i.cue_point_id, - frame : i.frame, + frame: i.frame, length: { - raw_adtl.ltxt_for_cue_point(i.cue_point_id).first() - .filter(|x| x.purpose == FourCC::make(b"rgn ")) - .map(|x| x.frame_length) + raw_adtl + .ltxt_for_cue_point(i.cue_point_id) + .first() + .filter(|x| x.purpose == FourCC::make(b"rgn ")) + .map(|x| x.frame_length) }, label: { - raw_adtl.labels_for_cue_point(i.cue_point_id).iter() + raw_adtl + .labels_for_cue_point(i.cue_point_id) + .iter() .map(|s| convert_to_cue_string(&s.text)) .next() }, - note : { - raw_adtl.notes_for_cue_point(i.cue_point_id).iter() + note: { + raw_adtl + .notes_for_cue_point(i.cue_point_id) + .iter() //.filter_map(|x| str::from_utf8(&x.text).ok()) .map(|s| convert_to_cue_string(&s.text)) .next() }, - offset: i.frame_offset + offset: i.frame_offset, } - }).collect() - ) + }) + .collect()) } - } diff --git a/src/errors.rs b/src/errors.rs index fe2eff5..3d073fb 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,13 +1,15 @@ -use std::{fmt::{Debug,Display}, io}; -use std::error::Error as StdError; use super::fourcc::FourCC; +use std::error::Error as StdError; +use std::{ + fmt::{Debug, Display}, + io, +}; use uuid; /// Errors returned by methods in this crate. #[derive(Debug)] pub enum Error { - /// An `io::Error` occurred IOError(io::Error), @@ -16,14 +18,14 @@ pub enum Error { /// The file does not begin with a recognized WAVE header HeaderNotRecognized, - + /// A wave file with a 64-bit header does not contain /// the required `ds64` metadata element MissingRequiredDS64, /// A data chunk required to complete the operation /// is not present in the file - ChunkMissing { signature : FourCC }, + ChunkMissing { signature: FourCC }, /// The file is formatted improperly FmtChunkAfterData, @@ -37,11 +39,10 @@ pub enum Error { /// The file cannot be converted into an RF64 file due /// to its internal structure - InsufficientDS64Reservation {expected: u64, actual: u64}, + InsufficientDS64Reservation { expected: u64, actual: u64 }, /// The file is not optimized for writing new data DataChunkNotPreparedForAppend, - } impl StdError for Error {} @@ -52,15 +53,14 @@ impl Display for Error { } } - impl From for Error { fn from(error: io::Error) -> Error { Error::IOError(error) } } -impl From for Error { +impl From for Error { fn from(error: uuid::Error) -> Error { Error::UuidError(error) - } -} \ No newline at end of file + } +} diff --git a/src/fmt.rs b/src/fmt.rs index dae4793..3ad7e24 100644 --- a/src/fmt.rs +++ b/src/fmt.rs @@ -1,32 +1,32 @@ -use uuid::Uuid; -use super::common_format::{CommonFormat, UUID_PCM,UUID_BFORMAT_PCM}; +use super::common_format::{CommonFormat, UUID_BFORMAT_PCM, UUID_PCM}; use std::io::Cursor; +use uuid::Uuid; use byteorder::LittleEndian; -use byteorder::{WriteBytesExt, ReadBytesExt}; +use byteorder::{ReadBytesExt, WriteBytesExt}; // Need more test cases for ADMAudioID #[allow(dead_code)] /// ADM Audio ID record. -/// +/// /// This structure relates a channel in the wave file to either a common ADM -/// channel definition or further definition in the WAV file's ADM metadata +/// channel definition or further definition in the WAV file's ADM metadata /// chunk. -/// -/// An individual channel in a WAV file can have multiple Audio IDs in an ADM +/// +/// An individual channel in a WAV file can have multiple Audio IDs in an ADM /// `AudioProgramme`. -/// +/// /// See BS.2088-1 § 8, also BS.2094, also blahblahblah... #[derive(Debug)] pub struct ADMAudioID { pub track_uid: [char; 12], pub channel_format_ref: [char; 14], - pub pack_ref: [char; 11] + pub pack_ref: [char; 11], } /// Describes a single channel in a WAV file. -/// +/// /// This information is correlated from the Wave format ChannelMap field and /// the `chna` chunk, if present. #[derive(Debug)] @@ -35,7 +35,7 @@ pub struct ChannelDescriptor { pub index: u16, /// Channel assignment - /// + /// /// This is either implied (in the case of mono or stereo wave files) or /// explicitly given in `WaveFormatExtentended` for files with more tracks. pub speaker: ChannelMask, @@ -44,40 +44,38 @@ pub struct ChannelDescriptor { pub adm_track_audio_ids: Vec, } - -/// A bitmask indicating which channels are present in +/// A bitmask indicating which channels are present in /// the file. -/// +/// #[derive(Debug, Clone, Copy, PartialEq)] pub enum ChannelMask { - DirectOut = 0x0, - FrontLeft = 0x1, - FrontRight = 0x2, - FrontCenter = 0x4, - LowFrequency = 0x8, - BackLeft = 0x10, - BackRight = 0x20, - FrontCenterLeft = 0x40, + DirectOut = 0x0, + FrontLeft = 0x1, + FrontRight = 0x2, + FrontCenter = 0x4, + LowFrequency = 0x8, + BackLeft = 0x10, + BackRight = 0x20, + FrontCenterLeft = 0x40, FrontCenterRight = 0x80, - BackCenter = 0x100, - SideLeft = 0x200, - SideRight = 0x400, - TopCenter = 0x800, - TopFrontLeft = 0x1000, - TopFrontCenter = 0x2000, - TopFrontRight = 0x4000, - TopBackLeft = 0x8000, - TopBackCenter = 0x10000, - TopBackRight = 0x20000, + BackCenter = 0x100, + SideLeft = 0x200, + SideRight = 0x400, + TopCenter = 0x800, + TopFrontLeft = 0x1000, + TopFrontCenter = 0x2000, + TopFrontRight = 0x4000, + TopBackLeft = 0x8000, + TopBackCenter = 0x10000, + TopBackRight = 0x20000, } impl From for ChannelMask { - - fn from(value: u32) -> Self { + fn from(value: u32) -> Self { match value { 0x1 => Self::FrontLeft, 0x2 => Self::FrontRight, - 0x4 => Self::FrontCenter, + 0x4 => Self::FrontCenter, 0x8 => Self::LowFrequency, 0x10 => Self::BackLeft, 0x20 => Self::BackRight, @@ -87,24 +85,25 @@ impl From for ChannelMask { 0x200 => Self::SideLeft, 0x400 => Self::SideRight, 0x800 => Self::TopCenter, - 0x1000 => Self::TopFrontLeft, + 0x1000 => Self::TopFrontLeft, 0x2000 => Self::TopFrontCenter, 0x4000 => Self::TopFrontRight, 0x8000 => Self::TopBackLeft, 0x10000 => Self::TopBackCenter, 0x20000 => Self::TopBackRight, - _ => Self::DirectOut + _ => Self::DirectOut, } } } impl ChannelMask { - pub fn channels(input_mask : u32, channel_count: u16) -> Vec { + pub fn channels(input_mask: u32, channel_count: u16) -> Vec { let reserved_mask = 0xfff2_0000_u32; if (input_mask & reserved_mask) > 0 { - vec![ ChannelMask::DirectOut ; channel_count as usize ] + vec![ChannelMask::DirectOut; channel_count as usize] } else { - (0..18).map(|i| 1 << i ) + (0..18) + .map(|i| 1 << i) .filter(|mask| mask & input_mask > 0) .map(|mask| Into::::into(mask)) .collect() @@ -114,36 +113,35 @@ impl ChannelMask { /** * Extended Wave Format - * + * * https://docs.microsoft.com/en-us/windows/win32/api/mmreg/ns-mmreg-waveformatextensible */ #[derive(Debug, Copy, Clone)] pub struct WaveFmtExtended { - /// Valid bits per sample - pub valid_bits_per_sample : u16, + pub valid_bits_per_sample: u16, /// Channel mask - /// + /// /// Identifies the speaker assignment for each channel in the file - pub channel_mask : u32, + pub channel_mask: u32, /// Codec GUID - /// + /// /// Identifies the codec of the audio stream - pub type_guid : Uuid, + pub type_guid: Uuid, } /// /// WAV file data format record. /// /// The `fmt` record contains essential information describing the binary -/// structure of the data segment of the WAVE file, such as sample +/// structure of the data segment of the WAVE file, such as sample /// rate, sample binary format, channel count, etc. /// -/// -/// ## Resources -/// +/// +/// ## Resources +/// /// ### Implementation of Wave format `fmt` chunk /// - [MSDN WAVEFORMATEX](https://docs.microsoft.com/en-us/windows/win32/api/mmeapi/ns-mmeapi-waveformatex) /// - [MSDN WAVEFORMATEXTENSIBLE](https://docs.microsoft.com/en-us/windows/win32/api/mmreg/ns-mmreg-waveformatextensible) @@ -152,17 +150,15 @@ pub struct WaveFmtExtended { /// - [RFC 3261][rfc3261] (June 1998) "WAVE and AVI Codec Registries" /// - [Sampler Metadata](http://www.piclist.com/techref/io/serial/midi/wave.html) /// - [Peter Kabal, McGill University](http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html) -/// - [Multimedia Programming Interface and Data Specifications 1.0](http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/Docs/riffmci.pdf) +/// - [Multimedia Programming Interface and Data Specifications 1.0](http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/Docs/riffmci.pdf) /// (August 1991), IBM Corporation and Microsoft Corporation -/// -/// [rfc3261]: https://tools.ietf.org/html/rfc2361 - +/// +/// [rfc3261]: https://tools.ietf.org/html/rfc2361 #[derive(Debug, Copy, Clone)] pub struct WaveFmt { - /// A tag identifying the codec in use. - /// + /// /// If this is 0xFFFE, the codec will be identified by a GUID /// in `extended_format` pub tag: u16, @@ -176,12 +172,12 @@ pub struct WaveFmt { pub sample_rate: u32, /// Count of bytes per second - /// + /// /// By rule, this is `block_alignment * sample_rate` pub bytes_per_second: u32, /// Count of bytes per audio frame - /// + /// /// By rule, this is `channel_count * bits_per_sample / 8` pub block_alignment: u16, @@ -199,15 +195,13 @@ pub struct WaveFmt { pub bits_per_sample: u16, /// Extended format description - /// + /// /// Additional format metadata if `channel_count` is greater than 2, /// or if certain codecs are used. - pub extended_format: Option + pub extended_format: Option, } - impl WaveFmt { - pub fn valid_bits_per_sample(&self) -> u16 { if let Some(ext) = self.extended_format { ext.valid_bits_per_sample @@ -221,7 +215,7 @@ impl WaveFmt { Self::new_pcm_multichannel(sample_rate, bits_per_sample, 0x4) } - /// Create a new integer PCM format for a standard Left-Right stereo audio + /// Create a new integer PCM format for a standard Left-Right stereo audio /// stream. pub fn new_pcm_stereo(sample_rate: u32, bits_per_sample: u16) -> Self { Self::new_pcm_multichannel(sample_rate, bits_per_sample, 0x3) @@ -230,45 +224,65 @@ impl WaveFmt { /// Create a new integer PCM format for ambisonic b-format. pub fn new_pcm_ambisonic(sample_rate: u32, bits_per_sample: u16, channel_count: u16) -> Self { let container_bits_per_sample = bits_per_sample + (bits_per_sample % 8); - let container_bytes_per_sample= container_bits_per_sample / 8; + let container_bytes_per_sample = container_bits_per_sample / 8; WaveFmt { - tag : 0xFFFE, + tag: 0xFFFE, channel_count, sample_rate, - bytes_per_second: container_bytes_per_sample as u32 * sample_rate * channel_count as u32, + bytes_per_second: container_bytes_per_sample as u32 + * sample_rate + * channel_count as u32, block_alignment: container_bytes_per_sample * channel_count, bits_per_sample: container_bits_per_sample, extended_format: Some(WaveFmtExtended { valid_bits_per_sample: bits_per_sample, channel_mask: ChannelMask::DirectOut as u32, - type_guid: UUID_BFORMAT_PCM - }) + type_guid: UUID_BFORMAT_PCM, + }), } } /// Create a new integer PCM format `WaveFmt` with a custom channel bitmap. - /// - /// The order of `channels` is not important. When reading or writing - /// audio frames you must use the standard multichannel order for Wave + /// + /// The order of `channels` is not important. When reading or writing + /// audio frames you must use the standard multichannel order for Wave /// files, the numerical order of the cases of `ChannelMask`. - pub fn new_pcm_multichannel(sample_rate: u32, bits_per_sample: u16, channel_bitmap: u32) -> Self { + pub fn new_pcm_multichannel( + sample_rate: u32, + bits_per_sample: u16, + channel_bitmap: u32, + ) -> Self { let container_bits_per_sample = bits_per_sample + (bits_per_sample % 8); - let container_bytes_per_sample= container_bits_per_sample / 8; - - let channel_count: u16 = (0..=31).fold(0u16, |accum, n| accum + (0x1 & (channel_bitmap >> n) as u16) ); + let container_bytes_per_sample = container_bits_per_sample / 8; - let result : (u16, Option) = match channel_bitmap { - ch if bits_per_sample != container_bits_per_sample => ( - (0xFFFE, Some(WaveFmtExtended { valid_bits_per_sample: bits_per_sample, channel_mask: ch, - type_guid: UUID_PCM }) ) - ), + let channel_count: u16 = (0..=31).fold(0u16, |accum, n| { + accum + (0x1 & (channel_bitmap >> n) as u16) + }); + + let result: (u16, Option) = match channel_bitmap { + ch if bits_per_sample != container_bits_per_sample => { + (( + 0xFFFE, + Some(WaveFmtExtended { + valid_bits_per_sample: bits_per_sample, + channel_mask: ch, + type_guid: UUID_PCM, + }), + )) + } 0b0100 => (0x0001, None), 0b0011 => (0x0001, None), - ch => ( - (0xFFFE, Some( WaveFmtExtended { valid_bits_per_sample: bits_per_sample, channel_mask: ch, - type_guid: UUID_PCM})) - ) + ch => { + (( + 0xFFFE, + Some(WaveFmtExtended { + valid_bits_per_sample: bits_per_sample, + channel_mask: ch, + type_guid: UUID_PCM, + }), + )) + } }; let (tag, extformat) = result; @@ -277,33 +291,35 @@ impl WaveFmt { tag, channel_count, sample_rate, - bytes_per_second: container_bytes_per_sample as u32 * sample_rate * channel_count as u32, + bytes_per_second: container_bytes_per_sample as u32 + * sample_rate + * channel_count as u32, block_alignment: container_bytes_per_sample * channel_count, bits_per_sample: container_bits_per_sample, - extended_format: extformat + extended_format: extformat, } } /// Format or codec of the file's audio data. - /// + /// /// The `CommonFormat` unifies the format tag and the format extension GUID. Use this /// method to determine the codec. pub fn common_format(&self) -> CommonFormat { - CommonFormat::make( self.tag, self.extended_format.map(|ext| ext.type_guid)) + CommonFormat::make(self.tag, self.extended_format.map(|ext| ext.type_guid)) } - /// Create a frame buffer sized to hold `length` frames for a reader or + /// Create a frame buffer sized to hold `length` frames for a reader or /// writer - /// + /// /// This is a conveneince method that creates a `Vec` with - /// as many elements as there are channels in the underlying stream. - pub fn create_frame_buffer(&self, length : usize) -> Vec { + /// as many elements as there are channels in the underlying stream. + pub fn create_frame_buffer(&self, length: usize) -> Vec { vec![0i32; self.channel_count as usize * length] } - /// Create a raw byte buffer to hold `length` blocks from a reader or + /// Create a raw byte buffer to hold `length` blocks from a reader or /// writer - pub fn create_raw_buffer(&self, length : usize) -> Vec { + pub fn create_raw_buffer(&self, length: usize) -> Vec { vec![0u8; self.block_alignment as usize * length] } @@ -311,11 +327,13 @@ impl WaveFmt { pub fn pack_frames(&self, from_frames: &[i32], into_bytes: &mut [u8]) -> () { let mut write_cursor = Cursor::new(into_bytes); - assert!(from_frames.len() % self.channel_count as usize == 0, - "frames buffer does not contain a number of samples % channel_count == 0"); + assert!( + from_frames.len() % self.channel_count as usize == 0, + "frames buffer does not contain a number of samples % channel_count == 0" + ); - for n in 0..from_frames.len() { - match (self.valid_bits_per_sample(), self.bits_per_sample) { + for n in 0..from_frames.len() { + match (self.valid_bits_per_sample(), self.bits_per_sample) { (0..=8,8) => write_cursor.write_u8((from_frames[n] + 0x80) as u8 ).unwrap(), // EBU 3285 §A2.2 (9..=16,16) => write_cursor.write_i16::(from_frames[n] as i16).unwrap(), (10..=24,24) => write_cursor.write_i24::(from_frames[n]).unwrap(), @@ -323,7 +341,7 @@ impl WaveFmt { (b,_)=> panic!("Unrecognized integer format, bits per sample {}, channels {}, block_alignment {}", b, self.channel_count, self.block_alignment) } - } + } () } @@ -342,55 +360,69 @@ impl WaveFmt { } } - /// Channel descriptors for each channel. pub fn channels(&self) -> Vec { match self.channel_count { - 1 => vec![ - ChannelDescriptor { - index: 0, - speaker: ChannelMask::FrontCenter, - adm_track_audio_ids: vec![] - } - ], + 1 => vec![ChannelDescriptor { + index: 0, + speaker: ChannelMask::FrontCenter, + adm_track_audio_ids: vec![], + }], 2 => vec![ ChannelDescriptor { index: 0, speaker: ChannelMask::FrontLeft, - adm_track_audio_ids: vec![] + adm_track_audio_ids: vec![], }, ChannelDescriptor { index: 1, speaker: ChannelMask::FrontRight, - adm_track_audio_ids: vec![] - } + adm_track_audio_ids: vec![], + }, ], x if x > 2 => { let channel_mask = self.extended_format.map(|x| x.channel_mask).unwrap_or(0); let channels = ChannelMask::channels(channel_mask, self.channel_count); - let channels_expanded = channels.iter().chain(std::iter::repeat(&ChannelMask::DirectOut)); + let channels_expanded = channels + .iter() + .chain(std::iter::repeat(&ChannelMask::DirectOut)); (0..self.channel_count) .zip(channels_expanded) - .map(|(n,chan)| ChannelDescriptor { + .map(|(n, chan)| ChannelDescriptor { index: n, - speaker: *chan, - adm_track_audio_ids: vec![] - }).collect() - }, + speaker: *chan, + adm_track_audio_ids: vec![], + }) + .collect() + } x => panic!("Channel count ({}) was illegal!", x), } } } trait ReadWavAudioData { - fn read_i32_frames(&mut self, format: WaveFmt, into: &mut [i32]) -> Result; - fn read_f32_frames(&mut self, format: WaveFmt, into: &mut [f32]) -> Result; + fn read_i32_frames( + &mut self, + format: WaveFmt, + into: &mut [i32], + ) -> Result; + fn read_f32_frames( + &mut self, + format: WaveFmt, + into: &mut [f32], + ) -> Result; } -impl ReadWavAudioData for T where T: std::io::Read { - - fn read_i32_frames(&mut self, format: WaveFmt, into: &mut [i32]) -> Result { +impl ReadWavAudioData for T +where + T: std::io::Read, +{ + fn read_i32_frames( + &mut self, + format: WaveFmt, + into: &mut [i32], + ) -> Result { assert!(into.len() % format.channel_count as usize == 0); for n in 0..(into.len()) { @@ -406,11 +438,14 @@ impl ReadWavAudioData for T where T: std::io::Read { todo!() } - fn read_f32_frames(&mut self, format: WaveFmt, into: &mut [f32]) -> Result { + fn read_f32_frames( + &mut self, + format: WaveFmt, + into: &mut [f32], + ) -> Result { assert!(into.len() % format.channel_count as usize == 0); todo!() } - } trait WriteWavAudioData { @@ -418,12 +453,14 @@ trait WriteWavAudioData { fn write_f32_frames(&mut self, format: WaveFmt, from: &[f32]) -> Result; } -impl WriteWavAudioData for T where T: std::io::Write { - - fn write_i32_frames(&mut self, format: WaveFmt, _: &[i32]) -> Result { - todo!() +impl WriteWavAudioData for T +where + T: std::io::Write, +{ + fn write_i32_frames(&mut self, format: WaveFmt, _: &[i32]) -> Result { + todo!() } - fn write_f32_frames(&mut self, format: WaveFmt, _: &[f32]) -> Result { - todo!() + fn write_f32_frames(&mut self, format: WaveFmt, _: &[f32]) -> Result { + todo!() } -} \ No newline at end of file +} diff --git a/src/fourcc.rs b/src/fourcc.rs index 6ea2c31..0c1a151 100644 --- a/src/fourcc.rs +++ b/src/fourcc.rs @@ -2,7 +2,7 @@ use std::fmt::Debug; use std::io; /// A Four-character Code -/// +/// /// For idetifying chunks, structured contiguous slices or segments /// within a WAV file. #[derive(Eq, PartialEq, Hash, Copy, Clone)] @@ -15,8 +15,13 @@ impl FourCC { } impl From<[char; 4]> for FourCC { - fn from(chars : [char; 4]) -> Self { - Self([chars[0] as u8 , chars[1] as u8, chars[2] as u8, chars[3] as u8]) + fn from(chars: [char; 4]) -> Self { + Self([ + chars[0] as u8, + chars[1] as u8, + chars[2] as u8, + chars[3] as u8, + ]) } } @@ -32,37 +37,45 @@ impl From for [u8; 4] { } } - -impl From<&FourCC> for [char;4] { - fn from( f: &FourCC) -> Self { - [f.0[0] as char, f.0[1] as char, f.0[2] as char, f.0[3] as char,] +impl From<&FourCC> for [char; 4] { + fn from(f: &FourCC) -> Self { + [ + f.0[0] as char, + f.0[1] as char, + f.0[2] as char, + f.0[3] as char, + ] } } -impl From for [char;4] { - fn from( f: FourCC) -> Self { - [f.0[0] as char, f.0[1] as char, f.0[2] as char, f.0[3] as char,] +impl From for [char; 4] { + fn from(f: FourCC) -> Self { + [ + f.0[0] as char, + f.0[1] as char, + f.0[2] as char, + f.0[3] as char, + ] } } - impl From<&FourCC> for String { - fn from(f: &FourCC) -> Self { - let chars: [char;4] = f.into(); - chars.iter().collect::() + fn from(f: &FourCC) -> Self { + let chars: [char; 4] = f.into(); + chars.iter().collect::() } } impl From for String { - fn from(f: FourCC) -> Self { - let chars: [char;4] = f.into(); - chars.iter().collect::() + fn from(f: FourCC) -> Self { + let chars: [char; 4] = f.into(); + chars.iter().collect::() } } impl Debug for FourCC { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { - let s : String = self.into(); + let s: String = self.into(); write!(f, "FourCC({})", s) } } @@ -72,30 +85,35 @@ pub trait ReadFourCC: io::Read { } pub trait WriteFourCC: io::Write { - fn write_fourcc(&mut self, fourcc :FourCC) -> Result<(), io::Error>; + fn write_fourcc(&mut self, fourcc: FourCC) -> Result<(), io::Error>; } -impl ReadFourCC for T where T: io::Read { +impl ReadFourCC for T +where + T: io::Read, +{ fn read_fourcc(&mut self) -> Result { - let mut buf : [u8; 4] = [0 ; 4]; + let mut buf: [u8; 4] = [0; 4]; self.read_exact(&mut buf)?; - Ok( FourCC::from(buf) ) + Ok(FourCC::from(buf)) } } -impl WriteFourCC for T where T: io::Write { - fn write_fourcc(&mut self, fourcc :FourCC) -> Result<(), io::Error> { - let buf : [u8; 4] = fourcc.into(); +impl WriteFourCC for T +where + T: io::Write, +{ + fn write_fourcc(&mut self, fourcc: FourCC) -> Result<(), io::Error> { + let buf: [u8; 4] = fourcc.into(); self.write_all(&buf)?; Ok(()) } } - pub const RIFF_SIG: FourCC = FourCC::make(b"RIFF"); pub const WAVE_SIG: FourCC = FourCC::make(b"WAVE"); -pub const RF64_SIG: FourCC = FourCC::make(b"RF64"); -pub const DS64_SIG: FourCC = FourCC::make(b"ds64"); +pub const RF64_SIG: FourCC = FourCC::make(b"RF64"); +pub const DS64_SIG: FourCC = FourCC::make(b"ds64"); pub const BW64_SIG: FourCC = FourCC::make(b"BW64"); pub const DATA_SIG: FourCC = FourCC::make(b"data"); @@ -117,7 +135,6 @@ pub const LABL_SIG: FourCC = FourCC::make(b"labl"); pub const NOTE_SIG: FourCC = FourCC::make(b"note"); pub const LTXT_SIG: FourCC = FourCC::make(b"ltxt"); - #[cfg(test)] mod tests { use super::*; @@ -125,7 +142,7 @@ mod tests { #[test] fn test_to_string() { let a = FourCC::make(b"a1b2"); - let s : String = a.into(); + let s: String = a.into(); assert_eq!(s, "a1b2"); } -} \ No newline at end of file +} diff --git a/src/lib.rs b/src/lib.rs index 0ecb58c..0c87342 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,29 +1,29 @@ -/*! +/*! # bwavfile - + Rust Wave File Reader/Writer with Broadcast-WAV, MBWF and RF64 Support ## Interfaces ### `WaveReader` -`WaveReader` can open and parse a Wave, Broadcast-Wave, or RF64/BW64 64-bit +`WaveReader` can open and parse a Wave, Broadcast-Wave, or RF64/BW64 64-bit wave file. Metadata can be accessed and parsed in arbitrary order and audio -samples can be accessed using the `AudioFrameReader` type, created by an +samples can be accessed using the `AudioFrameReader` type, created by an accessor method of `WaveReader`. ### `WaveWriter` -`WaveWriter` can create a new Wave, Broadcast-Wave, or RF64/BW64 64-bit wave -file. Metadata chunks and audio samples are added sequentially, write-only, to -a Wave file which is automatically promoted from standard Wave to RF64 wave +`WaveWriter` can create a new Wave, Broadcast-Wave, or RF64/BW64 64-bit wave +file. Metadata chunks and audio samples are added sequentially, write-only, to +a Wave file which is automatically promoted from standard Wave to RF64 wave when the total WAVE form size exceeds 0xFFFFFFFF bytes. ## Objectives and Roadmap -This package aims to support read and writing any kind of WAV file you are likely -to encounter in a professional audio, motion picture production, broadcast, or music +This package aims to support read and writing any kind of WAV file you are likely +to encounter in a professional audio, motion picture production, broadcast, or music production. Apps we test against: @@ -36,29 +36,29 @@ Apps we test against: [github]: https://github.com/iluvcapra/bwavfile */ -extern crate encoding; extern crate byteorder; +extern crate encoding; extern crate uuid; -mod fourcc; -mod errors; mod common_format; +mod errors; +mod fourcc; -mod parser; mod list_form; +mod parser; +mod bext; mod chunks; mod cue; -mod bext; mod fmt; mod wavereader; mod wavewriter; -pub use errors::Error; -pub use wavereader::{WaveReader, AudioFrameReader}; -pub use wavewriter::{WaveWriter, AudioFrameWriter}; pub use bext::Bext; -pub use fmt::{WaveFmt, WaveFmtExtended, ChannelDescriptor, ChannelMask, ADMAudioID}; pub use common_format::CommonFormat; -pub use cue::Cue; \ No newline at end of file +pub use cue::Cue; +pub use errors::Error; +pub use fmt::{ADMAudioID, ChannelDescriptor, ChannelMask, WaveFmt, WaveFmtExtended}; +pub use wavereader::{AudioFrameReader, WaveReader}; +pub use wavewriter::{AudioFrameWriter, WaveWriter}; diff --git a/src/list_form.rs b/src/list_form.rs index 4890617..f65a2a0 100644 --- a/src/list_form.rs +++ b/src/list_form.rs @@ -1,22 +1,22 @@ use super::fourcc::{FourCC, ReadFourCC}; -use byteorder::{ReadBytesExt, LittleEndian}; +use byteorder::{LittleEndian, ReadBytesExt}; use std::io::{Cursor, Error, Read}; pub struct ListFormItem { - pub signature : FourCC, - pub contents : Vec + pub signature: FourCC, + pub contents: Vec, } /// A helper that will accept a LIST chunk as a [u8] /// and give you back each segment -/// -pub fn collect_list_form(list_contents :& [u8]) -> Result, Error> { +/// +pub fn collect_list_form(list_contents: &[u8]) -> Result, Error> { let mut cursor = Cursor::new(list_contents); let mut remain = list_contents.len(); let _ = cursor.read_fourcc()?; // skip signature remain -= 4; - let mut retval : Vec = vec![]; + let mut retval: Vec = vec![]; while remain > 0 { let this_sig = cursor.read_fourcc()?; @@ -25,16 +25,18 @@ pub fn collect_list_form(list_contents :& [u8]) -> Result, Err let mut content_buf = vec![0u8; this_size]; cursor.read_exact(&mut content_buf)?; remain -= this_size; - - retval.push( ListFormItem { signature : this_sig, contents : content_buf } ); - + + retval.push(ListFormItem { + signature: this_sig, + contents: content_buf, + }); + if this_size % 2 == 1 { cursor.read_u8()?; //panic!("Got this far!"); remain -= 1; } } - - Ok( retval ) -} \ No newline at end of file + Ok(retval) +} diff --git a/src/parser.rs b/src/parser.rs index 09e7ca0..f7cd87c 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,15 +1,14 @@ - +use std::collections::HashMap; use std::io; use std::io::SeekFrom::{Current, Start}; -use std::io::{Seek, Read}; -use std::collections::HashMap; +use std::io::{Read, Seek}; use byteorder::LittleEndian; use byteorder::ReadBytesExt; use super::errors::Error; use super::fourcc::{FourCC, ReadFourCC}; -use super::fourcc::{RIFF_SIG, RF64_SIG, BW64_SIG, WAVE_SIG, DS64_SIG, DATA_SIG}; +use super::fourcc::{BW64_SIG, DATA_SIG, DS64_SIG, RF64_SIG, RIFF_SIG, WAVE_SIG}; // just for your reference... // RF64 documentation https://www.itu.int/dms_pubrec/itu-r/rec/bs/R-REC-BS.2088-1-201910-I!!PDF-E.pdf @@ -21,12 +20,26 @@ const RF64_SIZE_MARKER: u32 = 0xFF_FF_FF_FF; #[derive(Debug)] pub enum Event { StartParse, - ReadHeader { signature: FourCC, length_field: u32 }, - ReadRF64Header { signature: FourCC }, - ReadDS64 {file_size: u64, long_sizes: HashMap }, - BeginChunk { signature: FourCC, content_start: u64, content_length: u64 }, - Failed { error: Error }, - FinishParse + ReadHeader { + signature: FourCC, + length_field: u32, + }, + ReadRF64Header { + signature: FourCC, + }, + ReadDS64 { + file_size: u64, + long_sizes: HashMap, + }, + BeginChunk { + signature: FourCC, + content_start: u64, + content_length: u64, + }, + Failed { + error: Error, + }, + FinishParse, } #[derive(Debug)] @@ -36,67 +49,80 @@ enum State { ReadyForDS64, ReadyForChunk { at: u64, remaining: u64 }, Error, - Complete + Complete, } pub struct Parser { stream: R, state: State, - ds64state: HashMap + ds64state: HashMap, } #[derive(Debug, PartialEq, Eq)] pub struct ChunkIteratorItem { pub signature: FourCC, pub start: u64, - pub length: u64 + pub length: u64, } impl Parser { - // wraps a stream pub fn make(stream: R) -> Result { let newmap: HashMap = HashMap::new(); let mut the_stream = stream; the_stream.seek(Start(0))?; return Ok(Parser { - stream: the_stream, + stream: the_stream, state: State::New, ds64state: newmap, - }) + }); } // pub fn into_inner(self) -> R { // self.stream // } - pub fn into_chunk_iterator(self) -> impl Iterator>{ - self.filter_map({|event| - if let Event::BeginChunk {signature , content_start, content_length } = event { - Some(Ok(ChunkIteratorItem {signature, start: content_start, length: content_length })) - } else if let Event::Failed { error } = event { - Some(Err(error)) - } else { - None + pub fn into_chunk_iterator(self) -> impl Iterator> { + self.filter_map({ + |event| { + if let Event::BeginChunk { + signature, + content_start, + content_length, + } = event + { + Some(Ok(ChunkIteratorItem { + signature, + start: content_start, + length: content_length, + })) + } else if let Event::Failed { error } = event { + Some(Err(error)) + } else { + None + } } }) } - pub fn into_chunk_list(self) -> Result,Error> { + pub fn into_chunk_list(self) -> Result, Error> { let mut error = Ok(()); - let chunks = self.into_chunk_iterator() + let chunks = self + .into_chunk_iterator() .scan(&mut error, |err, res| match res { Ok(ok) => Some(ok), - Err(e) => { **err = Err(e); None } + Err(e) => { + **err = Err(e); + None + } }) .collect(); - + error?; - Ok( chunks ) + Ok(chunks) } - } impl Iterator for Parser { @@ -110,55 +136,53 @@ impl Iterator for Parser { } impl Parser { - - fn parse_header(&mut self) -> Result<(Event,State),io::Error> { + fn parse_header(&mut self) -> Result<(Event, State), io::Error> { let file_sig = self.stream.read_fourcc()?; let length = self.stream.read_u32::()?; let list_sig = self.stream.read_fourcc()?; - let event : Event; + let event: Event; let next_state: State; match (file_sig, length, list_sig) { (RIFF_SIG, size, WAVE_SIG) => { event = Event::ReadHeader { signature: file_sig, - length_field: size + length_field: size, }; next_state = State::ReadyForChunk { at: 12, remaining: (length - 4) as u64, }; - }, + } (RF64_SIG, RF64_SIZE_MARKER, WAVE_SIG) | (BW64_SIG, RF64_SIZE_MARKER, WAVE_SIG) => { event = Event::ReadRF64Header { - signature: file_sig + signature: file_sig, }; next_state = State::ReadyForDS64; - }, + } _ => { event = Event::Failed { - error: Error::HeaderNotRecognized + error: Error::HeaderNotRecognized, }; next_state = State::Error; } } - return Ok( (event, next_state) ); + return Ok((event, next_state)); } fn parse_ds64(&mut self) -> Result<(Event, State), Error> { - let at :u64 = 12; + let at: u64 = 12; let ds64_sig = self.stream.read_fourcc()?; let ds64_size = self.stream.read_u32::()? as u64; - let mut read :u64 = 0; + let mut read: u64 = 0; if ds64_sig != DS64_SIG { return Err(Error::MissingRequiredDS64); - } else { let long_file_size = self.stream.read_u64::()?; let long_data_size = self.stream.read_u64::()?; @@ -176,10 +200,10 @@ impl Parser { } self.ds64state.insert(DATA_SIG, long_data_size); - + if read < ds64_size { /* for some reason the ds64 chunk returned by Pro Tools is longer than - it should be but it's all zeroes so... skip. + it should be but it's all zeroes so... skip. For the record libsndfile seems to do the same thing... https://github.com/libsndfile/libsndfile/blob/08d802a3d18fa19c74f38ed910d9e33f80248187/src/rf64.c#L230 @@ -189,7 +213,7 @@ impl Parser { let event = Event::ReadDS64 { file_size: long_file_size, - long_sizes : self.ds64state.clone(), + long_sizes: self.ds64state.clone(), }; let state = State::ReadyForChunk { @@ -197,19 +221,17 @@ impl Parser { remaining: long_file_size - (4 + 8 + ds64_size), }; - return Ok( (event, state) ); + return Ok((event, state)); } } - fn enter_chunk(&mut self, at :u64, remaining: u64) -> Result<(Event, State), io::Error> { - + fn enter_chunk(&mut self, at: u64, remaining: u64) -> Result<(Event, State), io::Error> { let event; let state; if remaining == 0 { event = Event::FinishParse; state = State::Complete; - } else { let this_fourcc = self.stream.read_fourcc()?; let this_size: u64; @@ -221,59 +243,67 @@ impl Parser { this_size = self.stream.read_u32::()? as u64; } - let this_displacement :u64 = if this_size % 2 == 1 { this_size + 1 } else { this_size }; + let this_displacement: u64 = if this_size % 2 == 1 { + this_size + 1 + } else { + this_size + }; self.stream.seek(Current(this_displacement as i64))?; event = Event::BeginChunk { signature: this_fourcc, content_start: at + 8, - content_length: this_size + content_length: this_size, }; - + state = State::ReadyForChunk { at: at + 8 + this_displacement, - remaining: remaining - 8 - this_displacement + remaining: remaining - 8 - this_displacement, } } - return Ok( (event, state) ); + return Ok((event, state)); } fn handle_state(&mut self) -> Result<(Option, State), Error> { match self.state { State::New => { - return Ok( ( Some(Event::StartParse) , State::ReadyForHeader) ); - }, + return Ok((Some(Event::StartParse), State::ReadyForHeader)); + } State::ReadyForHeader => { let (event, state) = self.parse_header()?; - return Ok( ( Some(event), state ) ); - }, + return Ok((Some(event), state)); + } State::ReadyForDS64 => { let (event, state) = self.parse_ds64()?; - return Ok( ( Some(event), state ) ); - }, + return Ok((Some(event), state)); + } State::ReadyForChunk { at, remaining } => { let (event, state) = self.enter_chunk(at, remaining)?; - return Ok( ( Some(event), state ) ); - }, + return Ok((Some(event), state)); + } State::Error => { - return Ok( ( Some(Event::FinishParse) , State::Complete ) ); - }, + return Ok((Some(Event::FinishParse), State::Complete)); + } State::Complete => { - return Ok( ( None, State::Complete ) ); + return Ok((None, State::Complete)); } } } fn advance(&mut self) -> (Option, State) { match self.handle_state() { - Ok(( event , state) ) => { + Ok((event, state)) => { return (event, state); - }, + } Err(error) => { - return (Some(Event::Failed { error: error.into() } ), State::Error ); + return ( + Some(Event::Failed { + error: error.into(), + }), + State::Error, + ); } } } } - diff --git a/src/wavereader.rs b/src/wavereader.rs index e15614d..7a7f78f 100644 --- a/src/wavereader.rs +++ b/src/wavereader.rs @@ -1,62 +1,72 @@ - use std::fs::File; use std::path::Path; -use std::io::SeekFrom; use std::io::Cursor; -use std::io::{Read, Seek, BufReader}; -use std::io::SeekFrom::{Start,Current,}; +use std::io::SeekFrom; +use std::io::SeekFrom::{Current, Start}; +use std::io::{BufReader, Read, Seek}; -use super::parser::Parser; -use super::fourcc::{FourCC, ReadFourCC, FMT__SIG, DATA_SIG, BEXT_SIG, LIST_SIG, - JUNK_SIG, FLLR_SIG, CUE__SIG, ADTL_SIG, AXML_SIG, IXML_SIG}; -use super::errors::Error as ParserError; -use super::fmt::{WaveFmt, ChannelDescriptor, ChannelMask}; use super::bext::Bext; use super::chunks::ReadBWaveChunks; use super::cue::Cue; +use super::errors::Error as ParserError; use super::errors::Error; +use super::fmt::{ChannelDescriptor, ChannelMask, WaveFmt}; +use super::fourcc::{ + FourCC, ReadFourCC, ADTL_SIG, AXML_SIG, BEXT_SIG, CUE__SIG, DATA_SIG, FLLR_SIG, FMT__SIG, + IXML_SIG, JUNK_SIG, LIST_SIG, +}; +use super::parser::Parser; use super::CommonFormat; use byteorder::LittleEndian; use byteorder::ReadBytesExt; - - /// Read audio frames -/// +/// /// The inner reader is interpreted as a raw audio data /// bitstream having a format specified by `format`. -/// +/// #[derive(Debug)] pub struct AudioFrameReader { - inner : R, + inner: R, format: WaveFmt, start: u64, - length: u64 + length: u64, } impl AudioFrameReader { - /// Create a new `AudioFrameReader` - /// + /// /// ### Panics - /// + /// /// This method does a few sanity checks on the provided format /// parameter to confirm the `block_alignment` law is fulfilled /// and the format tag is readable by this implementation (only - /// format 0x01 is supported at this time.) + /// format 0x01 is supported at this time.) pub fn new(mut inner: R, format: WaveFmt, start: u64, length: u64) -> Result { - assert!(format.block_alignment * 8 == format.bits_per_sample * format.channel_count, + assert!( + format.block_alignment * 8 == format.bits_per_sample * format.channel_count, "Unable to read audio frames from packed formats: block alignment is {}, should be {}", - format.block_alignment, (format.bits_per_sample / 8 ) * format.channel_count); - - assert!(format.common_format() == CommonFormat::IntegerPCM || format.common_format() == CommonFormat::IeeeFloatPCM, - "Unsupported format tag {:?}", format.tag); - + format.block_alignment, + (format.bits_per_sample / 8) * format.channel_count + ); + + assert!( + format.common_format() == CommonFormat::IntegerPCM + || format.common_format() == CommonFormat::IeeeFloatPCM, + "Unsupported format tag {:?}", + format.tag + ); + inner.seek(Start(start))?; - Ok( AudioFrameReader { inner , format , start, length} ) + Ok(AudioFrameReader { + inner, + format, + start, + length, + }) } /// Unwrap the inner reader. @@ -65,42 +75,44 @@ impl AudioFrameReader { } /// Locate the read position to a different frame - /// + /// /// Seeks within the audio stream. - /// + /// /// Returns the new location of the read position. - /// + /// /// locate() behaves similarly to Read methods in that /// seeking after the end of the audio data is not an error. - pub fn locate(&mut self, to :u64) -> Result { + pub fn locate(&mut self, to: u64) -> Result { let position = to * self.format.block_alignment as u64; let seek_result = self.inner.seek(Start(self.start + position))?; - Ok( (seek_result - self.start) / self.format.block_alignment as u64 ) + Ok((seek_result - self.start) / self.format.block_alignment as u64) } - /// Read a frame - /// + /// /// A single frame is read from the audio stream and the read location /// is advanced one frame. - /// + /// /// Regardless of the number of bits in the audio sample, this method - /// always writes `i32` samples back to the buffer. These samples are + /// always writes `i32` samples back to the buffer. These samples are /// written back "right-aligned" so samples that are shorter than i32 /// will leave the MSB bits empty. - /// - /// For example: A full-code sample in 16 bit (0xFFFF) will be written + /// + /// For example: A full-code sample in 16 bit (0xFFFF) will be written /// back to the buffer as 0x0000FFFF. /// - /// + /// /// ### Panics - /// - /// The `buffer` must have a number of elements equal to the number of + /// + /// The `buffer` must have a number of elements equal to the number of /// channels and this method will panic if this is not the case. - pub fn read_integer_frame(&mut self, buffer:&mut [i32]) -> Result { - assert!(buffer.len() as u16 == self.format.channel_count, - "read_integer_frame was called with a mis-sized buffer, expected {}, was {}", - self.format.channel_count, buffer.len()); + pub fn read_integer_frame(&mut self, buffer: &mut [i32]) -> Result { + assert!( + buffer.len() as u16 == self.format.channel_count, + "read_integer_frame was called with a mis-sized buffer, expected {}, was {}", + self.format.channel_count, + buffer.len() + ); let framed_bits_per_sample = self.format.block_alignment * 8 / self.format.channel_count; @@ -117,16 +129,19 @@ impl AudioFrameReader { b, self.format.channel_count, self.format.block_alignment) } } - Ok( 1 ) + Ok(1) } else { - Ok( 0 ) + Ok(0) } } pub fn read_float_frame(&mut self, buffer: &mut [f32]) -> Result { - assert!(buffer.len() as u16 == self.format.channel_count, + assert!( + buffer.len() as u16 == self.format.channel_count, "read_float_frame was called with a mis-sized buffer, expected {}, was {}", - self.format.channel_count, buffer.len()); + self.format.channel_count, + buffer.len() + ); let framed_bits_per_sample = self.format.block_alignment * 8 / self.format.channel_count; @@ -140,9 +155,9 @@ impl AudioFrameReader { b, self.format.channel_count, self.format.block_alignment) } } - Ok( 1 ) + Ok(1) } else { - Ok( 0 ) + Ok(0) } } } @@ -150,7 +165,7 @@ impl AudioFrameReader { /// Wave, Broadcast-WAV and RF64/BW64 parser/reader. /// /// ``` -/// use bwavfile::WaveReader; +/// use bwavfile::WaveReader; /// let mut r = WaveReader::open("tests/media/ff_silence.wav").unwrap(); /// /// let format = r.format().unwrap(); @@ -161,17 +176,17 @@ impl AudioFrameReader { /// let mut buffer = format.create_frame_buffer(1); /// /// let read = frame_reader.read_integer_frame(&mut buffer).unwrap(); -/// +/// /// assert_eq!(buffer, [0i32]); /// assert_eq!(read, 1); -/// +/// /// ``` -/// +/// /// ## Resources -/// +/// /// ### Implementation of Wave Files /// - [Peter Kabal, McGill University](http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html) -/// - [Multimedia Programming Interface and Data Specifications 1.0](http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/Docs/riffmci.pdf) +/// - [Multimedia Programming Interface and Data Specifications 1.0](http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/Docs/riffmci.pdf) /// (August 1991), IBM Corporation and Microsoft Corporation /// /// ### Implementation of Broadcast Wave Files @@ -184,14 +199,13 @@ impl AudioFrameReader { /// - Presently in force, adopted by the EBU in [EBU Tech 3306v2][ebu3306v2] (June 2018). /// - [EBU Tech 3306v1][ebu3306v1] (July 2009), "MBWF / RF64: An extended File Format for Audio" /// - No longer in force, however long-established. -/// +/// /// /// [ebu3285]: https://tech.ebu.ch/docs/tech/tech3285.pdf /// [ebu3306v1]: https://tech.ebu.ch/docs/tech/tech3306v1_1.pdf /// [ebu3306v2]: https://tech.ebu.ch/docs/tech/tech3306.pdf /// [itu2088]: https://www.itu.int/dms_pubrec/itu-r/rec/bs/R-REC-BS.2088-1-201910-I!!PDF-E.pdf -/// [rfc3261]: https://tools.ietf.org/html/rfc2361 - +/// [rfc3261]: https://tools.ietf.org/html/rfc2361 #[derive(Debug)] pub struct WaveReader { @@ -199,40 +213,37 @@ pub struct WaveReader { } impl WaveReader> { - pub fn open>(path: P) -> Result { let f = File::open(path)?; let inner = BufReader::new(f); - Ok( Self::new(inner)? ) + Ok(Self::new(inner)?) } } impl WaveReader { - - /// Open a file for reading with unbuffered IO. - /// - /// A convenience that opens `path` and calls `Self::new()` - + /// Open a file for reading with unbuffered IO. + /// + /// A convenience that opens `path` and calls `Self::new()` + pub fn open_unbuffered>(path: P) -> Result { let inner = File::open(path)?; - return Ok( Self::new(inner)? ) + return Ok(Self::new(inner)?); } } impl WaveReader { - /// Wrap a `Read` struct in a new `WaveReader`. - /// + /// /// This is the primary entry point into the `WaveReader` interface. The /// stream passed as `inner` must be at the beginning of the header of the - /// WAVE data. For a .wav file, this means it must be at the start of the + /// WAVE data. For a .wav file, this means it must be at the start of the /// file. /// /// This function does a minimal validation on the provided stream and - /// will return an `Err(errors::Error)` immediately if there is a structural - /// inconsistency that makes the stream unreadable or if it's missing + /// will return an `Err(errors::Error)` immediately if there is a structural + /// inconsistency that makes the stream unreadable or if it's missing /// essential components that make interpreting the audio data impossible. - + /// ```rust /// use std::fs::File; /// use std::io::{Error,ErrorKind}; @@ -249,15 +260,14 @@ impl WaveReader { /// } /// Err(e) => panic!("Unexpected error was returned {:?}", e) /// } - /// + /// /// ``` - pub fn new(inner: R) -> Result { + pub fn new(inner: R) -> Result { let mut retval = Self { inner }; retval.validate_readable()?; Ok(retval) } - /// Unwrap the inner reader. pub fn into_inner(self) -> R { return self.inner; @@ -269,18 +279,21 @@ impl WaveReader { pub fn audio_frame_reader(mut self) -> Result, ParserError> { let format = self.format()?; let audio_chunk_reader = self.get_chunk_extent_at_index(DATA_SIG, 0)?; - Ok(AudioFrameReader::new(self.inner, format, audio_chunk_reader.0, audio_chunk_reader.1)?) + Ok(AudioFrameReader::new( + self.inner, + format, + audio_chunk_reader.0, + audio_chunk_reader.1, + )?) } - /// The count of audio frames in the file. pub fn frame_length(&mut self) -> Result { - let (_, data_length ) = self.get_chunk_extent_at_index(DATA_SIG, 0)?; + let (_, data_length) = self.get_chunk_extent_at_index(DATA_SIG, 0)?; let format = self.format()?; - Ok( data_length / (format.block_alignment as u64) ) - } + Ok(data_length / (format.block_alignment as u64)) + } - /// Sample and frame format of this wave file. /// pub fn format(&mut self) -> Result { @@ -290,29 +303,28 @@ impl WaveReader { } /// The Broadcast-WAV metadata record for this file, if present. - /// + /// pub fn broadcast_extension(&mut self) -> Result, ParserError> { - let mut bext_buff : Vec = vec![ ]; + let mut bext_buff: Vec = vec![]; let result = self.read_chunk(BEXT_SIG, 0, &mut bext_buff)?; if result > 0 { let mut bext_cursor = Cursor::new(bext_buff); - Ok( Some( bext_cursor.read_bext()? ) ) + Ok(Some(bext_cursor.read_bext()?)) } else { - Ok( None) + Ok(None) } - } /// Describe the channels in this file - /// + /// /// Returns a vector of channel descriptors, one for each channel - /// + /// /// ```rust /// use bwavfile::WaveReader; /// use bwavfile::ChannelMask; /// /// let mut f = WaveReader::open("tests/media/pt_24bit_51.wav").unwrap(); - /// + /// /// let chans = f.channels().unwrap(); /// assert_eq!(chans[0].index, 0); /// assert_eq!(chans[0].speaker, ChannelMask::FrontLeft); @@ -321,97 +333,100 @@ impl WaveReader { /// assert_eq!(chans[4].speaker, ChannelMask::BackLeft); /// ``` pub fn channels(&mut self) -> Result, ParserError> { - let format = self.format()?; - let channel_masks : Vec = match (format.channel_count, format.extended_format) { - (1,_) => vec![ChannelMask::FrontCenter], - (2,_) => vec![ChannelMask::FrontLeft, ChannelMask::FrontRight], - (n,Some(x)) => ChannelMask::channels(x.channel_mask, n), - (n,_) => vec![ChannelMask::DirectOut; n as usize] + let channel_masks: Vec = match (format.channel_count, format.extended_format) { + (1, _) => vec![ChannelMask::FrontCenter], + (2, _) => vec![ChannelMask::FrontLeft, ChannelMask::FrontRight], + (n, Some(x)) => ChannelMask::channels(x.channel_mask, n), + (n, _) => vec![ChannelMask::DirectOut; n as usize], }; - Ok( (0..format.channel_count).zip(channel_masks) - .map(|(i,m)| ChannelDescriptor { index: i, speaker:m, adm_track_audio_ids: vec![] } ) - .collect() ) + Ok((0..format.channel_count) + .zip(channel_masks) + .map(|(i, m)| ChannelDescriptor { + index: i, + speaker: m, + adm_track_audio_ids: vec![], + }) + .collect()) } /// Read cue points. - /// + /// /// ```rust /// use bwavfile::WaveReader; /// use bwavfile::Cue; - /// + /// /// let mut f = WaveReader::open("tests/media/izotope_test.wav").unwrap(); /// let cue_points = f.cue_points().unwrap(); - /// + /// /// assert_eq!(cue_points.len(), 3); /// assert_eq!(cue_points[0].frame, 12532); /// assert_eq!(cue_points[0].length, None); /// assert_eq!(cue_points[0].label, Some(String::from("Marker 1"))); /// assert_eq!(cue_points[0].note, Some(String::from("Marker 1 Comment"))); - /// + /// /// assert_eq!(cue_points[1].frame, 20997); /// assert_eq!(cue_points[1].length, None); /// assert_eq!(cue_points[1].label, Some(String::from("Marker 2"))); - /// assert_eq!(cue_points[1].note, Some(String::from("Marker 2 Comment"))); - /// + /// assert_eq!(cue_points[1].note, Some(String::from("Marker 2 Comment"))); + /// /// assert_eq!(cue_points[2].frame, 26711); /// assert_eq!(cue_points[2].length, Some(6465)); /// assert_eq!(cue_points[2].label, Some(String::from("Timed Region"))); - /// assert_eq!(cue_points[2].note, Some(String::from("Region Comment"))); - /// + /// assert_eq!(cue_points[2].note, Some(String::from("Region Comment"))); + /// /// ``` - pub fn cue_points(&mut self) -> Result,ParserError> { - let mut cue_buffer : Vec = vec![]; - let mut adtl_buffer : Vec = vec![]; + pub fn cue_points(&mut self) -> Result, ParserError> { + let mut cue_buffer: Vec = vec![]; + let mut adtl_buffer: Vec = vec![]; let cue_read = self.read_chunk(CUE__SIG, 0, &mut cue_buffer)?; let adtl_read = self.read_list(ADTL_SIG, &mut adtl_buffer)?; match (cue_read, adtl_read) { - (0,_) => Ok( vec![] ), - (_,0) => Ok( Cue::collect_from(&cue_buffer, None)? ), - (_,_) => Ok( Cue::collect_from(&cue_buffer, Some(&adtl_buffer) )? ) + (0, _) => Ok(vec![]), + (_, 0) => Ok(Cue::collect_from(&cue_buffer, None)?), + (_, _) => Ok(Cue::collect_from(&cue_buffer, Some(&adtl_buffer))?), } } /// Read iXML data. - /// + /// /// The iXML data will be appended to `buffer`. - /// If there are no iXML metadata present in the file, + /// If there are no iXML metadata present in the file, /// Ok(0) will be returned. pub fn read_ixml(&mut self, buffer: &mut Vec) -> Result { - self.read_chunk(IXML_SIG, 0, buffer) + self.read_chunk(IXML_SIG, 0, buffer) } /// Read AXML data. - /// - /// The axml data will be appended to `buffer`. By convention this will + /// + /// The axml data will be appended to `buffer`. By convention this will /// generally be ADM metadata. - /// - /// If there are no axml metadata present in the file, + /// + /// If there are no axml metadata present in the file, /// Ok(0) will be returned pub fn read_axml(&mut self, buffer: &mut Vec) -> Result { self.read_chunk(AXML_SIG, 0, buffer) } - /** - * Validate file is readable. - * - * `Ok(())` if the source meets the minimum standard of - * readability by a permissive client: - * - `fmt` chunk and `data` chunk are present - * - `fmt` chunk appears before `data` chunk - */ + * Validate file is readable. + * + * `Ok(())` if the source meets the minimum standard of + * readability by a permissive client: + * - `fmt` chunk and `data` chunk are present + * - `fmt` chunk appears before `data` chunk + */ pub fn validate_readable(&mut self) -> Result<(), ParserError> { - let (fmt_pos, _) = self.get_chunk_extent_at_index(FMT__SIG, 0)?; + let (fmt_pos, _) = self.get_chunk_extent_at_index(FMT__SIG, 0)?; let (data_pos, _) = self.get_chunk_extent_at_index(DATA_SIG, 0)?; if fmt_pos < data_pos { Ok(()) } else { - Err( ParserError::FmtChunkAfterData) + Err(ParserError::FmtChunkAfterData) } } @@ -442,35 +457,38 @@ impl WaveReader { /// let mut x = WaveReader::open("tests/media/pt_24bit_51.wav").unwrap(); /// x.validate_minimal().expect_err("Complex WAV validated minimal!"); /// ``` - pub fn validate_minimal(&mut self) -> Result<(), ParserError> { + pub fn validate_minimal(&mut self) -> Result<(), ParserError> { self.validate_readable()?; - let chunk_fourccs : Vec = Parser::make(&mut self.inner)? - .into_chunk_list()?.iter().map(|c| c.signature ).collect(); + let chunk_fourccs: Vec = Parser::make(&mut self.inner)? + .into_chunk_list()? + .iter() + .map(|c| c.signature) + .collect(); if chunk_fourccs == vec![FMT__SIG, DATA_SIG] { Ok(()) /* FIXME: finish implementation */ } else { - Err( ParserError::NotMinimalWaveFile ) + Err(ParserError::NotMinimalWaveFile) } } /// Validate Broadcast-WAVE file format - /// - /// Returns `Ok(())` if `validate_readable()` and file contains a + /// + /// Returns `Ok(())` if `validate_readable()` and file contains a /// Broadcast-WAV metadata record (a `bext` chunk). - /// + /// /// ### Examples - /// + /// /// ``` /// # use bwavfile::WaveReader; - /// + /// /// let mut w = WaveReader::open("tests/media/ff_bwav_stereo.wav").unwrap(); /// w.validate_broadcast_wave().expect("BWAVE file did not validate BWAVE"); - /// + /// /// let mut x = WaveReader::open("tests/media/pt_24bit.wav").unwrap(); /// x.validate_broadcast_wave().expect("BWAVE file did not validate BWAVE"); - /// + /// /// let mut y = WaveReader::open("tests/media/audacity_16bit.wav").unwrap(); /// y.validate_broadcast_wave().expect_err("Plain WAV file DID validate BWAVE"); /// ``` @@ -479,14 +497,14 @@ impl WaveReader { self.validate_readable()?; let (_, _) = self.get_chunk_extent_at_index(BEXT_SIG, 0)?; Ok(()) - } + } /// /// Verify data is aligned to a block boundary. /// - /// Returns `Ok(())` if `validate_readable()` and the start of the + /// Returns `Ok(())` if `validate_readable()` and the start of the /// `data` chunk's content begins at 0x4000. - pub fn validate_data_chunk_alignment(&mut self) -> Result<() , ParserError> { + pub fn validate_data_chunk_alignment(&mut self) -> Result<(), ParserError> { self.validate_readable()?; let (start, _) = self.get_chunk_extent_at_index(DATA_SIG, 0)?; if start == 0x4000 { @@ -497,10 +515,10 @@ impl WaveReader { } /// Verify audio data can be appended immediately to this file. - /// + /// /// Returns `Ok(())` if: /// - `validate_readable()` - /// - there is a `JUNK` or `FLLR` immediately at the beginning of the chunk + /// - there is a `JUNK` or `FLLR` immediately at the beginning of the chunk /// list adequately large enough to be overwritten by a `ds64` (92 bytes) /// - `data` is the final chunk pub fn validate_prepared_for_append(&mut self) -> Result<(), ParserError> { @@ -509,66 +527,82 @@ impl WaveReader { let chunks = Parser::make(&mut self.inner)?.into_chunk_list()?; let ds64_space_required = 92; - let eligible_filler_chunks = chunks.iter() + let eligible_filler_chunks = chunks + .iter() .take_while(|c| c.signature == JUNK_SIG || c.signature == FLLR_SIG); let filler = eligible_filler_chunks .enumerate() - .fold(0, |accum, (n, item)| if n == 0 { accum + item.length } else {accum + item.length + 8}); + .fold(0, |accum, (n, item)| { + if n == 0 { + accum + item.length + } else { + accum + item.length + 8 + } + }); if filler < ds64_space_required { - Err(ParserError::InsufficientDS64Reservation {expected: ds64_space_required, actual: filler}) + Err(ParserError::InsufficientDS64Reservation { + expected: ds64_space_required, + actual: filler, + }) } else { let data_pos = chunks.iter().position(|c| c.signature == DATA_SIG); - + match data_pos { Some(p) if p == chunks.len() - 1 => Ok(()), - _ => Err(ParserError::DataChunkNotPreparedForAppend) + _ => Err(ParserError::DataChunkNotPreparedForAppend), } } } } -impl WaveReader { - +impl WaveReader { // Private implementation // - // As time passes thi get smore obnoxious because I haven't implemented recursive chunk + // As time passes thi get smore obnoxious because I haven't implemented recursive chunk // parsing in the raw parser and I'm working around it // fn chunk_reader(&mut self, signature: FourCC, at_index: u32) -> Result, ParserError> { // let (start, length) = self.get_chunk_extent_at_index(signature, at_index)?; // Ok( RawChunkReader::new(&mut self.inner, start, length) ) - // } + // } fn read_list(&mut self, ident: FourCC, buffer: &mut Vec) -> Result { if let Some(index) = self.get_list_form(ident)? { self.read_chunk(LIST_SIG, index, buffer) } else { - Ok( 0 ) + Ok(0) } } - - fn read_chunk(&mut self, ident: FourCC, at: u32, mut buffer: &mut Vec) -> Result { - + fn read_chunk( + &mut self, + ident: FourCC, + at: u32, + mut buffer: &mut Vec, + ) -> Result { match self.get_chunk_extent_at_index(ident, at) { Ok((start, length)) => { buffer.resize(length as usize, 0x0); self.inner.seek(SeekFrom::Start(start))?; - self.inner.read(&mut buffer).map_err(|e| ParserError::IOError(e)) - }, - Err(ParserError::ChunkMissing { signature : _} ) => Ok(0), - Err( any ) => Err(any.into()) + self.inner + .read(&mut buffer) + .map_err(|e| ParserError::IOError(e)) + } + Err(ParserError::ChunkMissing { signature: _ }) => Ok(0), + Err(any) => Err(any.into()), } } /// Extent of every chunk with the given fourcc - fn get_chunks_extents(&mut self, fourcc: FourCC) -> Result, ParserError> { + fn get_chunks_extents(&mut self, fourcc: FourCC) -> Result, ParserError> { let p = Parser::make(&mut self.inner)?.into_chunk_list()?; - Ok( p.iter().filter(|item| item.signature == fourcc) - .map(|item| (item.start, item.length)).collect() ) + Ok(p.iter() + .filter(|item| item.signature == fourcc) + .map(|item| (item.start, item.length)) + .collect()) } /// Index of first LIST for with the given FORM fourcc @@ -577,18 +611,22 @@ impl WaveReader { self.inner.seek(SeekFrom::Start(*start as u64))?; let this_fourcc = self.inner.read_fourcc()?; if this_fourcc == fourcc { - return Ok( Some( n as u32 ) ); + return Ok(Some(n as u32)); } } - Ok( None ) + Ok(None) } - fn get_chunk_extent_at_index(&mut self, fourcc: FourCC, index: u32) -> Result<(u64,u64), ParserError> { + fn get_chunk_extent_at_index( + &mut self, + fourcc: FourCC, + index: u32, + ) -> Result<(u64, u64), ParserError> { if let Some((start, length)) = self.get_chunks_extents(fourcc)?.iter().nth(index as usize) { - Ok ((*start, *length)) + Ok((*start, *length)) } else { - Err( ParserError::ChunkMissing { signature : fourcc } ) + Err(ParserError::ChunkMissing { signature: fourcc }) } } } @@ -596,10 +634,9 @@ impl WaveReader { #[test] fn test_list_form() { let mut f = WaveReader::open("tests/media/izotope_test.wav").unwrap(); - let mut buf : Vec = vec![]; - + let mut buf: Vec = vec![]; + f.read_list(ADTL_SIG, &mut buf).unwrap(); - assert_ne!(buf.len(), 0); - + assert_ne!(buf.len(), 0); } diff --git a/src/wavewriter.rs b/src/wavewriter.rs index 8f2520d..a620143 100644 --- a/src/wavewriter.rs +++ b/src/wavewriter.rs @@ -1,47 +1,58 @@ use std::fs::File; +use std::io::{BufWriter, Cursor, Seek, SeekFrom, Write}; use std::path::Path; -use std::io::{Write,Seek,SeekFrom,Cursor,BufWriter}; -use super::Error; -use super::fourcc::{FourCC, WriteFourCC, RIFF_SIG, RF64_SIG, DS64_SIG, - WAVE_SIG, FMT__SIG, DATA_SIG, ELM1_SIG, JUNK_SIG, BEXT_SIG,AXML_SIG, - IXML_SIG}; use super::fmt::WaveFmt; +use super::fourcc::{ + FourCC, WriteFourCC, AXML_SIG, BEXT_SIG, DATA_SIG, DS64_SIG, ELM1_SIG, FMT__SIG, IXML_SIG, + JUNK_SIG, RF64_SIG, RIFF_SIG, WAVE_SIG, +}; +use super::Error; //use super::common_format::CommonFormat; -use super::chunks::WriteBWaveChunks; use super::bext::Bext; +use super::chunks::WriteBWaveChunks; use byteorder::LittleEndian; use byteorder::WriteBytesExt; /// Write audio frames to a `WaveWriter`. -/// -/// -pub struct AudioFrameWriter where W: Write + Seek { - inner : WaveChunkWriter +/// +/// +pub struct AudioFrameWriter +where + W: Write + Seek, +{ + inner: WaveChunkWriter, } -impl AudioFrameWriter where W: Write + Seek { - +impl AudioFrameWriter +where + W: Write + Seek, +{ fn new(inner: WaveChunkWriter) -> Self { AudioFrameWriter { inner } } - fn write_integer_frames_to_buffer(&self, from_frames :&[i32], to_buffer : &mut [u8]) -> () { - assert!(from_frames.len() % self.inner.inner.format.channel_count as usize == 0, - "frames buffer does not contain a number of samples % channel_count == 0"); + fn write_integer_frames_to_buffer(&self, from_frames: &[i32], to_buffer: &mut [u8]) -> () { + assert!( + from_frames.len() % self.inner.inner.format.channel_count as usize == 0, + "frames buffer does not contain a number of samples % channel_count == 0" + ); self.inner.inner.format.pack_frames(&from_frames, to_buffer); () } /// Write interleaved samples in `buffer` - /// + /// /// # Panics - /// + /// /// This function will panic if `buffer.len()` modulo the Wave file's channel count /// is not zero. - pub fn write_integer_frames(&mut self, buffer: &[i32]) -> Result { - let mut write_buffer = self.inner.inner.format + pub fn write_integer_frames(&mut self, buffer: &[i32]) -> Result { + let mut write_buffer = self + .inner + .inner + .format .create_raw_buffer(buffer.len() / self.inner.inner.format.channel_count as usize); self.write_integer_frames_to_buffer(&buffer, &mut write_buffer); @@ -52,7 +63,7 @@ impl AudioFrameWriter where W: Write + Seek { } /// Finish writing audio frames and unwrap the inner `WaveWriter`. - /// + /// /// This method must be called when the client has finished writing audio /// data. This will finalize the audio data chunk. pub fn end(self) -> Result, Error> { @@ -61,29 +72,39 @@ impl AudioFrameWriter where W: Write + Seek { } /// Write a wave data chunk. -/// +/// /// `WaveChunkWriter` implements `Write` and as bytes are written to it, -/// -/// ### Important! -/// -/// When you are done writing to a chunk you must call `end()` in order to +/// +/// ### Important! +/// +/// When you are done writing to a chunk you must call `end()` in order to /// finalize the chunk for storage. -pub struct WaveChunkWriter where W: Write + Seek { - ident : FourCC, - inner : WaveWriter, - content_start_pos : u64, - length : u64 +pub struct WaveChunkWriter +where + W: Write + Seek, +{ + ident: FourCC, + inner: WaveWriter, + content_start_pos: u64, + length: u64, } -impl WaveChunkWriter where W: Write + Seek { - - fn begin(mut inner : WaveWriter, ident : FourCC) -> Result { - let length : u64 = 0; +impl WaveChunkWriter +where + W: Write + Seek, +{ + fn begin(mut inner: WaveWriter, ident: FourCC) -> Result { + let length: u64 = 0; inner.inner.write_fourcc(ident)?; inner.inner.write_u32::(length as u32)?; inner.increment_form_length(8)?; let content_start_pos = inner.inner.seek(SeekFrom::End(0))?; - Ok( WaveChunkWriter { ident, inner , content_start_pos, length } ) + Ok(WaveChunkWriter { + ident, + inner, + content_start_pos, + length, + }) } fn end(mut self) -> Result, Error> { @@ -92,75 +113,84 @@ impl WaveChunkWriter where W: Write + Seek { self.inner.inner.write(&[0u8])?; self.inner.increment_form_length(1)?; } - Ok( self.inner ) + Ok(self.inner) } fn increment_chunk_length(&mut self, amount: u64) -> Result<(), std::io::Error> { self.length = self.length + amount; if !self.inner.is_rf64 { - self.inner.inner.seek(SeekFrom::Start(self.content_start_pos - 4))?; - self.inner.inner.write_u32::(self.length as u32)?; + self.inner + .inner + .seek(SeekFrom::Start(self.content_start_pos - 4))?; + self.inner + .inner + .write_u32::(self.length as u32)?; } else { if self.ident == DATA_SIG { let data_chunk_64bit_field_offset = 8 + 4 + 8 + 8; - self.inner.inner.seek(SeekFrom::Start(self.content_start_pos - 4))?; - self.inner.inner.write_u32::(0xFFFF_FFFF)?; - // this only need to happen once, not every time we increment + self.inner + .inner + .seek(SeekFrom::Start(self.content_start_pos - 4))?; + self.inner.inner.write_u32::(0xFFFF_FFFF)?; + // this only need to happen once, not every time we increment - self.inner.inner.seek(SeekFrom::Start(data_chunk_64bit_field_offset))?; + self.inner + .inner + .seek(SeekFrom::Start(data_chunk_64bit_field_offset))?; self.inner.inner.write_u64::(self.length)?; } else { todo!("FIXME RF64 wave writing is not yet supported for chunks other than `data`") } - } Ok(()) } } -impl Write for WaveChunkWriter where W: Write + Seek { - - fn write(&mut self, buffer: &[u8]) -> Result { +impl Write for WaveChunkWriter +where + W: Write + Seek, +{ + fn write(&mut self, buffer: &[u8]) -> Result { self.inner.inner.seek(SeekFrom::End(0))?; let written = self.inner.inner.write(buffer)?; self.inner.increment_form_length(written as u64)?; self.increment_chunk_length(written as u64)?; - Ok( written ) + Ok(written) } - fn flush(&mut self) -> Result<(), std::io::Error> { + fn flush(&mut self) -> Result<(), std::io::Error> { self.inner.inner.flush() } } /// Wave, Broadcast-WAV and RF64/BW64 writer. -/// +/// /// A `WaveWriter` creates a new wave file at the given path (with `create()`) /// or into the given `Write`- and `Seek`-able inner writer. -/// +/// /// Audio is added to the wave file by starting the audio data chunk with /// `WaveWriter::audio_frame_writer()`. All of the functions that add chunks /// move the WaveWriter and return it to the host when complete. -/// +/// /// # Structure of New Wave Files -/// +/// /// `WaveWriter` will create a Wave file with two chunks automatically: a 96 -/// byte `JUNK` chunk and a standard `fmt ` chunk, which has the extended -/// length if the format your provided requires it. The first `JUNK` chunk is +/// byte `JUNK` chunk and a standard `fmt ` chunk, which has the extended +/// length if the format your provided requires it. The first `JUNK` chunk is /// a reservation for a `ds64` record which will be written over it if /// the file needs to be upgraded to RF64 format. -/// -/// Chunks are added to the file in the order the client adds them. +/// +/// Chunks are added to the file in the order the client adds them. /// `audio_file_writer()` will add a `data` chunk for the audio data, and will -/// also add an `elm1` filler chunk prior to the data chunk to ensure that the +/// also add an `elm1` filler chunk prior to the data chunk to ensure that the /// first byte of the data chunk's content is aligned with 0x4000. -/// +/// /// ``` /// use bwavfile::{WaveWriter,WaveFmt}; /// # use std::io::Cursor; -/// +/// /// // Write a three-sample wave file to a cursor /// let mut cursor = Cursor::new(vec![0u8;0]); /// let format = WaveFmt::new_pcm_mono(48000, 24); @@ -175,10 +205,10 @@ impl Write for WaveChunkWriter where W: Write + Seek { /// ``` /// /// ## Resources -/// +/// /// ### Implementation of Wave Files /// - [Peter Kabal, McGill University](http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html) -/// - [Multimedia Programming Interface and Data Specifications 1.0](http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/Docs/riffmci.pdf) +/// - [Multimedia Programming Interface and Data Specifications 1.0](http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/Docs/riffmci.pdf) /// (August 1991), IBM Corporation and Microsoft Corporation /// /// ### Implementation of Broadcast Wave Files @@ -191,57 +221,66 @@ impl Write for WaveChunkWriter where W: Write + Seek { /// - Presently in force, adopted by the EBU in [EBU Tech 3306v2][ebu3306v2] (June 2018). /// - [EBU Tech 3306v1][ebu3306v1] (July 2009), "MBWF / RF64: An extended File Format for Audio" /// - No longer in force, however long-established. -/// +/// /// /// [ebu3285]: https://tech.ebu.ch/docs/tech/tech3285.pdf /// [ebu3306v1]: https://tech.ebu.ch/docs/tech/tech3306v1_1.pdf /// [ebu3306v2]: https://tech.ebu.ch/docs/tech/tech3306.pdf /// [itu2088]: https://www.itu.int/dms_pubrec/itu-r/rec/bs/R-REC-BS.2088-1-201910-I!!PDF-E.pdf -/// [rfc3261]: https://tools.ietf.org/html/rfc2361 -pub struct WaveWriter where W: Write + Seek { - inner : W, +/// [rfc3261]: https://tools.ietf.org/html/rfc2361 +pub struct WaveWriter +where + W: Write + Seek, +{ + inner: W, form_length: u64, /// True if file is RF64 pub is_rf64: bool, /// Format of the wave file. - pub format: WaveFmt + pub format: WaveFmt, } -const DS64_RESERVATION_LENGTH : u32 = 96; +const DS64_RESERVATION_LENGTH: u32 = 96; impl WaveWriter> { - /// Create a new Wave file at `path`. - pub fn create>(path : P, format : WaveFmt) -> Result { + pub fn create>(path: P, format: WaveFmt) -> Result { let f = File::create(path)?; let b = BufWriter::new(f); - Ok( Self::new(b, format)? ) + Ok(Self::new(b, format)?) } } impl WaveWriter { /// Creare a new Wave file with unbuffered IO at `path` - pub fn create_unbuffered>(path : P, format : WaveFmt) -> Result { + pub fn create_unbuffered>(path: P, format: WaveFmt) -> Result { let f = File::create(path)?; - Ok( Self::new(f, format)? ) + Ok(Self::new(f, format)?) } } -impl WaveWriter where W: Write + Seek { - +impl WaveWriter +where + W: Write + Seek, +{ /// Wrap a writer in a Wave writer. - /// - /// The inner writer will immediately have a RIFF WAVE file header + /// + /// The inner writer will immediately have a RIFF WAVE file header /// written to it along with the format descriptor (and possibly a `fact` /// chunk if appropriate). - pub fn new(mut inner : W, format: WaveFmt) -> Result { + pub fn new(mut inner: W, format: WaveFmt) -> Result { inner.write_fourcc(RIFF_SIG)?; inner.write_u32::(0)?; inner.write_fourcc(WAVE_SIG)?; - let mut retval = WaveWriter { inner, form_length: 0, is_rf64: false, format}; + let mut retval = WaveWriter { + inner, + form_length: 0, + is_rf64: false, + format, + }; retval.increment_form_length(4)?; @@ -252,10 +291,10 @@ impl WaveWriter where W: Write + Seek { chunk.write_wave_fmt(&format)?; let retval = chunk.end()?; - Ok( retval ) + Ok(retval) } - fn write_chunk(&mut self, ident: FourCC, data : &[u8]) -> Result<(),Error> { + fn write_chunk(&mut self, ident: FourCC, data: &[u8]) -> Result<(), Error> { self.inner.seek(SeekFrom::End(0))?; self.inner.write_fourcc(ident)?; assert!(data.len() < u32::MAX as usize); @@ -271,21 +310,21 @@ impl WaveWriter where W: Write + Seek { } /// Write Broadcast-Wave metadata to the file. - /// - /// This function will write the metadata chunk immediately to the end of - /// the file; if you have already written and closed the audio data the + /// + /// This function will write the metadata chunk immediately to the end of + /// the file; if you have already written and closed the audio data the /// bext chunk will be positioned after it. - pub fn write_broadcast_metadata(&mut self, bext: &Bext) -> Result<(),Error> { + pub fn write_broadcast_metadata(&mut self, bext: &Bext) -> Result<(), Error> { //FIXME Implement re-writing let mut c = Cursor::new(vec![0u8; 0]); c.write_bext(&bext)?; let buf = c.into_inner(); - self.write_chunk(BEXT_SIG, &buf )?; + self.write_chunk(BEXT_SIG, &buf)?; Ok(()) } /// Write iXML metadata - pub fn write_ixml(&mut self, ixml: &[u8]) -> Result<(),Error> { + pub fn write_ixml(&mut self, ixml: &[u8]) -> Result<(), Error> { //FIXME Implement re-writing self.write_chunk(IXML_SIG, &ixml) } @@ -302,7 +341,7 @@ impl WaveWriter where W: Write + Seek { self.write_chunk(JUNK_SIG, &filler) } - /// Create an audio frame writer, which takes possession of the callee + /// Create an audio frame writer, which takes possession of the callee /// `WaveWriter`. /// pub fn audio_frame_writer(mut self) -> Result, Error> { @@ -317,11 +356,11 @@ impl WaveWriter where W: Write + Seek { chunk.write(&buf)?; let closed = chunk.end()?; let inner = closed.chunk(DATA_SIG)?; - Ok( AudioFrameWriter::new(inner) ) + Ok(AudioFrameWriter::new(inner)) } /// Open a wave chunk writer here - fn chunk(mut self, ident: FourCC) -> Result,Error> { + fn chunk(mut self, ident: FourCC) -> Result, Error> { self.inner.seek(SeekFrom::End(0))?; WaveChunkWriter::begin(self, ident) } @@ -350,10 +389,10 @@ impl WaveWriter where W: Write + Seek { self.inner.write_u64::(self.form_length)?; } else if self.form_length < u32::MAX as u64 { self.inner.seek(SeekFrom::Start(4))?; - self.inner.write_u32::(self.form_length as u32)?; + self.inner + .write_u32::(self.form_length as u32)?; } else { self.promote_to_rf64()?; - } Ok(()) } @@ -361,11 +400,11 @@ impl WaveWriter where W: Write + Seek { #[test] fn test_new() { - use std::io::Cursor; use super::fourcc::ReadFourCC; use byteorder::ReadBytesExt; + use std::io::Cursor; - let mut cursor = Cursor::new(vec![0u8;0]); + let mut cursor = Cursor::new(vec![0u8; 0]); let format = WaveFmt::new_pcm_mono(4800, 24); WaveWriter::new(&mut cursor, format).unwrap(); @@ -377,7 +416,7 @@ fn test_new() { assert_eq!(cursor.read_fourcc().unwrap(), JUNK_SIG); let junk_size = cursor.read_u32::().unwrap(); - assert_eq!(junk_size,96); + assert_eq!(junk_size, 96); cursor.seek(SeekFrom::Current(junk_size as i64)).unwrap(); assert_eq!(cursor.read_fourcc().unwrap(), FMT__SIG); @@ -387,14 +426,14 @@ fn test_new() { #[test] fn test_write_audio() { - use std::io::Cursor; use super::fourcc::ReadFourCC; use byteorder::ReadBytesExt; + use std::io::Cursor; - let mut cursor = Cursor::new(vec![0u8;0]); + let mut cursor = Cursor::new(vec![0u8; 0]); let format = WaveFmt::new_pcm_mono(48000, 24); let w = WaveWriter::new(&mut cursor, format).unwrap(); - + let mut frame_writer = w.audio_frame_writer().unwrap(); frame_writer.write_integer_frames(&[0i32]).unwrap(); @@ -430,14 +469,17 @@ fn test_write_audio() { let tell = cursor.seek(SeekFrom::Current(0)).unwrap(); assert!(tell % 0x4000 == 0); - assert_eq!(form_size, 4 + 8 + junk_size + 8 + fmt_size + 8 + elm1_size + 8 + data_size + data_size % 2) + assert_eq!( + form_size, + 4 + 8 + junk_size + 8 + fmt_size + 8 + elm1_size + 8 + data_size + data_size % 2 + ) } #[test] fn test_write_bext() { use std::io::Cursor; - let mut cursor = Cursor::new(vec![0u8;0]); + let mut cursor = Cursor::new(vec![0u8; 0]); let format = WaveFmt::new_pcm_mono(48000, 24); let mut w = WaveWriter::new(&mut cursor, format).unwrap(); @@ -469,14 +511,13 @@ fn test_write_bext() { frame_writer.end().unwrap(); } - // NOTE! This test of RF64 writing takes several minutes to complete. #[test] fn test_create_rf64() { use super::fourcc::ReadFourCC; use byteorder::ReadBytesExt; - let mut cursor = Cursor::new(vec![0u8;0]); + let mut cursor = Cursor::new(vec![0u8; 0]); let format = WaveFmt::new_pcm_stereo(48000, 24); let w = WaveWriter::new(&mut cursor, format).unwrap(); @@ -493,7 +534,10 @@ fn test_create_rf64() { } af.end().unwrap(); - assert!(cursor.seek(SeekFrom::End(0)).unwrap() > 0xFFFF_FFFFu64, "internal test error, Created file is not long enough to be RF64" ); + assert!( + cursor.seek(SeekFrom::End(0)).unwrap() > 0xFFFF_FFFFu64, + "internal test error, Created file is not long enough to be RF64" + ); let expected_data_length = four_and_a_half_hours_of_frames * format.block_alignment as u64; cursor.seek(SeekFrom::Start(0)).unwrap(); @@ -506,20 +550,33 @@ fn test_create_rf64() { let form_size = cursor.read_u64::().unwrap(); let data_size = cursor.read_u64::().unwrap(); assert_eq!(data_size, expected_data_length); - cursor.seek(SeekFrom::Current(ds64_size as i64 - 16)).unwrap(); + cursor + .seek(SeekFrom::Current(ds64_size as i64 - 16)) + .unwrap(); assert_eq!(cursor.read_fourcc().unwrap(), FMT__SIG); let fmt_size = cursor.read_u32::().unwrap(); - cursor.seek(SeekFrom::Current((fmt_size + fmt_size % 2) as i64)).unwrap(); - + cursor + .seek(SeekFrom::Current((fmt_size + fmt_size % 2) as i64)) + .unwrap(); + assert_eq!(cursor.read_fourcc().unwrap(), ELM1_SIG); let elm1_size = cursor.read_u32::().unwrap(); - let data_start = cursor.seek(SeekFrom::Current((elm1_size + elm1_size % 2) as i64)).unwrap(); - - assert!((data_start + 8) % 0x4000 == 0, "data content start is not aligned, starts at {}", data_start + 8); + let data_start = cursor + .seek(SeekFrom::Current((elm1_size + elm1_size % 2) as i64)) + .unwrap(); + + assert!( + (data_start + 8) % 0x4000 == 0, + "data content start is not aligned, starts at {}", + data_start + 8 + ); assert_eq!(cursor.read_fourcc().unwrap(), DATA_SIG); assert_eq!(cursor.read_u32::().unwrap(), 0xFFFF_FFFF); cursor.seek(SeekFrom::Current(data_size as i64)).unwrap(); - assert_eq!(4 + 8 + ds64_size as u64 + 8 + data_size + 8 + fmt_size as u64 + 8 + elm1_size as u64, form_size) + assert_eq!( + 4 + 8 + ds64_size as u64 + 8 + data_size + 8 + fmt_size as u64 + 8 + elm1_size as u64, + form_size + ) } diff --git a/tests/ffprobe_media_tests.rs b/tests/ffprobe_media_tests.rs index 2b8213b..6e7db9a 100644 --- a/tests/ffprobe_media_tests.rs +++ b/tests/ffprobe_media_tests.rs @@ -1,7 +1,6 @@ - extern crate serde_json; use core::fmt::Debug; -use serde_json::{Value, from_str}; +use serde_json::{from_str, Value}; use std::fs::File; use std::io::Read; @@ -13,43 +12,42 @@ use bwavfile::WaveReader; // as read by `WaveReader`. // This is rickety but we're going with it -fn assert_match_stream(stream_key: &str, - other: impl Fn(&mut WaveReader) -> T) - where T: PartialEq + Debug, - T: Into - { - +fn assert_match_stream(stream_key: &str, other: impl Fn(&mut WaveReader) -> T) +where + T: PartialEq + Debug, + T: Into, +{ let mut json_file = File::open("tests/ffprobe_media_tests.json").unwrap(); let mut s = String::new(); json_file.read_to_string(&mut s).unwrap(); - if let Value::Array(v) = from_str(&mut s).unwrap() { /* */ + if let Value::Array(v) = from_str(&mut s).unwrap() { + /* */ v.iter() - .filter(|value| { - !value["format"]["filename"].is_null() - }) + .filter(|value| !value["format"]["filename"].is_null()) .for_each(|value| { - let filen : &str = value["format"]["filename"].as_str().unwrap(); - let json_value : &Value = &value["streams"][0][stream_key]; + let filen: &str = value["format"]["filename"].as_str().unwrap(); + let json_value: &Value = &value["streams"][0][stream_key]; let mut wavfile = WaveReader::open_unbuffered(filen).unwrap(); let wavfile_value: T = other(&mut wavfile); - println!("asserting {} for {}",stream_key, filen); + println!("asserting {} for {}", stream_key, filen); assert_eq!(Into::::into(wavfile_value), *json_value); - }) } -} +} #[test] -fn test_frame_count() { - assert_match_stream("duration_ts", |w| w.frame_length().unwrap() ); +fn test_frame_count() { + assert_match_stream("duration_ts", |w| w.frame_length().unwrap()); } #[test] fn test_sample_rate() { - assert_match_stream("sample_rate", |w| format!("{}", w.format().unwrap().sample_rate) ); + assert_match_stream("sample_rate", |w| { + format!("{}", w.format().unwrap().sample_rate) + }); } #[test] fn test_channel_count() { - assert_match_stream("channels", |w| w.format().unwrap().channel_count ); + assert_match_stream("channels", |w| w.format().unwrap().channel_count); } diff --git a/tests/integration_test.rs b/tests/integration_test.rs index 17b954e..85bd525 100644 --- a/tests/integration_test.rs +++ b/tests/integration_test.rs @@ -1,17 +1,15 @@ extern crate bwavfile; -use bwavfile::WaveReader; +use bwavfile::ChannelMask; use bwavfile::Error; -use bwavfile::{ ChannelMask}; +use bwavfile::WaveReader; #[test] fn test_open() { let path = "tests/media/ff_silence.wav"; match WaveReader::open(path) { - Ok(_) => { - () - }, + Ok(_) => (), Err(x) => { assert!(false, "Opened error.wav with unexpected error {:?}", x) } @@ -19,7 +17,7 @@ fn test_open() { } #[test] -fn test_format_silence() -> Result<(),Error> { +fn test_format_silence() -> Result<(), Error> { let path = "tests/media/ff_silence.wav"; let mut w = WaveReader::open(path)?; @@ -29,7 +27,7 @@ fn test_format_silence() -> Result<(),Error> { assert_eq!(format.sample_rate, 44100); assert_eq!(format.channel_count, 1); assert_eq!(format.tag as u16, 1); - Ok( () ) + Ok(()) } #[test] @@ -44,18 +42,18 @@ fn test_format_error() { } #[test] -fn test_frame_count() -> Result<(),Error> { +fn test_frame_count() -> Result<(), Error> { let path = "tests/media/ff_silence.wav"; let mut w = WaveReader::open(path)?; let l = w.frame_length()?; assert_eq!(l, 44100); - Ok( () ) + Ok(()) } #[test] -fn test_minimal_wave() { +fn test_minimal_wave() { let path = "tests/media/ff_silence.wav"; let mut w = WaveReader::open(path).expect("Failure opening file"); @@ -86,14 +84,12 @@ fn test_read() { let mut reader = w.audio_frame_reader().unwrap(); - - assert_eq!(reader.read_integer_frame(&mut buffer).unwrap(), 1); assert_eq!(buffer[0], -2823_i32); assert_eq!(reader.read_integer_frame(&mut buffer).unwrap(), 1); assert_eq!(buffer[0], 2012_i32); assert_eq!(reader.read_integer_frame(&mut buffer).unwrap(), 1); - assert_eq!(buffer[0], 4524_i32); + assert_eq!(buffer[0], 4524_i32); } #[test] @@ -126,10 +122,10 @@ fn test_channels_stereo() { let channels = w.channels().unwrap(); assert_eq!(channels.len(), 2); - assert_eq!(channels[0].index,0); - assert_eq!(channels[1].index,1); - assert_eq!(channels[0].speaker,ChannelMask::FrontLeft); - assert_eq!(channels[1].speaker,ChannelMask::FrontRight); + assert_eq!(channels[0].index, 0); + assert_eq!(channels[1].index, 1); + assert_eq!(channels[0].speaker, ChannelMask::FrontLeft); + assert_eq!(channels[1].speaker, ChannelMask::FrontRight); } #[test] @@ -140,8 +136,8 @@ fn test_channels_mono_no_extended() { let channels = w.channels().unwrap(); assert_eq!(channels.len(), 1); - assert_eq!(channels[0].index,0); - assert_eq!(channels[0].speaker,ChannelMask::FrontCenter); + assert_eq!(channels[0].index, 0); + assert_eq!(channels[0].speaker, ChannelMask::FrontCenter); } #[test] @@ -152,19 +148,21 @@ fn test_channels_stereo_no_fmt_extended() { let channels = w.channels().unwrap(); assert_eq!(channels.len(), 2); - assert_eq!(channels[0].index,0); - assert_eq!(channels[1].index,1); - assert_eq!(channels[0].speaker,ChannelMask::FrontLeft); - assert_eq!(channels[1].speaker,ChannelMask::FrontRight); + assert_eq!(channels[0].index, 0); + assert_eq!(channels[1].index, 1); + assert_eq!(channels[0].speaker, ChannelMask::FrontLeft); + assert_eq!(channels[1].speaker, ChannelMask::FrontRight); } ///See issue 6 and 7 #[test] fn test_frame_reader_consumes_reader() { // Issue #6 - use bwavfile::{WaveFmt, AudioFrameReader}; + use bwavfile::{AudioFrameReader, WaveFmt}; use std::fs::File; - fn from_wav_filename(wav_filename: &str) -> Result<(WaveFmt, AudioFrameReader>), ()> { + fn from_wav_filename( + wav_filename: &str, + ) -> Result<(WaveFmt, AudioFrameReader>), ()> { if let Ok(mut r) = WaveReader::open(&wav_filename) { let format = r.format().unwrap(); let frame_reader = r.audio_frame_reader().unwrap(); @@ -189,34 +187,34 @@ fn test_cue_read_sounddevices() { assert_eq!(cue_points[0].label, None); assert_eq!(cue_points[0].note, None); assert_eq!(cue_points[0].offset, 90112); - + assert_eq!(cue_points[1].frame, 0); assert_eq!(cue_points[1].length, None); assert_eq!(cue_points[1].label, None); - assert_eq!(cue_points[1].note, None); + assert_eq!(cue_points[1].note, None); assert_eq!(cue_points[1].offset, 176128); - + assert_eq!(cue_points[2].frame, 0); assert_eq!(cue_points[2].length, None); assert_eq!(cue_points[2].label, None); - assert_eq!(cue_points[2].note, None); + assert_eq!(cue_points[2].note, None); assert_eq!(cue_points[2].offset, 237568); assert_eq!(cue_points[3].frame, 0); assert_eq!(cue_points[3].length, None); assert_eq!(cue_points[3].label, None); - assert_eq!(cue_points[3].note, None); + assert_eq!(cue_points[3].note, None); assert_eq!(cue_points[3].offset, 294912); assert_eq!(cue_points[4].frame, 0); assert_eq!(cue_points[4].length, None); assert_eq!(cue_points[4].label, None); - assert_eq!(cue_points[4].note, None); + assert_eq!(cue_points[4].note, None); assert_eq!(cue_points[4].offset, 380928); assert_eq!(cue_points[5].frame, 0); assert_eq!(cue_points[5].length, None); assert_eq!(cue_points[5].label, None); - assert_eq!(cue_points[5].note, None); + assert_eq!(cue_points[5].note, None); assert_eq!(cue_points[5].offset, 385024); -} \ No newline at end of file +}