prettify code

This commit is contained in:
Wuelle
2021-12-30 21:59:29 +01:00
parent 5e563cddf8
commit 9a010ca0c4
17 changed files with 1312 additions and 1060 deletions

View File

@@ -1,10 +1,10 @@
//! bilts.rs //! bilts.rs
//! (c) 2021 Jamie Hardt. All rights reserved. //! (c) 2021 Jamie Hardt. All rights reserved.
//! //!
//! This program demonstrates the creation of a wave file with a BLITS //! 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. //! alignment signal.
//! //!
//! TODO: Pre-calculate the sine waves to speed up generation //! TODO: Pre-calculate the sine waves to speed up generation
//! TODO: Make tone onsets less snappy //! TODO: Make tone onsets less snappy
@@ -12,34 +12,33 @@ use std::f64;
use std::io; use std::io;
extern crate bwavfile; extern crate bwavfile;
use bwavfile::{WaveWriter, WaveFmt, Error}; use bwavfile::{Error, WaveFmt, WaveWriter};
#[macro_use] #[macro_use]
extern crate clap; 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 //I did it this way because I'm weird
Some(t).map(|i| (i as f64) * 2f64 * f64::consts::PI / wavelength as f64 ) Some(t)
.map(|f| f.sin() ) .map(|i| (i as f64) * 2f64 * f64::consts::PI / wavelength as f64)
.map(|s| (s * amplitude as f64) as i32) .map(|f| f.sin())
.unwrap() .map(|s| (s * amplitude as f64) as i32)
.unwrap()
} }
/// Return the corresponding f32 gain for a dbfs. /// Return the corresponding f32 gain for a dbfs.
/// ///
/// Retval will always be positive /// Retval will always be positive
fn dbfs_to_f32(dbfs : f32) -> f32 { fn dbfs_to_f32(dbfs: f32) -> f32 {
10f32.powf(dbfs / 20f32) 10f32.powf(dbfs / 20f32)
} }
fn dbfs_to_signed_int(dbfs: f32, bit_depth: u16) -> i32 { 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 ((full_code as f32) * dbfs_to_f32(dbfs)) as i32
} }
#[derive(Clone, Copy, PartialEq)] #[derive(Clone, Copy, PartialEq)]
enum ToneBurst { enum ToneBurst {
/// Tone of .0 frequency (hz) for .1 duration (ms) at .2 dBfs /// Tone of .0 frequency (hz) for .1 duration (ms) at .2 dBfs
@@ -49,62 +48,54 @@ enum ToneBurst {
} }
impl ToneBurst { impl ToneBurst {
fn duration(&self, sample_rate : u32) -> u64 { fn duration(&self, sample_rate: u32) -> u64 {
match self { match self {
Self::Tone(_, dur, _) => *dur * sample_rate as u64 / 1000, 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 { trait ToneBurstSignal {
fn duration(&self, sample_rate: u32) -> u64; fn duration(&self, sample_rate: u32) -> u64;
fn signal(&self, t: u64, sample_rate: u32, bit_depth: u16) -> i32; fn signal(&self, t: u64, sample_rate: u32, bit_depth: u16) -> i32;
} }
impl ToneBurstSignal for Vec<ToneBurst> { impl ToneBurstSignal for Vec<ToneBurst> {
fn duration(&self, sample_rate: u32) -> u64 { fn duration(&self, sample_rate: u32) -> u64 {
self.iter().fold(0u64, |accum, &item| { self.iter()
accum + &item.duration(sample_rate) .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() self.iter()
.scan(0u64, |accum, &item| { .scan(0u64, |accum, &item| {
let dur = item.duration(sample_rate); let dur = item.duration(sample_rate);
let this_time_range = *accum..(*accum + dur); let this_time_range = *accum..(*accum + dur);
*accum = *accum + dur; *accum = *accum + dur;
Some( (this_time_range, item) ) Some((this_time_range, item))
}) })
.find(|(range, _)| range.contains(&t)) .find(|(range, _)| range.contains(&t))
.map(|(_, item)| { .map(|(_, item)| match item {
match item { ToneBurst::Tone(freq, _, dbfs) => {
ToneBurst::Tone(freq, _, dbfs) => { let gain = dbfs_to_signed_int(dbfs, bit_depth);
let gain = dbfs_to_signed_int(dbfs, bit_depth); sine_wave(t, gain, (sample_rate as f32 / freq) as u32)
sine_wave(t, gain, (sample_rate as f32 / freq) as u32)
},
ToneBurst::Silence(_) => {
0
}
} }
}).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 // BLITS Tone signal format
// From EBU Tech 3304 §4 - https://tech.ebu.ch/docs/tech/tech3304.pdf // From EBU Tech 3304 §4 - https://tech.ebu.ch/docs/tech/tech3304.pdf
let left_channel_sequence : Vec<ToneBurst> = vec![ let left_channel_sequence: Vec<ToneBurst> = vec![
// channel ident // channel ident
ToneBurst::Tone(880.0, 600, -18.0), ToneBurst::Tone(880.0, 600, -18.0),
ToneBurst::Silence(200), ToneBurst::Silence(200),
ToneBurst::Silence(4000), ToneBurst::Silence(4000),
// LR ident // LR ident
ToneBurst::Tone(1000.0, 1000, -18.0), ToneBurst::Tone(1000.0, 1000, -18.0),
ToneBurst::Silence(300), 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::Silence(300),
ToneBurst::Tone(1000.0, 2000, -18.0), ToneBurst::Tone(1000.0, 2000, -18.0),
ToneBurst::Silence(300), ToneBurst::Silence(300),
// Phase check, // Phase check,
ToneBurst::Tone(2000.0, 3000, -24.0), ToneBurst::Tone(2000.0, 3000, -24.0),
ToneBurst::Silence(200) ToneBurst::Silence(200),
]; ];
let right_channel_sequence : Vec<ToneBurst> = vec![ let right_channel_sequence: Vec<ToneBurst> = vec![
// channel ident // channel ident
ToneBurst::Silence(800), ToneBurst::Silence(800),
ToneBurst::Tone(880.0, 600, -18.0), ToneBurst::Tone(880.0, 600, -18.0),
ToneBurst::Silence(200), ToneBurst::Silence(200),
ToneBurst::Silence(3200), ToneBurst::Silence(3200),
// LR ident // LR ident
ToneBurst::Tone(1000.0, 5100, -18.0), ToneBurst::Tone(1000.0, 5100, -18.0),
ToneBurst::Silence(300), ToneBurst::Silence(300),
// Phase check, // Phase check,
ToneBurst::Tone(2000.0, 3000, -24.0), ToneBurst::Tone(2000.0, 3000, -24.0),
ToneBurst::Silence(200) ToneBurst::Silence(200),
]; ];
let center_channel_sequence : Vec<ToneBurst> = vec![ let center_channel_sequence: Vec<ToneBurst> = vec![
// channel ident // channel ident
ToneBurst::Silence(1600), ToneBurst::Silence(1600),
ToneBurst::Tone(1320.0, 600, -18.0), ToneBurst::Tone(1320.0, 600, -18.0),
ToneBurst::Silence(200), ToneBurst::Silence(200),
ToneBurst::Silence(2400), ToneBurst::Silence(2400),
// LR ident // LR ident
ToneBurst::Silence(5400), ToneBurst::Silence(5400),
// Phase check, // Phase check,
ToneBurst::Tone(2000.0, 3000, -24.0), ToneBurst::Tone(2000.0, 3000, -24.0),
ToneBurst::Silence(200) ToneBurst::Silence(200),
]; ];
let lfe_channel_sequence : Vec<ToneBurst> = vec![ let lfe_channel_sequence: Vec<ToneBurst> = vec![
// channel ident // channel ident
ToneBurst::Silence(2400), ToneBurst::Silence(2400),
ToneBurst::Tone(82.5, 600, -18.0), ToneBurst::Tone(82.5, 600, -18.0),
ToneBurst::Silence(200), ToneBurst::Silence(200),
ToneBurst::Silence(1600), ToneBurst::Silence(1600),
// LR ident // LR ident
ToneBurst::Silence(5400), ToneBurst::Silence(5400),
// Phase check, // Phase check,
ToneBurst::Tone(2000.0, 3000, -24.0), ToneBurst::Tone(2000.0, 3000, -24.0),
ToneBurst::Silence(200) ToneBurst::Silence(200),
]; ];
let ls_channel_sequence : Vec<ToneBurst> = vec![ let ls_channel_sequence: Vec<ToneBurst> = vec![
// channel ident // channel ident
ToneBurst::Silence(3200), ToneBurst::Silence(3200),
ToneBurst::Tone(660.0, 600, -18.0), ToneBurst::Tone(660.0, 600, -18.0),
ToneBurst::Silence(200), ToneBurst::Silence(200),
ToneBurst::Silence(800), ToneBurst::Silence(800),
// LR ident // LR ident
ToneBurst::Silence(5400), ToneBurst::Silence(5400),
// Phase check, // Phase check,
ToneBurst::Tone(2000.0, 3000, -24.0), ToneBurst::Tone(2000.0, 3000, -24.0),
ToneBurst::Silence(200) ToneBurst::Silence(200),
]; ];
let rs_channel_sequence : Vec<ToneBurst> = vec![ let rs_channel_sequence: Vec<ToneBurst> = vec![
// channel ident // channel ident
ToneBurst::Silence(4000), ToneBurst::Silence(4000),
ToneBurst::Tone(660.0, 600, -18.0), ToneBurst::Tone(660.0, 600, -18.0),
ToneBurst::Silence(200), ToneBurst::Silence(200),
// LR ident // LR ident
ToneBurst::Silence(5400), ToneBurst::Silence(5400),
// Phase check, // Phase check,
ToneBurst::Tone(2000.0, 3000, -24.0), ToneBurst::Tone(2000.0, 3000, -24.0),
ToneBurst::Silence(200) ToneBurst::Silence(200),
]; ];
let length = [&left_channel_sequence, &right_channel_sequence, let length = [
&center_channel_sequence, &lfe_channel_sequence, &left_channel_sequence,
&ls_channel_sequence, &rs_channel_sequence].iter() &right_channel_sequence,
.map(|i| i.duration(sample_rate)) &center_channel_sequence,
.max().unwrap_or(0); &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| { 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), left_channel_sequence.signal(frame, sample_rate, bits_per_sample),
center_channel_sequence.signal(frame, sample_rate, bits_per_sample), right_channel_sequence.signal(frame, sample_rate, bits_per_sample),
lfe_channel_sequence.signal(frame, sample_rate, bits_per_sample), center_channel_sequence.signal(frame, sample_rate, bits_per_sample),
ls_channel_sequence.signal(frame, sample_rate, bits_per_sample), lfe_channel_sequence.signal(frame, sample_rate, bits_per_sample),
rs_channel_sequence.signal(frame, sample_rate, bits_per_sample)) ls_channel_sequence.signal(frame, sample_rate, bits_per_sample),
rs_channel_sequence.signal(frame, sample_rate, bits_per_sample),
)
}); });
let format = WaveFmt::new_pcm_multichannel(sample_rate, bits_per_sample, 0b111111); let 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<()> { fn main() -> io::Result<()> {
let matches = App::new("blits") let matches = App::new("blits")
.version(crate_version!()) .version(crate_version!())
.author(crate_authors!()) .author(crate_authors!())
.about("Generate a BLITS 5.1 alignment tone.") .about("Generate a BLITS 5.1 alignment tone.")
.arg(Arg::with_name("sample_rate") .arg(
.long("sample-rate") Arg::with_name("sample_rate")
.short("s") .long("sample-rate")
.help("Sample rate of output") .short("s")
.default_value("48000") .help("Sample rate of output")
) .default_value("48000"),
.arg(Arg::with_name("bit_depth") )
.long("bit-depth") .arg(
.short("b") Arg::with_name("bit_depth")
.help("Bit depth of output") .long("bit-depth")
.default_value("24") .short("b")
) .help("Bit depth of output")
.arg(Arg::with_name("OUTPUT") .default_value("24"),
.help("Output wave file") )
.default_value("blits.wav") .arg(
) Arg::with_name("OUTPUT")
.get_matches(); .help("Output wave file")
.default_value("blits.wav"),
)
.get_matches();
let sample_rate = matches.value_of("sample_rate").unwrap().parse::<u32>() let sample_rate = matches
.value_of("sample_rate")
.unwrap()
.parse::<u32>()
.expect("Failed to read sample rate"); .expect("Failed to read sample rate");
let bits_per_sample = matches.value_of("bit_depth").unwrap().parse::<u16>() let bits_per_sample = matches
.value_of("bit_depth")
.unwrap()
.parse::<u16>()
.expect("Failed to read bit depth"); .expect("Failed to read bit depth");
let filename = matches.value_of("OUTPUT").unwrap(); let filename = matches.value_of("OUTPUT").unwrap();
match create_blits_file(&filename, sample_rate, bits_per_sample) { match create_blits_file(&filename, sample_rate, bits_per_sample) {
Err( Error::IOError(x) ) => panic!("IO Error: {:?}", x), Err(Error::IOError(x)) => panic!("IO Error: {:?}", x),
Err( err ) => panic!("Error: {:?}", err), Err(err) => panic!("Error: {:?}", err),
Ok(()) => Ok(()) Ok(()) => Ok(()),
} }
} }

View File

@@ -1,6 +1,6 @@
//! wave-inter.rs //! wave-inter.rs
//! (c) 2021 Jamie Hardt. All rights reserved. //! (c) 2021 Jamie Hardt. All rights reserved.
//! //!
//! This program demonstrats combining several wave files into a single //! This program demonstrats combining several wave files into a single
//! polyphonic wave file. //! polyphonic wave file.
@@ -8,13 +8,18 @@ use std::io;
use std::path::Path; use std::path::Path;
extern crate bwavfile; extern crate bwavfile;
use bwavfile::{Error,WaveReader, WaveWriter, ChannelDescriptor, ChannelMask, WaveFmt}; use bwavfile::{ChannelDescriptor, ChannelMask, Error, WaveFmt, WaveReader, WaveWriter};
#[macro_use] #[macro_use]
extern crate clap; 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 { if force_numeric || channel_descriptor.speaker == ChannelMask::DirectOut {
format!("{}A{:02}", delim, index) format!("{}A{:02}", delim, index)
} else { } else {
@@ -37,13 +42,13 @@ fn name_suffix(force_numeric : bool, delim : &str, index: usize, channel_descrip
ChannelMask::TopBackLeft => "Ltb", ChannelMask::TopBackLeft => "Ltb",
ChannelMask::TopBackCenter => "Ctb", ChannelMask::TopBackCenter => "Ctb",
ChannelMask::TopBackRight => "Rtb", ChannelMask::TopBackRight => "Rtb",
ChannelMask::DirectOut => panic!("Error, can't get here") ChannelMask::DirectOut => panic!("Error, can't get here"),
}; };
format!("{}{}", delim, chan_name) 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 mut input_file = WaveReader::open(infile)?;
let channel_desc = input_file.channels()?; let channel_desc = input_file.channels()?;
let input_format = input_file.format()?; 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 infile_path = Path::new(infile);
let basename = infile_path.file_stem().expect("Unable to extract file basename").to_str().unwrap(); let basename = infile_path
let output_dir = infile_path.parent().expect("Unable to derive parent directory"); .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()?; let mut input_wave_reader = input_file.audio_frame_reader()?;
for (n, channel) in channel_desc.iter().enumerate() { for (n, channel) in channel_desc.iter().enumerate() {
let suffix = name_suffix(numeric_channel_names, delim, n + 1, channel); let suffix = name_suffix(numeric_channel_names, delim, n + 1, channel);
let outfile_name = output_dir.join(format!("{}{}.wav", basename, suffix)) let outfile_name = output_dir
.into_os_string().into_string().unwrap(); .join(format!("{}{}.wav", basename, suffix))
.into_os_string()
.into_string()
.unwrap();
println!("Will create file {}", outfile_name); 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 output_wave_writer = output_file.audio_frame_writer()?;
let mut buffer = input_format.create_frame_buffer(1); let mut buffer = input_format.create_frame_buffer(1);
@@ -88,25 +104,28 @@ fn main() -> io::Result<()> {
.version(crate_version!()) .version(crate_version!())
.author(crate_authors!()) .author(crate_authors!())
.about("Extract each channel of a polyphonic wave file as a new monoaural wave file.") .about("Extract each channel of a polyphonic wave file as a new monoaural wave file.")
.arg(Arg::with_name("numeric_names") .arg(
.long("numeric") Arg::with_name("numeric_names")
.short("n") .long("numeric")
.help("Use numeric channel names \"01\" \"02\" etc.") .short("n")
.takes_value(false) .help("Use numeric channel names \"01\" \"02\" etc.")
.takes_value(false),
) )
.arg(Arg::with_name("channel_delimiter") .arg(
.long("delim") Arg::with_name("channel_delimiter")
.short("d") .long("delim")
.help("Channel label delimiter.") .short("d")
.default_value(".") .help("Channel label delimiter.")
.default_value("."),
) )
.arg(Arg::with_name("INPUT") .arg(
.help("Input wave file") Arg::with_name("INPUT")
.required(true) .help("Input wave file")
.multiple(true) .required(true)
.multiple(true),
) )
.get_matches(); .get_matches();
let delimiter = matches.value_of("channel_delimiter").unwrap(); let delimiter = matches.value_of("channel_delimiter").unwrap();
let use_numeric_names = matches.is_present("numeric_names"); let use_numeric_names = matches.is_present("numeric_names");
let infile = matches.value_of("INPUT").unwrap(); let infile = matches.value_of("INPUT").unwrap();
@@ -114,6 +133,6 @@ fn main() -> io::Result<()> {
match process_file(infile, delimiter, use_numeric_names) { match process_file(infile, delimiter, use_numeric_names) {
Err(Error::IOError(io)) => Err(io), Err(Error::IOError(io)) => Err(io),
Err(e) => panic!("Error: {:?}", e), Err(e) => panic!("Error: {:?}", e),
Ok(()) => Ok(()) Ok(()) => Ok(()),
} }
} }

View File

@@ -1,6 +1,6 @@
//! wave-inter.rs //! wave-inter.rs
//! (c) 2021 Jamie Hardt. All rights reserved. //! (c) 2021 Jamie Hardt. All rights reserved.
//! //!
//! This program demonstrates combining several wave files into a single //! This program demonstrates combining several wave files into a single
//! polyphonic wave file. //! polyphonic wave file.
@@ -10,7 +10,7 @@ extern crate bwavfile;
#[macro_use] #[macro_use]
extern crate clap; extern crate clap;
use clap::{Arg, App}; use clap::{App, Arg};
fn main() -> io::Result<()> { fn main() -> io::Result<()> {
let matches = App::new("wave-inter") let matches = App::new("wave-inter")
@@ -32,4 +32,4 @@ fn main() -> io::Result<()> {
println!("Command line opts: {:?}", matches); println!("Command line opts: {:?}", matches);
todo!("Finish implementation"); todo!("Finish implementation");
} }

View File

@@ -1,15 +1,13 @@
pub type LU = f32; pub type LU = f32;
pub type LUFS = f32; pub type LUFS = f32;
pub type Decibels = f32; pub type Decibels = f32;
/// Broadcast-WAV metadata record. /// Broadcast-WAV metadata record.
/// ///
/// The `bext` record contains information about the original recording of the /// The `bext` record contains information about the original recording of the
/// Wave file, including a longish (256 ASCII chars) description field, /// Wave file, including a longish (256 ASCII chars) description field,
/// originator identification fields, creation calendar date and time, a /// originator identification fields, creation calendar date and time, a
/// sample-accurate recording time field, and a SMPTE UMID. /// sample-accurate recording time field, and a SMPTE UMID.
/// ///
/// For a Wave file to be a complaint "Broadcast-WAV" file, it must contain /// For a Wave file to be a complaint "Broadcast-WAV" file, it must contain
/// a `bext` metadata record. /// a `bext` metadata record.
@@ -17,7 +15,7 @@ pub type Decibels = f32;
/// ## Resources /// ## Resources
/// - [EBU Tech 3285](https://tech.ebu.ch/docs/tech/tech3285.pdf). /// - [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 &lt;CodingHistory&gt; field in Broadcast Wave Format files, BWF" /// - [EBU Tech R098](https://tech.ebu.ch/docs/r/r098.pdf) (1999) "Format for the &lt;CodingHistory&gt; 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
/// &lt;OriginatorReference&gt; field of the Broadcast Wave Format" /// &lt;OriginatorReference&gt; field of the Broadcast Wave Format"
// Note for me later: // Note for me later:
@@ -26,7 +24,6 @@ pub type Decibels = f32;
#[derive(Debug)] #[derive(Debug)]
pub struct Bext { pub struct Bext {
/// 256 ASCII character field with free text. /// 256 ASCII character field with free text.
pub description: String, pub description: String,
@@ -47,23 +44,23 @@ pub struct Bext {
pub time_reference: u64, pub time_reference: u64,
/// Bext chunk version. /// 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. /// loudness metadata.
pub version: u16, pub version: u16,
/// SMPTE 330M UMID /// SMPTE 330M UMID
/// ///
/// This field is `None` if the version is less than 1. /// This field is `None` if the version is less than 1.
pub umid: Option<[u8; 64]>, pub umid: Option<[u8; 64]>,
/// Integrated loudness in LUFS. /// Integrated loudness in LUFS.
/// ///
/// This field is `None` if the version is less than 2. /// This field is `None` if the version is less than 2.
pub loudness_value: Option<LUFS>, pub loudness_value: Option<LUFS>,
/// Loudness range in LU. /// Loudness range in LU.
/// ///
/// This field is `None` if the version is less than 2. /// This field is `None` if the version is less than 2.
pub loudness_range: Option<LU>, pub loudness_range: Option<LU>,
@@ -78,11 +75,10 @@ pub struct Bext {
pub max_momentary_loudness: Option<LUFS>, pub max_momentary_loudness: Option<LUFS>,
/// Maximum short-term loudness in LUFS. /// Maximum short-term loudness in LUFS.
/// ///
/// This field is `None` if the version is less than 2. /// This field is `None` if the version is less than 2.
pub max_short_term_loudness: Option<LUFS>, pub max_short_term_loudness: Option<LUFS>,
// 180 bytes of nothing // 180 bytes of nothing
/// Coding History. /// Coding History.
pub coding_history: String pub coding_history: String,
} }

View File

@@ -1,33 +1,40 @@
use std::io::{Read, Write}; use std::io::{Read, Write};
use encoding::{DecoderTrap, EncoderTrap};
use encoding::{Encoding};
use encoding::all::ASCII; use encoding::all::ASCII;
use encoding::Encoding;
use encoding::{DecoderTrap, EncoderTrap};
use byteorder::LittleEndian; use byteorder::LittleEndian;
use byteorder::{ReadBytesExt, WriteBytesExt}; use byteorder::{ReadBytesExt, WriteBytesExt};
use uuid::Uuid; use uuid::Uuid;
use super::bext::Bext;
use super::errors::Error as ParserError; use super::errors::Error as ParserError;
use super::fmt::{WaveFmt, WaveFmtExtended}; use super::fmt::{WaveFmt, WaveFmtExtended};
use super::bext::Bext;
pub trait ReadBWaveChunks: Read { pub trait ReadBWaveChunks: Read {
fn read_bext(&mut self) -> Result<Bext, ParserError>; fn read_bext(&mut self) -> Result<Bext, ParserError>;
fn read_bext_string_field(&mut self, length: usize) -> Result<String,ParserError>; fn read_bext_string_field(&mut self, length: usize) -> Result<String, ParserError>;
fn read_wave_fmt(&mut self) -> Result<WaveFmt, ParserError>; fn read_wave_fmt(&mut self) -> Result<WaveFmt, ParserError>;
} }
pub trait WriteBWaveChunks: Write { pub trait WriteBWaveChunks: Write {
fn write_wave_fmt(&mut self, format : &WaveFmt) -> 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_string_field(
fn write_bext(&mut self, bext: &Bext) -> Result<(),ParserError>; &mut self,
string: &String,
length: usize,
) -> Result<(), ParserError>;
fn write_bext(&mut self, bext: &Bext) -> Result<(), ParserError>;
} }
impl<T> WriteBWaveChunks for T where T: Write { impl<T> WriteBWaveChunks for T
fn write_wave_fmt(&mut self, format : &WaveFmt) -> Result<(), ParserError> { where
self.write_u16::<LittleEndian>(format.tag as u16 )?; T: Write,
{
fn write_wave_fmt(&mut self, format: &WaveFmt) -> Result<(), ParserError> {
self.write_u16::<LittleEndian>(format.tag as u16)?;
self.write_u16::<LittleEndian>(format.channel_count)?; self.write_u16::<LittleEndian>(format.channel_count)?;
self.write_u32::<LittleEndian>(format.sample_rate)?; self.write_u32::<LittleEndian>(format.sample_rate)?;
self.write_u32::<LittleEndian>(format.bytes_per_second)?; self.write_u32::<LittleEndian>(format.bytes_per_second)?;
@@ -44,12 +51,18 @@ impl<T> WriteBWaveChunks for T where T: Write {
Ok(()) Ok(())
} }
fn write_bext_string_field(&mut self, string: &String, length: usize) -> Result<(),ParserError> { fn write_bext_string_field(
let mut buf = ASCII.encode(&string, EncoderTrap::Ignore).expect("Error encoding text"); &mut self,
string: &String,
length: usize,
) -> Result<(), ParserError> {
let mut buf = ASCII
.encode(&string, EncoderTrap::Ignore)
.expect("Error encoding text");
buf.truncate(length); buf.truncate(length);
let filler_length = length - buf.len(); let filler_length = length - buf.len();
if filler_length > 0{ if filler_length > 0 {
let mut filler = vec![0u8; filler_length ]; let mut filler = vec![0u8; filler_length];
buf.append(&mut filler); buf.append(&mut filler);
} }
@@ -57,7 +70,7 @@ impl<T> WriteBWaveChunks for T where T: Write {
Ok(()) 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.description, 256)?;
self.write_bext_string_field(&bext.originator, 32)?; self.write_bext_string_field(&bext.originator, 32)?;
self.write_bext_string_field(&bext.originator_reference, 32)?; self.write_bext_string_field(&bext.originator_reference, 32)?;
@@ -69,21 +82,21 @@ impl<T> WriteBWaveChunks for T where T: Write {
let buf = bext.umid.unwrap_or([0u8; 64]); let buf = bext.umid.unwrap_or([0u8; 64]);
self.write_all(&buf)?; self.write_all(&buf)?;
self.write_i16::<LittleEndian>( self.write_i16::<LittleEndian>((bext.loudness_value.unwrap_or(0.0) * 100.0) as i16)?;
(bext.loudness_value.unwrap_or(0.0) * 100.0) as i16 )?; self.write_i16::<LittleEndian>((bext.loudness_range.unwrap_or(0.0) * 100.0) as i16)?;
self.write_i16::<LittleEndian>( self.write_i16::<LittleEndian>((bext.max_true_peak_level.unwrap_or(0.0) * 100.0) as i16)?;
(bext.loudness_range.unwrap_or(0.0) * 100.0) as i16 )?; self.write_i16::<LittleEndian>(
self.write_i16::<LittleEndian>( (bext.max_momentary_loudness.unwrap_or(0.0) * 100.0) as i16,
(bext.max_true_peak_level.unwrap_or(0.0) * 100.0) as i16 )?; )?;
self.write_i16::<LittleEndian>( self.write_i16::<LittleEndian>(
(bext.max_momentary_loudness.unwrap_or(0.0) * 100.0) as i16 )?; (bext.max_short_term_loudness.unwrap_or(0.0) * 100.0) as i16,
self.write_i16::<LittleEndian>( )?;
(bext.max_short_term_loudness.unwrap_or(0.0) * 100.0) as i16 )?;
let padding = [0u8; 180]; let padding = [0u8; 180];
self.write_all(&padding)?; self.write_all(&padding)?;
let coding = ASCII.encode(&bext.coding_history, EncoderTrap::Ignore) let coding = ASCII
.encode(&bext.coding_history, EncoderTrap::Ignore)
.expect("Error"); .expect("Error");
self.write_all(&coding)?; self.write_all(&coding)?;
@@ -91,20 +104,22 @@ impl<T> WriteBWaveChunks for T where T: Write {
} }
} }
impl<T> ReadBWaveChunks for T where T: Read { impl<T> ReadBWaveChunks for T
where
T: Read,
{
fn read_wave_fmt(&mut self) -> Result<WaveFmt, ParserError> { fn read_wave_fmt(&mut self) -> Result<WaveFmt, ParserError> {
let tag_value : u16; let tag_value: u16;
Ok(WaveFmt { Ok(WaveFmt {
tag: { tag: {
tag_value = self.read_u16::<LittleEndian>()?; tag_value = self.read_u16::<LittleEndian>()?;
tag_value tag_value
}, },
channel_count: self.read_u16::<LittleEndian>()?, channel_count: self.read_u16::<LittleEndian>()?,
sample_rate: self.read_u32::<LittleEndian>()?, sample_rate: self.read_u32::<LittleEndian>()?,
bytes_per_second: self.read_u32::<LittleEndian>()?, bytes_per_second: self.read_u32::<LittleEndian>()?,
block_alignment: self.read_u16::<LittleEndian>()?, block_alignment: self.read_u16::<LittleEndian>()?,
bits_per_sample: self.read_u16::<LittleEndian>()?, bits_per_sample: self.read_u16::<LittleEndian>()?,
extended_format: { extended_format: {
if tag_value == 0xFFFE { if tag_value == 0xFFFE {
let cb_size = self.read_u16::<LittleEndian>()?; let cb_size = self.read_u16::<LittleEndian>()?;
@@ -113,77 +128,111 @@ impl<T> ReadBWaveChunks for T where T: Read {
valid_bits_per_sample: self.read_u16::<LittleEndian>()?, valid_bits_per_sample: self.read_u16::<LittleEndian>()?,
channel_mask: self.read_u32::<LittleEndian>()?, channel_mask: self.read_u32::<LittleEndian>()?,
type_guid: { type_guid: {
let mut buf : [u8; 16] = [0; 16]; let mut buf: [u8; 16] = [0; 16];
self.read_exact(&mut buf)?; self.read_exact(&mut buf)?;
Uuid::from_slice(&buf)? Uuid::from_slice(&buf)?
} },
}) })
} else { } else {
None None
} }
} },
}) })
} }
fn read_bext_string_field(&mut self, length: usize) -> Result<String,ParserError> { fn read_bext_string_field(&mut self, length: usize) -> Result<String, ParserError> {
let mut buffer : Vec<u8> = vec![0; length]; let mut buffer: Vec<u8> = vec![0; length];
self.read(&mut buffer)?; self.read(&mut buffer)?;
let trimmed : Vec<u8> = buffer.iter().take_while(|c| **c != 0 as u8).cloned().collect(); let trimmed: Vec<u8> = buffer
Ok(ASCII.decode(&trimmed, DecoderTrap::Ignore).expect("Error decoding text")) .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<Bext, ParserError> { fn read_bext(&mut self) -> Result<Bext, ParserError> {
let version : u16; let version: u16;
Ok( Bext { Ok(Bext {
description: self.read_bext_string_field(256)?, description: self.read_bext_string_field(256)?,
originator: self.read_bext_string_field(32)?, originator: self.read_bext_string_field(32)?,
originator_reference : self.read_bext_string_field(32)?, originator_reference: self.read_bext_string_field(32)?,
origination_date : self.read_bext_string_field(10)?, origination_date: self.read_bext_string_field(10)?,
origination_time : self.read_bext_string_field(8)?, origination_time: self.read_bext_string_field(8)?,
time_reference: self.read_u64::<LittleEndian>()?, time_reference: self.read_u64::<LittleEndian>()?,
version: { version: {
version = self.read_u16::<LittleEndian>()?; version = self.read_u16::<LittleEndian>()?;
version version
}, },
umid: { umid: {
let mut buf = [0u8 ; 64]; let mut buf = [0u8; 64];
self.read(&mut buf)?; self.read(&mut buf)?;
if version > 0 { Some(buf) } else { None } if version > 0 {
}, Some(buf)
loudness_value: { } else {
let val = (self.read_i16::<LittleEndian>()? as f32) / 100f32; None
if version > 1 { Some(val) } else { None }
},
loudness_range: {
let val = self.read_i16::<LittleEndian>()? as f32 / 100f32;
if version > 1 { Some(val) } else { None }
},
max_true_peak_level: {
let val = self.read_i16::<LittleEndian>()? as f32 / 100f32;
if version > 1 { Some(val) } else { None }
},
max_momentary_loudness: {
let val = self.read_i16::<LittleEndian>()? as f32 / 100f32;
if version > 1 { Some(val) } else { None }
},
max_short_term_loudness: {
let val = self.read_i16::<LittleEndian>()? 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")
} }
},
loudness_value: {
let val = (self.read_i16::<LittleEndian>()? as f32) / 100f32;
if version > 1 {
Some(val)
} else {
None
}
},
loudness_range: {
let val = self.read_i16::<LittleEndian>()? as f32 / 100f32;
if version > 1 {
Some(val)
} else {
None
}
},
max_true_peak_level: {
let val = self.read_i16::<LittleEndian>()? as f32 / 100f32;
if version > 1 {
Some(val)
} else {
None
}
},
max_momentary_loudness: {
let val = self.read_i16::<LittleEndian>()? as f32 / 100f32;
if version > 1 {
Some(val)
} else {
None
}
},
max_short_term_loudness: {
let val = self.read_i16::<LittleEndian>()? 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] #[test]
fn test_read_51_wav() { fn test_read_51_wav() {
use super::fmt::ChannelMask;
use super::common_format::CommonFormat; use super::common_format::CommonFormat;
use super::fmt::ChannelMask;
let path = "tests/media/pt_24bit_51.wav"; 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); let channels = ChannelMask::channels(extended.channel_mask, format.channel_count);
assert_eq!(channels, [ChannelMask::FrontLeft, ChannelMask::FrontRight, assert_eq!(
ChannelMask::FrontCenter, ChannelMask::LowFrequency, channels,
ChannelMask::BackLeft, ChannelMask::BackRight]); [
ChannelMask::FrontLeft,
ChannelMask::FrontRight,
ChannelMask::FrontCenter,
ChannelMask::LowFrequency,
ChannelMask::BackLeft,
ChannelMask::BackRight
]
);
assert_eq!(format.common_format(), CommonFormat::IntegerPCM); assert_eq!(format.common_format(), CommonFormat::IntegerPCM);
} }

View File

@@ -1,9 +1,9 @@
use uuid::Uuid; use uuid::Uuid;
const BASIC_PCM: u16 = 0x0001; const BASIC_PCM: u16 = 0x0001;
const BASIC_FLOAT: u16 = 0x0003; const BASIC_FLOAT: u16 = 0x0003;
const BASIC_MPEG: u16 = 0x0050; const BASIC_MPEG: u16 = 0x0050;
const BASIC_EXTENDED: u16 = 0xFFFE; const BASIC_EXTENDED: u16 = 0xFFFE;
/* RC 2361 §4: /* 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, pub const UUID_PCM: Uuid = Uuid::from_bytes([
0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71]); 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, pub const UUID_FLOAT: Uuid = Uuid::from_bytes([
0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71]); 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, pub const UUID_MPEG: Uuid = Uuid::from_bytes([
0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71]); 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, pub const UUID_BFORMAT_PCM: Uuid = Uuid::from_bytes([
0x86, 0x44, 0xc8, 0xc1, 0xca, 0x00, 0x00, 0x00]); 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_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 { 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() Uuid::from_fields_le(tag as u32, 0x0000, 0x0010, &tail).unwrap()
} }
/// Sample format of the Wave file. /// Sample format of the Wave file.
/// ///
/// ///
#[derive(Debug, Copy, Clone, PartialEq)] #[derive(Debug, Copy, Clone, PartialEq)]
pub enum CommonFormat { pub enum CommonFormat {
/// Integer linear PCM /// Integer linear PCM
IntegerPCM, IntegerPCM,
/// IEEE Floating-point Linear PCM /// IEEE Floating-point Linear PCM
IeeeFloatPCM, IeeeFloatPCM,
/// MPEG /// MPEG
Mpeg, Mpeg,
/// Ambisonic B-Format Linear PCM /// Ambisonic B-Format Linear PCM
AmbisonicBFormatIntegerPCM, AmbisonicBFormatIntegerPCM,
/// Ambisonic B-Format Float PCM /// Ambisonic B-Format Float PCM
AmbisonicBFormatIeeeFloatPCM, AmbisonicBFormatIeeeFloatPCM,
/// An unknown format identified by a basic format tag. /// An unknown format identified by a basic format tag.
UnknownBasic(u16), UnknownBasic(u16),
/// An unknown format identified by an extension UUID. /// An unknown format identified by an extension UUID.
UnknownExtended(Uuid), UnknownExtended(Uuid),
} }
@@ -70,18 +74,18 @@ impl CommonFormat {
(BASIC_PCM, _) => Self::IntegerPCM, (BASIC_PCM, _) => Self::IntegerPCM,
(BASIC_FLOAT, _) => Self::IeeeFloatPCM, (BASIC_FLOAT, _) => Self::IeeeFloatPCM,
(BASIC_MPEG, _) => Self::Mpeg, (BASIC_MPEG, _) => Self::Mpeg,
(BASIC_EXTENDED, Some(UUID_PCM)) => Self::IntegerPCM, (BASIC_EXTENDED, Some(UUID_PCM)) => Self::IntegerPCM,
(BASIC_EXTENDED, Some(UUID_FLOAT))=> Self::IeeeFloatPCM, (BASIC_EXTENDED, Some(UUID_FLOAT)) => Self::IeeeFloatPCM,
(BASIC_EXTENDED, Some(UUID_BFORMAT_PCM)) => Self::AmbisonicBFormatIntegerPCM, (BASIC_EXTENDED, Some(UUID_BFORMAT_PCM)) => Self::AmbisonicBFormatIntegerPCM,
(BASIC_EXTENDED, Some(UUID_BFORMAT_FLOAT)) => Self::AmbisonicBFormatIeeeFloatPCM, (BASIC_EXTENDED, Some(UUID_BFORMAT_FLOAT)) => Self::AmbisonicBFormatIeeeFloatPCM,
(BASIC_EXTENDED, Some(x)) => CommonFormat::UnknownExtended(x), (BASIC_EXTENDED, Some(x)) => CommonFormat::UnknownExtended(x),
(x, _) => CommonFormat::UnknownBasic(x) (x, _) => CommonFormat::UnknownBasic(x),
} }
} }
/// Get the appropriate tag and `Uuid` for the callee. /// 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. /// returned tag will be 0xFFFE and the `Uuid` will describe the format.
pub fn take(self) -> (u16, Uuid) { pub fn take(self) -> (u16, Uuid) {
match self { match self {
@@ -90,8 +94,8 @@ impl CommonFormat {
Self::Mpeg => (BASIC_MPEG, UUID_MPEG), Self::Mpeg => (BASIC_MPEG, UUID_MPEG),
Self::AmbisonicBFormatIntegerPCM => (BASIC_EXTENDED, UUID_BFORMAT_PCM), Self::AmbisonicBFormatIntegerPCM => (BASIC_EXTENDED, UUID_BFORMAT_PCM),
Self::AmbisonicBFormatIeeeFloatPCM => (BASIC_EXTENDED, UUID_BFORMAT_FLOAT), Self::AmbisonicBFormatIeeeFloatPCM => (BASIC_EXTENDED, UUID_BFORMAT_FLOAT),
Self::UnknownBasic(x) => ( x, uuid_from_basic_tag(x) ), Self::UnknownBasic(x) => (x, uuid_from_basic_tag(x)),
Self::UnknownExtended(x) => ( BASIC_EXTENDED, x) Self::UnknownExtended(x) => (BASIC_EXTENDED, x),
} }
} }
} }

View File

@@ -1,30 +1,30 @@
use super::fourcc::{FourCC,ReadFourCC, WriteFourCC, LABL_SIG, NOTE_SIG, use super::fourcc::{
ADTL_SIG, LTXT_SIG, DATA_SIG}; FourCC, ReadFourCC, WriteFourCC, ADTL_SIG, DATA_SIG, LABL_SIG, LTXT_SIG, NOTE_SIG,
};
use super::list_form::collect_list_form; 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::all::ASCII;
use encoding::Encoding;
use encoding::{DecoderTrap, EncoderTrap};
use std::io::{Cursor, Error, Read, Write}; use std::io::{Cursor, Error, Read, Write};
#[derive(Copy,Clone, Debug)] #[derive(Copy, Clone, Debug)]
struct RawCue { struct RawCue {
cue_point_id : u32, cue_point_id: u32,
frame : u32, frame: u32,
chunk_id : FourCC, chunk_id: FourCC,
chunk_start : u32, chunk_start: u32,
block_start : u32, block_start: u32,
frame_offset : u32 frame_offset: u32,
} }
impl RawCue { impl RawCue {
fn write_to(cues: Vec<Self>) -> Vec<u8> {
fn write_to(cues : Vec<Self>) -> Vec<u8> {
let mut writer = Cursor::new(vec![0u8; 0]); let mut writer = Cursor::new(vec![0u8; 0]);
writer.write_u32::<LittleEndian>(cues.len() as u32).unwrap(); writer.write_u32::<LittleEndian>(cues.len() as u32).unwrap();
for cue in cues.iter() { for cue in cues.iter() {
writer.write_u32::<LittleEndian>(cue.cue_point_id).unwrap(); writer.write_u32::<LittleEndian>(cue.cue_point_id).unwrap();
@@ -38,64 +38,64 @@ impl RawCue {
writer.into_inner() writer.into_inner()
} }
fn read_from(data : &[u8]) -> Result<Vec<Self>,Error> { fn read_from(data: &[u8]) -> Result<Vec<Self>, Error> {
let mut rdr = Cursor::new(data); let mut rdr = Cursor::new(data);
let count = rdr.read_u32::<LittleEndian>()?; let count = rdr.read_u32::<LittleEndian>()?;
let mut retval : Vec<Self> = vec![]; let mut retval: Vec<Self> = vec![];
for _ in 0..count { for _ in 0..count {
retval.push( Self { retval.push(Self {
cue_point_id : rdr.read_u32::<LittleEndian>()?, cue_point_id: rdr.read_u32::<LittleEndian>()?,
frame : rdr.read_u32::<LittleEndian>()?, frame: rdr.read_u32::<LittleEndian>()?,
chunk_id : rdr.read_fourcc()?, chunk_id: rdr.read_fourcc()?,
chunk_start : rdr.read_u32::<LittleEndian>()?, chunk_start: rdr.read_u32::<LittleEndian>()?,
block_start : rdr.read_u32::<LittleEndian>()?, block_start: rdr.read_u32::<LittleEndian>()?,
frame_offset : rdr.read_u32::<LittleEndian>()? frame_offset: rdr.read_u32::<LittleEndian>()?,
}) })
} }
Ok( retval ) Ok(retval)
} }
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
struct RawLabel { struct RawLabel {
cue_point_id : u32, cue_point_id: u32,
text : Vec<u8> text: Vec<u8>,
} }
impl RawLabel { impl RawLabel {
fn write_to(&self) -> Vec<u8> { fn write_to(&self) -> Vec<u8> {
let mut writer = Cursor::new(vec![0u8;0]); let mut writer = Cursor::new(vec![0u8; 0]);
writer.write_u32::<LittleEndian>(self.cue_point_id as u32).unwrap(); writer
.write_u32::<LittleEndian>(self.cue_point_id as u32)
.unwrap();
writer.write(&self.text).unwrap(); writer.write(&self.text).unwrap();
writer.into_inner() writer.into_inner()
} }
fn read_from(data : &[u8]) -> Result<Self, Error> { fn read_from(data: &[u8]) -> Result<Self, Error> {
let mut rdr = Cursor::new(data); let mut rdr = Cursor::new(data);
let length = data.len(); let length = data.len();
Ok( Self { Ok(Self {
cue_point_id : rdr.read_u32::<LittleEndian>()?, cue_point_id: rdr.read_u32::<LittleEndian>()?,
text : { text: {
let mut buf = vec![0u8; (length - 4) as usize ]; let mut buf = vec![0u8; (length - 4) as usize];
rdr.read_exact(&mut buf)?; rdr.read_exact(&mut buf)?;
buf buf
} },
}) })
} }
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
struct RawNote { struct RawNote {
cue_point_id : u32, cue_point_id: u32,
text : Vec<u8> text: Vec<u8>,
} }
impl RawNote { impl RawNote {
fn write_to(&self) -> Vec<u8> { fn write_to(&self) -> Vec<u8> {
let mut writer = Cursor::new(vec![0u8; 0]); let mut writer = Cursor::new(vec![0u8; 0]);
writer.write_u32::<LittleEndian>(self.cue_point_id).unwrap(); writer.write_u32::<LittleEndian>(self.cue_point_id).unwrap();
@@ -103,35 +103,34 @@ impl RawNote {
writer.into_inner() writer.into_inner()
} }
fn read_from(data : &[u8]) -> Result<Self, Error> { fn read_from(data: &[u8]) -> Result<Self, Error> {
let mut rdr = Cursor::new(data); let mut rdr = Cursor::new(data);
let length = data.len(); let length = data.len();
Ok( Self { Ok(Self {
cue_point_id : rdr.read_u32::<LittleEndian>()?, cue_point_id: rdr.read_u32::<LittleEndian>()?,
text : { text: {
let mut buf = vec![0u8; (length - 4) as usize ]; let mut buf = vec![0u8; (length - 4) as usize];
rdr.read_exact(&mut buf)?; rdr.read_exact(&mut buf)?;
buf buf
} },
}) })
} }
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
struct RawLtxt { struct RawLtxt {
cue_point_id : u32, cue_point_id: u32,
frame_length : u32, frame_length: u32,
purpose : FourCC, purpose: FourCC,
country : u16, country: u16,
language : u16, language: u16,
dialect : u16, dialect: u16,
code_page : u16, code_page: u16,
text: Option<Vec<u8>> text: Option<Vec<u8>>,
} }
impl RawLtxt { impl RawLtxt {
fn write_to(&self) -> Vec<u8> { fn write_to(&self) -> Vec<u8> {
let mut writer = Cursor::new(vec![0u8; 0]); let mut writer = Cursor::new(vec![0u8; 0]);
writer.write_u32::<LittleEndian>(self.cue_point_id).unwrap(); writer.write_u32::<LittleEndian>(self.cue_point_id).unwrap();
@@ -147,27 +146,27 @@ impl RawLtxt {
writer.into_inner() writer.into_inner()
} }
fn read_from(data : &[u8]) -> Result<Self, Error> { fn read_from(data: &[u8]) -> Result<Self, Error> {
let mut rdr = Cursor::new(data); let mut rdr = Cursor::new(data);
let length = data.len(); let length = data.len();
Ok( Self { Ok(Self {
cue_point_id : rdr.read_u32::<LittleEndian>()?, cue_point_id: rdr.read_u32::<LittleEndian>()?,
frame_length : rdr.read_u32::<LittleEndian>()?, frame_length: rdr.read_u32::<LittleEndian>()?,
purpose : rdr.read_fourcc()?, purpose: rdr.read_fourcc()?,
country : rdr.read_u16::<LittleEndian>()?, country: rdr.read_u16::<LittleEndian>()?,
language : rdr.read_u16::<LittleEndian>()?, language: rdr.read_u16::<LittleEndian>()?,
dialect : rdr.read_u16::<LittleEndian>()?, dialect: rdr.read_u16::<LittleEndian>()?,
code_page : rdr.read_u16::<LittleEndian>()?, code_page: rdr.read_u16::<LittleEndian>()?,
text : { text: {
if length - 20 > 0 { if length - 20 > 0 {
let mut buf = vec![0u8; (length - 20) as usize]; let mut buf = vec![0u8; (length - 20) as usize];
rdr.read_exact(&mut buf)?; rdr.read_exact(&mut buf)?;
Some( buf ) Some(buf)
} else { } else {
None None
} }
} },
}) })
} }
} }
@@ -177,223 +176,227 @@ enum RawAdtlMember {
Label(RawLabel), Label(RawLabel),
Note(RawNote), Note(RawNote),
LabeledText(RawLtxt), LabeledText(RawLtxt),
Unrecognized(FourCC) Unrecognized(FourCC),
} }
impl RawAdtlMember { impl RawAdtlMember {
fn compile_adtl(members : &[Self]) -> Vec<u8> { fn compile_adtl(members: &[Self]) -> Vec<u8> {
let mut w = Cursor::new(vec![0u8; 0]); let mut w = Cursor::new(vec![0u8; 0]);
// It seems like all this casing could be done with traits // It seems like all this casing could be done with traits
for member in members.iter() { for member in members.iter() {
let (fcc, buf) = match member { let (fcc, buf) = match member {
RawAdtlMember::Label(l) => ( (LABL_SIG, l.write_to()) ), RawAdtlMember::Label(l) => ((LABL_SIG, l.write_to())),
RawAdtlMember::Note(n) => ( (NOTE_SIG, n.write_to()) ), RawAdtlMember::Note(n) => ((NOTE_SIG, n.write_to())),
RawAdtlMember::LabeledText(t) => ( (LTXT_SIG, t.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::Unrecognized(f) => (*f, vec![0u8; 0]), // <-- this is a dopey case but here for completeness
}; };
w.write_fourcc(fcc).unwrap(); w.write_fourcc(fcc).unwrap();
w.write_u32::<LittleEndian>(buf.len() as u32).unwrap(); w.write_u32::<LittleEndian>(buf.len() as u32).unwrap();
w.write(&buf).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 chunk_content = w.into_inner();
let mut writer = Cursor::new(vec![0u8; 0]); let mut writer = Cursor::new(vec![0u8; 0]);
writer.write_fourcc(ADTL_SIG).unwrap(); writer.write_fourcc(ADTL_SIG).unwrap();
writer.write_u32::<LittleEndian>(chunk_content.len() as u32).unwrap(); writer
.write_u32::<LittleEndian>(chunk_content.len() as u32)
.unwrap();
writer.write(&chunk_content).unwrap(); writer.write(&chunk_content).unwrap();
writer.into_inner() writer.into_inner()
} }
fn collect_from(chunk : &[u8]) -> Result<Vec<RawAdtlMember>,Error> { fn collect_from(chunk: &[u8]) -> Result<Vec<RawAdtlMember>, Error> {
let chunks = collect_list_form(chunk)?; let chunks = collect_list_form(chunk)?;
let mut retval : Vec<RawAdtlMember> = vec![]; let mut retval: Vec<RawAdtlMember> = vec![];
for chunk in chunks.iter() { for chunk in chunks.iter() {
retval.push( retval.push(match chunk.signature {
match chunk.signature { LABL_SIG => RawAdtlMember::Label(RawLabel::read_from(&chunk.contents)?),
LABL_SIG => RawAdtlMember::Label( RawLabel::read_from(&chunk.contents)? ), NOTE_SIG => RawAdtlMember::Note(RawNote::read_from(&chunk.contents)?),
NOTE_SIG => RawAdtlMember::Note( RawNote::read_from(&chunk.contents)? ), LTXT_SIG => RawAdtlMember::LabeledText(RawLtxt::read_from(&chunk.contents)?),
LTXT_SIG => RawAdtlMember::LabeledText( RawLtxt::read_from(&chunk.contents)? ), x => RawAdtlMember::Unrecognized(x),
x => RawAdtlMember::Unrecognized(x) })
}
)
} }
Ok( retval ) Ok(retval)
} }
} }
trait AdtlMemberSearch { trait AdtlMemberSearch {
fn labels_for_cue_point(&self, id: u32) -> Vec<&RawLabel>; 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>; fn ltxt_for_cue_point(&self, id: u32) -> Vec<&RawLtxt>;
} }
impl AdtlMemberSearch for Vec<RawAdtlMember> { impl AdtlMemberSearch for Vec<RawAdtlMember> {
fn labels_for_cue_point(&self, id: u32) -> Vec<&RawLabel> { fn labels_for_cue_point(&self, id: u32) -> Vec<&RawLabel> {
self.iter().filter_map(|item| { self.iter()
match item { .filter_map(|item| match item {
RawAdtlMember::Label(x) if x.cue_point_id == id => Some(x), RawAdtlMember::Label(x) if x.cue_point_id == id => Some(x),
_ => None _ => None,
} })
}) .collect()
.collect()
} }
fn notes_for_cue_point(&self, id: u32) -> Vec<&RawNote> { fn notes_for_cue_point(&self, id: u32) -> Vec<&RawNote> {
self.iter().filter_map(|item| { self.iter()
match item { .filter_map(|item| match item {
RawAdtlMember::Note(x) if x.cue_point_id == id => Some(x), RawAdtlMember::Note(x) if x.cue_point_id == id => Some(x),
_ => None _ => None,
} })
}) .collect()
.collect()
} }
fn ltxt_for_cue_point(&self, id: u32) -> Vec<&RawLtxt> { fn ltxt_for_cue_point(&self, id: u32) -> Vec<&RawLtxt> {
self.iter().filter_map(|item| { self.iter()
match item { .filter_map(|item| match item {
RawAdtlMember::LabeledText(x) if x.cue_point_id == id => Some(x), RawAdtlMember::LabeledText(x) if x.cue_point_id == id => Some(x),
_ => None _ => None,
} })
}) .collect()
.collect()
} }
} }
/// A cue point recorded in the `cue` and `adtl` metadata. /// A cue point recorded in the `cue` and `adtl` metadata.
/// ///
/// ## Resources /// ## Resources
/// - [Cue list, label and other metadata](https://sites.google.com/site/musicgapi/technical-documents/wav-file-format#smpl) /// - [Cue list, label and other metadata](https://sites.google.com/site/musicgapi/technical-documents/wav-file-format#smpl)
/// ///
/// ### Not Implemented /// ### Not Implemented
/// - [EBU 3285 Supplement 2](https://tech.ebu.ch/docs/tech/tech3285s2.pdf) (July 2001): Quality chunk and cuesheet /// - [EBU 3285 Supplement 2](https://tech.ebu.ch/docs/tech/tech3285s2.pdf) (July 2001): Quality chunk and cuesheet
pub struct Cue { pub struct Cue {
/// The time of this marker /// The time of this marker
pub frame : u32, pub frame: u32,
/// The length of this marker, if it is a range /// The length of this marker, if it is a range
pub length : Option<u32>, pub length: Option<u32>,
/// The text "label"/name of this marker if provided /// The text "label"/name of this marker if provided
pub label : Option<String>, pub label: Option<String>,
/// The text "note"/comment of this marker if provided /// The text "note"/comment of this marker if provided
pub note : Option<String>, pub note: Option<String>,
/// The offser of this marker /// The offser of this marker
/// ///
/// **Note:** Applications use the `frame` and `offset` fields /// **Note:** Applications use the `frame` and `offset` fields
/// in different ways. iZotope RX Audio Editor writes the /// in different ways. iZotope RX Audio Editor writes the
/// marker position to *both* fields, while a Sound Devices /// marker position to *both* fields, while a Sound Devices
/// recorder writes the marker position to *only* the `offset` /// recorder writes the marker position to *only* the `offset`
/// field. /// field.
pub offset : u32 pub offset: u32,
} }
fn convert_to_cue_string(buffer: &[u8]) -> String {
fn convert_to_cue_string(buffer : &[u8]) -> String { let trimmed: Vec<u8> = buffer
let trimmed : Vec<u8> = buffer.iter().take_while(|c| **c != 0 as u8).cloned().collect(); .iter()
ASCII.decode(&trimmed, DecoderTrap::Ignore).expect("Error decoding text") .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<u8> { fn convert_from_cue_string(val: &str) -> Vec<u8> {
ASCII.encode(&val, EncoderTrap::Ignore).expect("Error encoding text") ASCII
.encode(&val, EncoderTrap::Ignore)
.expect("Error encoding text")
} }
impl Cue { impl Cue {
/// Take a list of `Cue`s and convert it into `RawCue` and `RawAdtlMember`s /// Take a list of `Cue`s and convert it into `RawCue` and `RawAdtlMember`s
fn compile_to(cues : &[Cue]) -> (Vec<RawCue>, Vec<RawAdtlMember>) { fn compile_to(cues: &[Cue]) -> (Vec<RawCue>, Vec<RawAdtlMember>) {
cues.iter().enumerate() cues.iter()
.enumerate()
.map(|(n, cue)| { .map(|(n, cue)| {
let raw_cue = RawCue { let raw_cue = RawCue {
cue_point_id: n as u32, cue_point_id: n as u32,
frame : cue.frame, frame: cue.frame,
chunk_id: DATA_SIG, chunk_id: DATA_SIG,
chunk_start: 0, chunk_start: 0,
block_start: 0, block_start: 0,
frame_offset: cue.offset frame_offset: cue.offset,
}; };
let raw_label = cue.label.as_ref().map(|val| { let raw_label = cue.label.as_ref().map(|val| RawLabel {
RawLabel { cue_point_id: n as u32,
cue_point_id : n as u32, text: convert_from_cue_string(&val),
text: convert_from_cue_string(&val)
}
}); });
let raw_note = cue.note.as_ref().map(|val| { let raw_note = cue.note.as_ref().map(|val| RawNote {
RawNote { cue_point_id: n as u32,
cue_point_id: n as u32, text: convert_from_cue_string(&val),
text : convert_from_cue_string(&val)
}
}); });
let raw_ltxt = cue.length.map(|val| { let raw_ltxt = cue.length.map(|val| RawLtxt {
RawLtxt { cue_point_id: n as u32,
cue_point_id : n as u32, frame_length: val,
frame_length : val, purpose: FourCC::make(b"rgn "),
purpose : FourCC::make(b"rgn "), country: 0,
country : 0, language: 0,
language : 0, dialect: 0,
dialect : 0, code_page: 0,
code_page : 0, text: None,
text : None
}
}); });
(raw_cue, raw_label, raw_note, raw_ltxt) (raw_cue, raw_label, raw_note, raw_ltxt)
}) })
.fold((Vec::<RawCue>::new(), Vec::<RawAdtlMember>::new()), .fold(
|(mut cues, mut adtls) , (cue, label, note, ltxt)| { (Vec::<RawCue>::new(), Vec::<RawAdtlMember>::new()),
cues.push(cue); |(mut cues, mut adtls), (cue, label, note, ltxt)| {
label.map(|l| adtls.push( RawAdtlMember::Label(l))); cues.push(cue);
note.map(|n| adtls.push(RawAdtlMember::Note(n))); label.map(|l| adtls.push(RawAdtlMember::Label(l)));
ltxt.map(|m| adtls.push(RawAdtlMember::LabeledText(m))); note.map(|n| adtls.push(RawAdtlMember::Note(n)));
(cues, adtls) ltxt.map(|m| adtls.push(RawAdtlMember::LabeledText(m)));
}) (cues, adtls)
},
)
} }
pub fn collect_from(cue_chunk : &[u8], adtl_chunk : Option<&[u8]>) -> Result<Vec<Cue>, Error> { pub fn collect_from(cue_chunk: &[u8], adtl_chunk: Option<&[u8]>) -> Result<Vec<Cue>, Error> {
let raw_cues = RawCue::read_from(cue_chunk)?; let raw_cues = RawCue::read_from(cue_chunk)?;
let raw_adtl : Vec<RawAdtlMember>; let raw_adtl: Vec<RawAdtlMember>;
if let Some(adtl) = adtl_chunk { if let Some(adtl) = adtl_chunk {
raw_adtl = RawAdtlMember::collect_from(adtl)?; raw_adtl = RawAdtlMember::collect_from(adtl)?;
} else { } else {
raw_adtl = vec![]; raw_adtl = vec![];
} }
Ok( Ok(raw_cues
raw_cues.iter() .iter()
.map(|i| { .map(|i| {
Cue { Cue {
//ident : i.cue_point_id, //ident : i.cue_point_id,
frame : i.frame, frame: i.frame,
length: { length: {
raw_adtl.ltxt_for_cue_point(i.cue_point_id).first() raw_adtl
.filter(|x| x.purpose == FourCC::make(b"rgn ")) .ltxt_for_cue_point(i.cue_point_id)
.map(|x| x.frame_length) .first()
.filter(|x| x.purpose == FourCC::make(b"rgn "))
.map(|x| x.frame_length)
}, },
label: { 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)) .map(|s| convert_to_cue_string(&s.text))
.next() .next()
}, },
note : { note: {
raw_adtl.notes_for_cue_point(i.cue_point_id).iter() raw_adtl
.notes_for_cue_point(i.cue_point_id)
.iter()
//.filter_map(|x| str::from_utf8(&x.text).ok()) //.filter_map(|x| str::from_utf8(&x.text).ok())
.map(|s| convert_to_cue_string(&s.text)) .map(|s| convert_to_cue_string(&s.text))
.next() .next()
}, },
offset: i.frame_offset offset: i.frame_offset,
} }
}).collect() })
) .collect())
} }
} }

View File

@@ -1,13 +1,15 @@
use std::{fmt::{Debug,Display}, io};
use std::error::Error as StdError;
use super::fourcc::FourCC; use super::fourcc::FourCC;
use std::error::Error as StdError;
use std::{
fmt::{Debug, Display},
io,
};
use uuid; use uuid;
/// Errors returned by methods in this crate. /// Errors returned by methods in this crate.
#[derive(Debug)] #[derive(Debug)]
pub enum Error { pub enum Error {
/// An `io::Error` occurred /// An `io::Error` occurred
IOError(io::Error), IOError(io::Error),
@@ -16,14 +18,14 @@ pub enum Error {
/// The file does not begin with a recognized WAVE header /// The file does not begin with a recognized WAVE header
HeaderNotRecognized, HeaderNotRecognized,
/// A wave file with a 64-bit header does not contain /// A wave file with a 64-bit header does not contain
/// the required `ds64` metadata element /// the required `ds64` metadata element
MissingRequiredDS64, MissingRequiredDS64,
/// A data chunk required to complete the operation /// A data chunk required to complete the operation
/// is not present in the file /// is not present in the file
ChunkMissing { signature : FourCC }, ChunkMissing { signature: FourCC },
/// The file is formatted improperly /// The file is formatted improperly
FmtChunkAfterData, FmtChunkAfterData,
@@ -37,11 +39,10 @@ pub enum Error {
/// The file cannot be converted into an RF64 file due /// The file cannot be converted into an RF64 file due
/// to its internal structure /// to its internal structure
InsufficientDS64Reservation {expected: u64, actual: u64}, InsufficientDS64Reservation { expected: u64, actual: u64 },
/// The file is not optimized for writing new data /// The file is not optimized for writing new data
DataChunkNotPreparedForAppend, DataChunkNotPreparedForAppend,
} }
impl StdError for Error {} impl StdError for Error {}
@@ -52,15 +53,14 @@ impl Display for Error {
} }
} }
impl From<io::Error> for Error { impl From<io::Error> for Error {
fn from(error: io::Error) -> Error { fn from(error: io::Error) -> Error {
Error::IOError(error) Error::IOError(error)
} }
} }
impl From <uuid::Error> for Error { impl From<uuid::Error> for Error {
fn from(error: uuid::Error) -> Error { fn from(error: uuid::Error) -> Error {
Error::UuidError(error) Error::UuidError(error)
} }
} }

View File

@@ -1,32 +1,32 @@
use uuid::Uuid; use super::common_format::{CommonFormat, UUID_BFORMAT_PCM, UUID_PCM};
use super::common_format::{CommonFormat, UUID_PCM,UUID_BFORMAT_PCM};
use std::io::Cursor; use std::io::Cursor;
use uuid::Uuid;
use byteorder::LittleEndian; use byteorder::LittleEndian;
use byteorder::{WriteBytesExt, ReadBytesExt}; use byteorder::{ReadBytesExt, WriteBytesExt};
// Need more test cases for ADMAudioID // Need more test cases for ADMAudioID
#[allow(dead_code)] #[allow(dead_code)]
/// ADM Audio ID record. /// ADM Audio ID record.
/// ///
/// This structure relates a channel in the wave file to either a common ADM /// 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. /// 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`. /// `AudioProgramme`.
/// ///
/// See BS.2088-1 § 8, also BS.2094, also blahblahblah... /// See BS.2088-1 § 8, also BS.2094, also blahblahblah...
#[derive(Debug)] #[derive(Debug)]
pub struct ADMAudioID { pub struct ADMAudioID {
pub track_uid: [char; 12], pub track_uid: [char; 12],
pub channel_format_ref: [char; 14], pub channel_format_ref: [char; 14],
pub pack_ref: [char; 11] pub pack_ref: [char; 11],
} }
/// Describes a single channel in a WAV file. /// Describes a single channel in a WAV file.
/// ///
/// This information is correlated from the Wave format ChannelMap field and /// This information is correlated from the Wave format ChannelMap field and
/// the `chna` chunk, if present. /// the `chna` chunk, if present.
#[derive(Debug)] #[derive(Debug)]
@@ -35,7 +35,7 @@ pub struct ChannelDescriptor {
pub index: u16, pub index: u16,
/// Channel assignment /// Channel assignment
/// ///
/// This is either implied (in the case of mono or stereo wave files) or /// This is either implied (in the case of mono or stereo wave files) or
/// explicitly given in `WaveFormatExtentended` for files with more tracks. /// explicitly given in `WaveFormatExtentended` for files with more tracks.
pub speaker: ChannelMask, pub speaker: ChannelMask,
@@ -44,40 +44,38 @@ pub struct ChannelDescriptor {
pub adm_track_audio_ids: Vec<ADMAudioID>, pub adm_track_audio_ids: Vec<ADMAudioID>,
} }
/// A bitmask indicating which channels are present in
/// A bitmask indicating which channels are present in
/// the file. /// the file.
/// ///
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Debug, Clone, Copy, PartialEq)]
pub enum ChannelMask { pub enum ChannelMask {
DirectOut = 0x0, DirectOut = 0x0,
FrontLeft = 0x1, FrontLeft = 0x1,
FrontRight = 0x2, FrontRight = 0x2,
FrontCenter = 0x4, FrontCenter = 0x4,
LowFrequency = 0x8, LowFrequency = 0x8,
BackLeft = 0x10, BackLeft = 0x10,
BackRight = 0x20, BackRight = 0x20,
FrontCenterLeft = 0x40, FrontCenterLeft = 0x40,
FrontCenterRight = 0x80, FrontCenterRight = 0x80,
BackCenter = 0x100, BackCenter = 0x100,
SideLeft = 0x200, SideLeft = 0x200,
SideRight = 0x400, SideRight = 0x400,
TopCenter = 0x800, TopCenter = 0x800,
TopFrontLeft = 0x1000, TopFrontLeft = 0x1000,
TopFrontCenter = 0x2000, TopFrontCenter = 0x2000,
TopFrontRight = 0x4000, TopFrontRight = 0x4000,
TopBackLeft = 0x8000, TopBackLeft = 0x8000,
TopBackCenter = 0x10000, TopBackCenter = 0x10000,
TopBackRight = 0x20000, TopBackRight = 0x20000,
} }
impl From<u32> for ChannelMask { impl From<u32> for ChannelMask {
fn from(value: u32) -> Self {
fn from(value: u32) -> Self {
match value { match value {
0x1 => Self::FrontLeft, 0x1 => Self::FrontLeft,
0x2 => Self::FrontRight, 0x2 => Self::FrontRight,
0x4 => Self::FrontCenter, 0x4 => Self::FrontCenter,
0x8 => Self::LowFrequency, 0x8 => Self::LowFrequency,
0x10 => Self::BackLeft, 0x10 => Self::BackLeft,
0x20 => Self::BackRight, 0x20 => Self::BackRight,
@@ -87,24 +85,25 @@ impl From<u32> for ChannelMask {
0x200 => Self::SideLeft, 0x200 => Self::SideLeft,
0x400 => Self::SideRight, 0x400 => Self::SideRight,
0x800 => Self::TopCenter, 0x800 => Self::TopCenter,
0x1000 => Self::TopFrontLeft, 0x1000 => Self::TopFrontLeft,
0x2000 => Self::TopFrontCenter, 0x2000 => Self::TopFrontCenter,
0x4000 => Self::TopFrontRight, 0x4000 => Self::TopFrontRight,
0x8000 => Self::TopBackLeft, 0x8000 => Self::TopBackLeft,
0x10000 => Self::TopBackCenter, 0x10000 => Self::TopBackCenter,
0x20000 => Self::TopBackRight, 0x20000 => Self::TopBackRight,
_ => Self::DirectOut _ => Self::DirectOut,
} }
} }
} }
impl ChannelMask { impl ChannelMask {
pub fn channels(input_mask : u32, channel_count: u16) -> Vec<ChannelMask> { pub fn channels(input_mask: u32, channel_count: u16) -> Vec<ChannelMask> {
let reserved_mask = 0xfff2_0000_u32; let reserved_mask = 0xfff2_0000_u32;
if (input_mask & reserved_mask) > 0 { if (input_mask & reserved_mask) > 0 {
vec![ ChannelMask::DirectOut ; channel_count as usize ] vec![ChannelMask::DirectOut; channel_count as usize]
} else { } else {
(0..18).map(|i| 1 << i ) (0..18)
.map(|i| 1 << i)
.filter(|mask| mask & input_mask > 0) .filter(|mask| mask & input_mask > 0)
.map(|mask| Into::<ChannelMask>::into(mask)) .map(|mask| Into::<ChannelMask>::into(mask))
.collect() .collect()
@@ -114,36 +113,35 @@ impl ChannelMask {
/** /**
* Extended Wave Format * Extended Wave Format
* *
* https://docs.microsoft.com/en-us/windows/win32/api/mmreg/ns-mmreg-waveformatextensible * https://docs.microsoft.com/en-us/windows/win32/api/mmreg/ns-mmreg-waveformatextensible
*/ */
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct WaveFmtExtended { pub struct WaveFmtExtended {
/// Valid bits per sample /// Valid bits per sample
pub valid_bits_per_sample : u16, pub valid_bits_per_sample: u16,
/// Channel mask /// Channel mask
/// ///
/// Identifies the speaker assignment for each channel in the file /// Identifies the speaker assignment for each channel in the file
pub channel_mask : u32, pub channel_mask: u32,
/// Codec GUID /// Codec GUID
/// ///
/// Identifies the codec of the audio stream /// Identifies the codec of the audio stream
pub type_guid : Uuid, pub type_guid: Uuid,
} }
/// ///
/// WAV file data format record. /// WAV file data format record.
/// ///
/// The `fmt` record contains essential information describing the binary /// 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. /// rate, sample binary format, channel count, etc.
/// ///
/// ///
/// ## Resources /// ## Resources
/// ///
/// ### Implementation of Wave format `fmt` chunk /// ### Implementation of Wave format `fmt` chunk
/// - [MSDN WAVEFORMATEX](https://docs.microsoft.com/en-us/windows/win32/api/mmeapi/ns-mmeapi-waveformatex) /// - [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) /// - [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" /// - [RFC 3261][rfc3261] (June 1998) "WAVE and AVI Codec Registries"
/// - [Sampler Metadata](http://www.piclist.com/techref/io/serial/midi/wave.html) /// - [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) /// - [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 /// (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)] #[derive(Debug, Copy, Clone)]
pub struct WaveFmt { pub struct WaveFmt {
/// A tag identifying the codec in use. /// A tag identifying the codec in use.
/// ///
/// If this is 0xFFFE, the codec will be identified by a GUID /// If this is 0xFFFE, the codec will be identified by a GUID
/// in `extended_format` /// in `extended_format`
pub tag: u16, pub tag: u16,
@@ -176,12 +172,12 @@ pub struct WaveFmt {
pub sample_rate: u32, pub sample_rate: u32,
/// Count of bytes per second /// Count of bytes per second
/// ///
/// By rule, this is `block_alignment * sample_rate` /// By rule, this is `block_alignment * sample_rate`
pub bytes_per_second: u32, pub bytes_per_second: u32,
/// Count of bytes per audio frame /// Count of bytes per audio frame
/// ///
/// By rule, this is `channel_count * bits_per_sample / 8` /// By rule, this is `channel_count * bits_per_sample / 8`
pub block_alignment: u16, pub block_alignment: u16,
@@ -199,15 +195,13 @@ pub struct WaveFmt {
pub bits_per_sample: u16, pub bits_per_sample: u16,
/// Extended format description /// Extended format description
/// ///
/// Additional format metadata if `channel_count` is greater than 2, /// Additional format metadata if `channel_count` is greater than 2,
/// or if certain codecs are used. /// or if certain codecs are used.
pub extended_format: Option<WaveFmtExtended> pub extended_format: Option<WaveFmtExtended>,
} }
impl WaveFmt { impl WaveFmt {
pub fn valid_bits_per_sample(&self) -> u16 { pub fn valid_bits_per_sample(&self) -> u16 {
if let Some(ext) = self.extended_format { if let Some(ext) = self.extended_format {
ext.valid_bits_per_sample ext.valid_bits_per_sample
@@ -221,7 +215,7 @@ impl WaveFmt {
Self::new_pcm_multichannel(sample_rate, bits_per_sample, 0x4) 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. /// stream.
pub fn new_pcm_stereo(sample_rate: u32, bits_per_sample: u16) -> Self { pub fn new_pcm_stereo(sample_rate: u32, bits_per_sample: u16) -> Self {
Self::new_pcm_multichannel(sample_rate, bits_per_sample, 0x3) 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. /// 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 { 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_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 { WaveFmt {
tag : 0xFFFE, tag: 0xFFFE,
channel_count, channel_count,
sample_rate, 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, block_alignment: container_bytes_per_sample * channel_count,
bits_per_sample: container_bits_per_sample, bits_per_sample: container_bits_per_sample,
extended_format: Some(WaveFmtExtended { extended_format: Some(WaveFmtExtended {
valid_bits_per_sample: bits_per_sample, valid_bits_per_sample: bits_per_sample,
channel_mask: ChannelMask::DirectOut as u32, 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. /// Create a new integer PCM format `WaveFmt` with a custom channel bitmap.
/// ///
/// The order of `channels` is not important. When reading or writing /// The order of `channels` is not important. When reading or writing
/// audio frames you must use the standard multichannel order for Wave /// audio frames you must use the standard multichannel order for Wave
/// files, the numerical order of the cases of `ChannelMask`. /// 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_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;
let channel_count: u16 = (0..=31).fold(0u16, |accum, n| accum + (0x1 & (channel_bitmap >> n) as u16) );
let result : (u16, Option<WaveFmtExtended>) = match channel_bitmap { let channel_count: u16 = (0..=31).fold(0u16, |accum, n| {
ch if bits_per_sample != container_bits_per_sample => ( accum + (0x1 & (channel_bitmap >> n) as u16)
(0xFFFE, Some(WaveFmtExtended { valid_bits_per_sample: bits_per_sample, channel_mask: ch, });
type_guid: UUID_PCM }) )
), let result: (u16, Option<WaveFmtExtended>) = 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), 0b0100 => (0x0001, None),
0b0011 => (0x0001, None), 0b0011 => (0x0001, None),
ch => ( ch => {
(0xFFFE, Some( WaveFmtExtended { valid_bits_per_sample: bits_per_sample, channel_mask: ch, ((
type_guid: UUID_PCM})) 0xFFFE,
) Some(WaveFmtExtended {
valid_bits_per_sample: bits_per_sample,
channel_mask: ch,
type_guid: UUID_PCM,
}),
))
}
}; };
let (tag, extformat) = result; let (tag, extformat) = result;
@@ -277,33 +291,35 @@ impl WaveFmt {
tag, tag,
channel_count, channel_count,
sample_rate, 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, block_alignment: container_bytes_per_sample * channel_count,
bits_per_sample: container_bits_per_sample, bits_per_sample: container_bits_per_sample,
extended_format: extformat extended_format: extformat,
} }
} }
/// Format or codec of the file's audio data. /// Format or codec of the file's audio data.
/// ///
/// The `CommonFormat` unifies the format tag and the format extension GUID. Use this /// The `CommonFormat` unifies the format tag and the format extension GUID. Use this
/// method to determine the codec. /// method to determine the codec.
pub fn common_format(&self) -> CommonFormat { 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 /// writer
/// ///
/// This is a conveneince method that creates a `Vec<i32>` with /// This is a conveneince method that creates a `Vec<i32>` with
/// as many elements as there are channels in the underlying stream. /// as many elements as there are channels in the underlying stream.
pub fn create_frame_buffer(&self, length : usize) -> Vec<i32> { pub fn create_frame_buffer(&self, length: usize) -> Vec<i32> {
vec![0i32; self.channel_count as usize * length] 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 /// writer
pub fn create_raw_buffer(&self, length : usize) -> Vec<u8> { pub fn create_raw_buffer(&self, length: usize) -> Vec<u8> {
vec![0u8; self.block_alignment as usize * length] 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]) -> () { pub fn pack_frames(&self, from_frames: &[i32], into_bytes: &mut [u8]) -> () {
let mut write_cursor = Cursor::new(into_bytes); let mut write_cursor = Cursor::new(into_bytes);
assert!(from_frames.len() % self.channel_count as usize == 0, assert!(
"frames buffer does not contain a number of samples % channel_count == 0"); 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() { for n in 0..from_frames.len() {
match (self.valid_bits_per_sample(), self.bits_per_sample) { 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 (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::<LittleEndian>(from_frames[n] as i16).unwrap(), (9..=16,16) => write_cursor.write_i16::<LittleEndian>(from_frames[n] as i16).unwrap(),
(10..=24,24) => write_cursor.write_i24::<LittleEndian>(from_frames[n]).unwrap(), (10..=24,24) => write_cursor.write_i24::<LittleEndian>(from_frames[n]).unwrap(),
@@ -323,7 +341,7 @@ impl WaveFmt {
(b,_)=> panic!("Unrecognized integer format, bits per sample {}, channels {}, block_alignment {}", (b,_)=> panic!("Unrecognized integer format, bits per sample {}, channels {}, block_alignment {}",
b, self.channel_count, self.block_alignment) b, self.channel_count, self.block_alignment)
} }
} }
() ()
} }
@@ -342,55 +360,69 @@ impl WaveFmt {
} }
} }
/// Channel descriptors for each channel. /// Channel descriptors for each channel.
pub fn channels(&self) -> Vec<ChannelDescriptor> { pub fn channels(&self) -> Vec<ChannelDescriptor> {
match self.channel_count { match self.channel_count {
1 => vec![ 1 => vec![ChannelDescriptor {
ChannelDescriptor { index: 0,
index: 0, speaker: ChannelMask::FrontCenter,
speaker: ChannelMask::FrontCenter, adm_track_audio_ids: vec![],
adm_track_audio_ids: vec![] }],
}
],
2 => vec![ 2 => vec![
ChannelDescriptor { ChannelDescriptor {
index: 0, index: 0,
speaker: ChannelMask::FrontLeft, speaker: ChannelMask::FrontLeft,
adm_track_audio_ids: vec![] adm_track_audio_ids: vec![],
}, },
ChannelDescriptor { ChannelDescriptor {
index: 1, index: 1,
speaker: ChannelMask::FrontRight, speaker: ChannelMask::FrontRight,
adm_track_audio_ids: vec![] adm_track_audio_ids: vec![],
} },
], ],
x if x > 2 => { x if x > 2 => {
let channel_mask = self.extended_format.map(|x| x.channel_mask).unwrap_or(0); 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 = 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) (0..self.channel_count)
.zip(channels_expanded) .zip(channels_expanded)
.map(|(n,chan)| ChannelDescriptor { .map(|(n, chan)| ChannelDescriptor {
index: n, index: n,
speaker: *chan, speaker: *chan,
adm_track_audio_ids: vec![] adm_track_audio_ids: vec![],
}).collect() })
}, .collect()
}
x => panic!("Channel count ({}) was illegal!", x), x => panic!("Channel count ({}) was illegal!", x),
} }
} }
} }
trait ReadWavAudioData { trait ReadWavAudioData {
fn read_i32_frames(&mut self, format: WaveFmt, into: &mut [i32]) -> Result<usize,std::io::Error>; fn read_i32_frames(
fn read_f32_frames(&mut self, format: WaveFmt, into: &mut [f32]) -> Result<usize,std::io::Error>; &mut self,
format: WaveFmt,
into: &mut [i32],
) -> Result<usize, std::io::Error>;
fn read_f32_frames(
&mut self,
format: WaveFmt,
into: &mut [f32],
) -> Result<usize, std::io::Error>;
} }
impl<T> ReadWavAudioData for T where T: std::io::Read { impl<T> ReadWavAudioData for T
where
fn read_i32_frames(&mut self, format: WaveFmt, into: &mut [i32]) -> Result<usize, std::io::Error> { T: std::io::Read,
{
fn read_i32_frames(
&mut self,
format: WaveFmt,
into: &mut [i32],
) -> Result<usize, std::io::Error> {
assert!(into.len() % format.channel_count as usize == 0); assert!(into.len() % format.channel_count as usize == 0);
for n in 0..(into.len()) { for n in 0..(into.len()) {
@@ -406,11 +438,14 @@ impl<T> ReadWavAudioData for T where T: std::io::Read {
todo!() todo!()
} }
fn read_f32_frames(&mut self, format: WaveFmt, into: &mut [f32]) -> Result<usize, std::io::Error> { fn read_f32_frames(
&mut self,
format: WaveFmt,
into: &mut [f32],
) -> Result<usize, std::io::Error> {
assert!(into.len() % format.channel_count as usize == 0); assert!(into.len() % format.channel_count as usize == 0);
todo!() todo!()
} }
} }
trait WriteWavAudioData { trait WriteWavAudioData {
@@ -418,12 +453,14 @@ trait WriteWavAudioData {
fn write_f32_frames(&mut self, format: WaveFmt, from: &[f32]) -> Result<usize, std::io::Error>; fn write_f32_frames(&mut self, format: WaveFmt, from: &[f32]) -> Result<usize, std::io::Error>;
} }
impl<T> WriteWavAudioData for T where T: std::io::Write { impl<T> WriteWavAudioData for T
where
fn write_i32_frames(&mut self, format: WaveFmt, _: &[i32]) -> Result<usize, std::io::Error> { T: std::io::Write,
todo!() {
fn write_i32_frames(&mut self, format: WaveFmt, _: &[i32]) -> Result<usize, std::io::Error> {
todo!()
} }
fn write_f32_frames(&mut self, format: WaveFmt, _: &[f32]) -> Result<usize, std::io::Error> { fn write_f32_frames(&mut self, format: WaveFmt, _: &[f32]) -> Result<usize, std::io::Error> {
todo!() todo!()
} }
} }

View File

@@ -2,7 +2,7 @@ use std::fmt::Debug;
use std::io; use std::io;
/// A Four-character Code /// A Four-character Code
/// ///
/// For idetifying chunks, structured contiguous slices or segments /// For idetifying chunks, structured contiguous slices or segments
/// within a WAV file. /// within a WAV file.
#[derive(Eq, PartialEq, Hash, Copy, Clone)] #[derive(Eq, PartialEq, Hash, Copy, Clone)]
@@ -15,8 +15,13 @@ impl FourCC {
} }
impl From<[char; 4]> for FourCC { impl From<[char; 4]> for FourCC {
fn from(chars : [char; 4]) -> Self { fn from(chars: [char; 4]) -> Self {
Self([chars[0] as u8 , chars[1] as u8, chars[2] as u8, chars[3] as u8]) Self([
chars[0] as u8,
chars[1] as u8,
chars[2] as u8,
chars[3] as u8,
])
} }
} }
@@ -32,37 +37,45 @@ impl From<FourCC> for [u8; 4] {
} }
} }
impl From<&FourCC> for [char; 4] {
impl From<&FourCC> for [char;4] { fn from(f: &FourCC) -> Self {
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,] 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] { impl From<FourCC> for [char; 4] {
fn from( f: FourCC) -> Self { 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,] [
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 { impl From<&FourCC> for String {
fn from(f: &FourCC) -> Self { fn from(f: &FourCC) -> Self {
let chars: [char;4] = f.into(); let chars: [char; 4] = f.into();
chars.iter().collect::<String>() chars.iter().collect::<String>()
} }
} }
impl From<FourCC> for String { impl From<FourCC> for String {
fn from(f: FourCC) -> Self { fn from(f: FourCC) -> Self {
let chars: [char;4] = f.into(); let chars: [char; 4] = f.into();
chars.iter().collect::<String>() chars.iter().collect::<String>()
} }
} }
impl Debug for FourCC { impl Debug for FourCC {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { 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) write!(f, "FourCC({})", s)
} }
} }
@@ -72,30 +85,35 @@ pub trait ReadFourCC: io::Read {
} }
pub trait WriteFourCC: io::Write { 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<T> ReadFourCC for T where T: io::Read { impl<T> ReadFourCC for T
where
T: io::Read,
{
fn read_fourcc(&mut self) -> Result<FourCC, io::Error> { fn read_fourcc(&mut self) -> Result<FourCC, io::Error> {
let mut buf : [u8; 4] = [0 ; 4]; let mut buf: [u8; 4] = [0; 4];
self.read_exact(&mut buf)?; self.read_exact(&mut buf)?;
Ok( FourCC::from(buf) ) Ok(FourCC::from(buf))
} }
} }
impl<T> WriteFourCC for T where T: io::Write { impl<T> WriteFourCC for T
fn write_fourcc(&mut self, fourcc :FourCC) -> Result<(), io::Error> { where
let buf : [u8; 4] = fourcc.into(); T: io::Write,
{
fn write_fourcc(&mut self, fourcc: FourCC) -> Result<(), io::Error> {
let buf: [u8; 4] = fourcc.into();
self.write_all(&buf)?; self.write_all(&buf)?;
Ok(()) Ok(())
} }
} }
pub const RIFF_SIG: FourCC = FourCC::make(b"RIFF"); pub const RIFF_SIG: FourCC = FourCC::make(b"RIFF");
pub const WAVE_SIG: FourCC = FourCC::make(b"WAVE"); pub const WAVE_SIG: FourCC = FourCC::make(b"WAVE");
pub const RF64_SIG: FourCC = FourCC::make(b"RF64"); pub const RF64_SIG: FourCC = FourCC::make(b"RF64");
pub const DS64_SIG: FourCC = FourCC::make(b"ds64"); pub const DS64_SIG: FourCC = FourCC::make(b"ds64");
pub const BW64_SIG: FourCC = FourCC::make(b"BW64"); pub const BW64_SIG: FourCC = FourCC::make(b"BW64");
pub const DATA_SIG: FourCC = FourCC::make(b"data"); 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 NOTE_SIG: FourCC = FourCC::make(b"note");
pub const LTXT_SIG: FourCC = FourCC::make(b"ltxt"); pub const LTXT_SIG: FourCC = FourCC::make(b"ltxt");
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@@ -125,7 +142,7 @@ mod tests {
#[test] #[test]
fn test_to_string() { fn test_to_string() {
let a = FourCC::make(b"a1b2"); let a = FourCC::make(b"a1b2");
let s : String = a.into(); let s: String = a.into();
assert_eq!(s, "a1b2"); assert_eq!(s, "a1b2");
} }
} }

View File

@@ -1,29 +1,29 @@
/*! /*!
# bwavfile # bwavfile
Rust Wave File Reader/Writer with Broadcast-WAV, MBWF and RF64 Support Rust Wave File Reader/Writer with Broadcast-WAV, MBWF and RF64 Support
## Interfaces ## Interfaces
### `WaveReader` ### `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 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`. accessor method of `WaveReader`.
### `WaveWriter` ### `WaveWriter`
`WaveWriter` can create a new Wave, Broadcast-Wave, or RF64/BW64 64-bit 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 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 a Wave file which is automatically promoted from standard Wave to RF64 wave
when the total WAVE form size exceeds 0xFFFFFFFF bytes. when the total WAVE form size exceeds 0xFFFFFFFF bytes.
## Objectives and Roadmap ## Objectives and Roadmap
This package aims to support read and writing any kind of WAV file you are likely 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 to encounter in a professional audio, motion picture production, broadcast, or music
production. production.
Apps we test against: Apps we test against:
@@ -36,29 +36,29 @@ Apps we test against:
[github]: https://github.com/iluvcapra/bwavfile [github]: https://github.com/iluvcapra/bwavfile
*/ */
extern crate encoding;
extern crate byteorder; extern crate byteorder;
extern crate encoding;
extern crate uuid; extern crate uuid;
mod fourcc;
mod errors;
mod common_format; mod common_format;
mod errors;
mod fourcc;
mod parser;
mod list_form; mod list_form;
mod parser;
mod bext;
mod chunks; mod chunks;
mod cue; mod cue;
mod bext;
mod fmt; mod fmt;
mod wavereader; mod wavereader;
mod wavewriter; mod wavewriter;
pub use errors::Error;
pub use wavereader::{WaveReader, AudioFrameReader};
pub use wavewriter::{WaveWriter, AudioFrameWriter};
pub use bext::Bext; pub use bext::Bext;
pub use fmt::{WaveFmt, WaveFmtExtended, ChannelDescriptor, ChannelMask, ADMAudioID};
pub use common_format::CommonFormat; pub use common_format::CommonFormat;
pub use cue::Cue; 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};

View File

@@ -1,22 +1,22 @@
use super::fourcc::{FourCC, ReadFourCC}; use super::fourcc::{FourCC, ReadFourCC};
use byteorder::{ReadBytesExt, LittleEndian}; use byteorder::{LittleEndian, ReadBytesExt};
use std::io::{Cursor, Error, Read}; use std::io::{Cursor, Error, Read};
pub struct ListFormItem { pub struct ListFormItem {
pub signature : FourCC, pub signature: FourCC,
pub contents : Vec<u8> pub contents: Vec<u8>,
} }
/// A helper that will accept a LIST chunk as a [u8] /// A helper that will accept a LIST chunk as a [u8]
/// and give you back each segment /// and give you back each segment
/// ///
pub fn collect_list_form(list_contents :& [u8]) -> Result<Vec<ListFormItem>, Error> { pub fn collect_list_form(list_contents: &[u8]) -> Result<Vec<ListFormItem>, Error> {
let mut cursor = Cursor::new(list_contents); let mut cursor = Cursor::new(list_contents);
let mut remain = list_contents.len(); let mut remain = list_contents.len();
let _ = cursor.read_fourcc()?; // skip signature let _ = cursor.read_fourcc()?; // skip signature
remain -= 4; remain -= 4;
let mut retval : Vec<ListFormItem> = vec![]; let mut retval: Vec<ListFormItem> = vec![];
while remain > 0 { while remain > 0 {
let this_sig = cursor.read_fourcc()?; let this_sig = cursor.read_fourcc()?;
@@ -25,16 +25,18 @@ pub fn collect_list_form(list_contents :& [u8]) -> Result<Vec<ListFormItem>, Err
let mut content_buf = vec![0u8; this_size]; let mut content_buf = vec![0u8; this_size];
cursor.read_exact(&mut content_buf)?; cursor.read_exact(&mut content_buf)?;
remain -= this_size; 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 { if this_size % 2 == 1 {
cursor.read_u8()?; cursor.read_u8()?;
//panic!("Got this far!"); //panic!("Got this far!");
remain -= 1; remain -= 1;
} }
} }
Ok( retval ) Ok(retval)
} }

View File

@@ -1,15 +1,14 @@
use std::collections::HashMap;
use std::io; use std::io;
use std::io::SeekFrom::{Current, Start}; use std::io::SeekFrom::{Current, Start};
use std::io::{Seek, Read}; use std::io::{Read, Seek};
use std::collections::HashMap;
use byteorder::LittleEndian; use byteorder::LittleEndian;
use byteorder::ReadBytesExt; use byteorder::ReadBytesExt;
use super::errors::Error; use super::errors::Error;
use super::fourcc::{FourCC, ReadFourCC}; 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... // 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 // 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)] #[derive(Debug)]
pub enum Event { pub enum Event {
StartParse, StartParse,
ReadHeader { signature: FourCC, length_field: u32 }, ReadHeader {
ReadRF64Header { signature: FourCC }, signature: FourCC,
ReadDS64 {file_size: u64, long_sizes: HashMap<FourCC,u64> }, length_field: u32,
BeginChunk { signature: FourCC, content_start: u64, content_length: u64 }, },
Failed { error: Error }, ReadRF64Header {
FinishParse signature: FourCC,
},
ReadDS64 {
file_size: u64,
long_sizes: HashMap<FourCC, u64>,
},
BeginChunk {
signature: FourCC,
content_start: u64,
content_length: u64,
},
Failed {
error: Error,
},
FinishParse,
} }
#[derive(Debug)] #[derive(Debug)]
@@ -36,67 +49,80 @@ enum State {
ReadyForDS64, ReadyForDS64,
ReadyForChunk { at: u64, remaining: u64 }, ReadyForChunk { at: u64, remaining: u64 },
Error, Error,
Complete Complete,
} }
pub struct Parser<R: Read + Seek> { pub struct Parser<R: Read + Seek> {
stream: R, stream: R,
state: State, state: State,
ds64state: HashMap<FourCC,u64> ds64state: HashMap<FourCC, u64>,
} }
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub struct ChunkIteratorItem { pub struct ChunkIteratorItem {
pub signature: FourCC, pub signature: FourCC,
pub start: u64, pub start: u64,
pub length: u64 pub length: u64,
} }
impl<R: Read + Seek> Parser<R> { impl<R: Read + Seek> Parser<R> {
// wraps a stream // wraps a stream
pub fn make(stream: R) -> Result<Self, Error> { pub fn make(stream: R) -> Result<Self, Error> {
let newmap: HashMap<FourCC, u64> = HashMap::new(); let newmap: HashMap<FourCC, u64> = HashMap::new();
let mut the_stream = stream; let mut the_stream = stream;
the_stream.seek(Start(0))?; the_stream.seek(Start(0))?;
return Ok(Parser { return Ok(Parser {
stream: the_stream, stream: the_stream,
state: State::New, state: State::New,
ds64state: newmap, ds64state: newmap,
}) });
} }
// pub fn into_inner(self) -> R { // pub fn into_inner(self) -> R {
// self.stream // self.stream
// } // }
pub fn into_chunk_iterator(self) -> impl Iterator<Item = Result<ChunkIteratorItem, Error>>{ pub fn into_chunk_iterator(self) -> impl Iterator<Item = Result<ChunkIteratorItem, Error>> {
self.filter_map({|event| self.filter_map({
if let Event::BeginChunk {signature , content_start, content_length } = event { |event| {
Some(Ok(ChunkIteratorItem {signature, start: content_start, length: content_length })) if let Event::BeginChunk {
} else if let Event::Failed { error } = event { signature,
Some(Err(error)) content_start,
} else { content_length,
None } = 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<Vec<ChunkIteratorItem>,Error> { pub fn into_chunk_list(self) -> Result<Vec<ChunkIteratorItem>, Error> {
let mut error = Ok(()); let mut error = Ok(());
let chunks = self.into_chunk_iterator() let chunks = self
.into_chunk_iterator()
.scan(&mut error, |err, res| match res { .scan(&mut error, |err, res| match res {
Ok(ok) => Some(ok), Ok(ok) => Some(ok),
Err(e) => { **err = Err(e); None } Err(e) => {
**err = Err(e);
None
}
}) })
.collect(); .collect();
error?; error?;
Ok( chunks ) Ok(chunks)
} }
} }
impl<R: Read + Seek> Iterator for Parser<R> { impl<R: Read + Seek> Iterator for Parser<R> {
@@ -110,55 +136,53 @@ impl<R: Read + Seek> Iterator for Parser<R> {
} }
impl<R: Read + Seek> Parser<R> { impl<R: Read + Seek> Parser<R> {
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 file_sig = self.stream.read_fourcc()?;
let length = self.stream.read_u32::<LittleEndian>()?; let length = self.stream.read_u32::<LittleEndian>()?;
let list_sig = self.stream.read_fourcc()?; let list_sig = self.stream.read_fourcc()?;
let event : Event; let event: Event;
let next_state: State; let next_state: State;
match (file_sig, length, list_sig) { match (file_sig, length, list_sig) {
(RIFF_SIG, size, WAVE_SIG) => { (RIFF_SIG, size, WAVE_SIG) => {
event = Event::ReadHeader { event = Event::ReadHeader {
signature: file_sig, signature: file_sig,
length_field: size length_field: size,
}; };
next_state = State::ReadyForChunk { next_state = State::ReadyForChunk {
at: 12, at: 12,
remaining: (length - 4) as u64, remaining: (length - 4) as u64,
}; };
}, }
(RF64_SIG, RF64_SIZE_MARKER, WAVE_SIG) | (BW64_SIG, RF64_SIZE_MARKER, WAVE_SIG) => { (RF64_SIG, RF64_SIZE_MARKER, WAVE_SIG) | (BW64_SIG, RF64_SIZE_MARKER, WAVE_SIG) => {
event = Event::ReadRF64Header { event = Event::ReadRF64Header {
signature: file_sig signature: file_sig,
}; };
next_state = State::ReadyForDS64; next_state = State::ReadyForDS64;
}, }
_ => { _ => {
event = Event::Failed { event = Event::Failed {
error: Error::HeaderNotRecognized error: Error::HeaderNotRecognized,
}; };
next_state = State::Error; next_state = State::Error;
} }
} }
return Ok( (event, next_state) ); return Ok((event, next_state));
} }
fn parse_ds64(&mut self) -> Result<(Event, State), Error> { 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_sig = self.stream.read_fourcc()?;
let ds64_size = self.stream.read_u32::<LittleEndian>()? as u64; let ds64_size = self.stream.read_u32::<LittleEndian>()? as u64;
let mut read :u64 = 0; let mut read: u64 = 0;
if ds64_sig != DS64_SIG { if ds64_sig != DS64_SIG {
return Err(Error::MissingRequiredDS64); return Err(Error::MissingRequiredDS64);
} else { } else {
let long_file_size = self.stream.read_u64::<LittleEndian>()?; let long_file_size = self.stream.read_u64::<LittleEndian>()?;
let long_data_size = self.stream.read_u64::<LittleEndian>()?; let long_data_size = self.stream.read_u64::<LittleEndian>()?;
@@ -176,10 +200,10 @@ impl<R: Read + Seek> Parser<R> {
} }
self.ds64state.insert(DATA_SIG, long_data_size); self.ds64state.insert(DATA_SIG, long_data_size);
if read < ds64_size { if read < ds64_size {
/* for some reason the ds64 chunk returned by Pro Tools is longer than /* 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... For the record libsndfile seems to do the same thing...
https://github.com/libsndfile/libsndfile/blob/08d802a3d18fa19c74f38ed910d9e33f80248187/src/rf64.c#L230 https://github.com/libsndfile/libsndfile/blob/08d802a3d18fa19c74f38ed910d9e33f80248187/src/rf64.c#L230
@@ -189,7 +213,7 @@ impl<R: Read + Seek> Parser<R> {
let event = Event::ReadDS64 { let event = Event::ReadDS64 {
file_size: long_file_size, file_size: long_file_size,
long_sizes : self.ds64state.clone(), long_sizes: self.ds64state.clone(),
}; };
let state = State::ReadyForChunk { let state = State::ReadyForChunk {
@@ -197,19 +221,17 @@ impl<R: Read + Seek> Parser<R> {
remaining: long_file_size - (4 + 8 + ds64_size), 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 event;
let state; let state;
if remaining == 0 { if remaining == 0 {
event = Event::FinishParse; event = Event::FinishParse;
state = State::Complete; state = State::Complete;
} else { } else {
let this_fourcc = self.stream.read_fourcc()?; let this_fourcc = self.stream.read_fourcc()?;
let this_size: u64; let this_size: u64;
@@ -221,59 +243,67 @@ impl<R: Read + Seek> Parser<R> {
this_size = self.stream.read_u32::<LittleEndian>()? as u64; this_size = self.stream.read_u32::<LittleEndian>()? 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))?; self.stream.seek(Current(this_displacement as i64))?;
event = Event::BeginChunk { event = Event::BeginChunk {
signature: this_fourcc, signature: this_fourcc,
content_start: at + 8, content_start: at + 8,
content_length: this_size content_length: this_size,
}; };
state = State::ReadyForChunk { state = State::ReadyForChunk {
at: at + 8 + this_displacement, 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<Event>, State), Error> { fn handle_state(&mut self) -> Result<(Option<Event>, State), Error> {
match self.state { match self.state {
State::New => { State::New => {
return Ok( ( Some(Event::StartParse) , State::ReadyForHeader) ); return Ok((Some(Event::StartParse), State::ReadyForHeader));
}, }
State::ReadyForHeader => { State::ReadyForHeader => {
let (event, state) = self.parse_header()?; let (event, state) = self.parse_header()?;
return Ok( ( Some(event), state ) ); return Ok((Some(event), state));
}, }
State::ReadyForDS64 => { State::ReadyForDS64 => {
let (event, state) = self.parse_ds64()?; let (event, state) = self.parse_ds64()?;
return Ok( ( Some(event), state ) ); return Ok((Some(event), state));
}, }
State::ReadyForChunk { at, remaining } => { State::ReadyForChunk { at, remaining } => {
let (event, state) = self.enter_chunk(at, remaining)?; let (event, state) = self.enter_chunk(at, remaining)?;
return Ok( ( Some(event), state ) ); return Ok((Some(event), state));
}, }
State::Error => { State::Error => {
return Ok( ( Some(Event::FinishParse) , State::Complete ) ); return Ok((Some(Event::FinishParse), State::Complete));
}, }
State::Complete => { State::Complete => {
return Ok( ( None, State::Complete ) ); return Ok((None, State::Complete));
} }
} }
} }
fn advance(&mut self) -> (Option<Event>, State) { fn advance(&mut self) -> (Option<Event>, State) {
match self.handle_state() { match self.handle_state() {
Ok(( event , state) ) => { Ok((event, state)) => {
return (event, state); return (event, state);
}, }
Err(error) => { Err(error) => {
return (Some(Event::Failed { error: error.into() } ), State::Error ); return (
Some(Event::Failed {
error: error.into(),
}),
State::Error,
);
} }
} }
} }
} }

View File

@@ -1,62 +1,72 @@
use std::fs::File; use std::fs::File;
use std::path::Path; use std::path::Path;
use std::io::SeekFrom;
use std::io::Cursor; use std::io::Cursor;
use std::io::{Read, Seek, BufReader}; use std::io::SeekFrom;
use std::io::SeekFrom::{Start,Current,}; 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::bext::Bext;
use super::chunks::ReadBWaveChunks; use super::chunks::ReadBWaveChunks;
use super::cue::Cue; use super::cue::Cue;
use super::errors::Error as ParserError;
use super::errors::Error; 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 super::CommonFormat;
use byteorder::LittleEndian; use byteorder::LittleEndian;
use byteorder::ReadBytesExt; use byteorder::ReadBytesExt;
/// Read audio frames /// Read audio frames
/// ///
/// The inner reader is interpreted as a raw audio data /// The inner reader is interpreted as a raw audio data
/// bitstream having a format specified by `format`. /// bitstream having a format specified by `format`.
/// ///
#[derive(Debug)] #[derive(Debug)]
pub struct AudioFrameReader<R: Read + Seek> { pub struct AudioFrameReader<R: Read + Seek> {
inner : R, inner: R,
format: WaveFmt, format: WaveFmt,
start: u64, start: u64,
length: u64 length: u64,
} }
impl<R: Read + Seek> AudioFrameReader<R> { impl<R: Read + Seek> AudioFrameReader<R> {
/// Create a new `AudioFrameReader` /// Create a new `AudioFrameReader`
/// ///
/// ### Panics /// ### Panics
/// ///
/// This method does a few sanity checks on the provided format /// This method does a few sanity checks on the provided format
/// parameter to confirm the `block_alignment` law is fulfilled /// parameter to confirm the `block_alignment` law is fulfilled
/// and the format tag is readable by this implementation (only /// 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<Self, Error> { pub fn new(mut inner: R, format: WaveFmt, start: u64, length: u64) -> Result<Self, Error> {
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 {}", "Unable to read audio frames from packed formats: block alignment is {}, should be {}",
format.block_alignment, (format.bits_per_sample / 8 ) * format.channel_count); 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);
assert!(
format.common_format() == CommonFormat::IntegerPCM
|| format.common_format() == CommonFormat::IeeeFloatPCM,
"Unsupported format tag {:?}",
format.tag
);
inner.seek(Start(start))?; inner.seek(Start(start))?;
Ok( AudioFrameReader { inner , format , start, length} ) Ok(AudioFrameReader {
inner,
format,
start,
length,
})
} }
/// Unwrap the inner reader. /// Unwrap the inner reader.
@@ -65,42 +75,44 @@ impl<R: Read + Seek> AudioFrameReader<R> {
} }
/// Locate the read position to a different frame /// Locate the read position to a different frame
/// ///
/// Seeks within the audio stream. /// Seeks within the audio stream.
/// ///
/// Returns the new location of the read position. /// Returns the new location of the read position.
/// ///
/// locate() behaves similarly to Read methods in that /// locate() behaves similarly to Read methods in that
/// seeking after the end of the audio data is not an error. /// seeking after the end of the audio data is not an error.
pub fn locate(&mut self, to :u64) -> Result<u64,Error> { pub fn locate(&mut self, to: u64) -> Result<u64, Error> {
let position = to * self.format.block_alignment as u64; let position = to * self.format.block_alignment as u64;
let seek_result = self.inner.seek(Start(self.start + position))?; 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 /// Read a frame
/// ///
/// A single frame is read from the audio stream and the read location /// A single frame is read from the audio stream and the read location
/// is advanced one frame. /// is advanced one frame.
/// ///
/// Regardless of the number of bits in the audio sample, this method /// 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 /// written back "right-aligned" so samples that are shorter than i32
/// will leave the MSB bits empty. /// 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. /// back to the buffer as 0x0000FFFF.
/// ///
/// ///
/// ### Panics /// ### 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. /// channels and this method will panic if this is not the case.
pub fn read_integer_frame(&mut self, buffer:&mut [i32]) -> Result<u64,Error> { pub fn read_integer_frame(&mut self, buffer: &mut [i32]) -> Result<u64, Error> {
assert!(buffer.len() as u16 == self.format.channel_count, assert!(
"read_integer_frame was called with a mis-sized buffer, expected {}, was {}", buffer.len() as u16 == self.format.channel_count,
self.format.channel_count, buffer.len()); "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; let framed_bits_per_sample = self.format.block_alignment * 8 / self.format.channel_count;
@@ -117,16 +129,19 @@ impl<R: Read + Seek> AudioFrameReader<R> {
b, self.format.channel_count, self.format.block_alignment) b, self.format.channel_count, self.format.block_alignment)
} }
} }
Ok( 1 ) Ok(1)
} else { } else {
Ok( 0 ) Ok(0)
} }
} }
pub fn read_float_frame(&mut self, buffer: &mut [f32]) -> Result<u64, Error> { pub fn read_float_frame(&mut self, buffer: &mut [f32]) -> Result<u64, Error> {
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 {}", "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; let framed_bits_per_sample = self.format.block_alignment * 8 / self.format.channel_count;
@@ -140,9 +155,9 @@ impl<R: Read + Seek> AudioFrameReader<R> {
b, self.format.channel_count, self.format.block_alignment) b, self.format.channel_count, self.format.block_alignment)
} }
} }
Ok( 1 ) Ok(1)
} else { } else {
Ok( 0 ) Ok(0)
} }
} }
} }
@@ -150,7 +165,7 @@ impl<R: Read + Seek> AudioFrameReader<R> {
/// Wave, Broadcast-WAV and RF64/BW64 parser/reader. /// 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 mut r = WaveReader::open("tests/media/ff_silence.wav").unwrap();
/// ///
/// let format = r.format().unwrap(); /// let format = r.format().unwrap();
@@ -161,17 +176,17 @@ impl<R: Read + Seek> AudioFrameReader<R> {
/// let mut buffer = format.create_frame_buffer(1); /// let mut buffer = format.create_frame_buffer(1);
/// ///
/// let read = frame_reader.read_integer_frame(&mut buffer).unwrap(); /// let read = frame_reader.read_integer_frame(&mut buffer).unwrap();
/// ///
/// assert_eq!(buffer, [0i32]); /// assert_eq!(buffer, [0i32]);
/// assert_eq!(read, 1); /// assert_eq!(read, 1);
/// ///
/// ``` /// ```
/// ///
/// ## Resources /// ## Resources
/// ///
/// ### Implementation of Wave Files /// ### Implementation of Wave Files
/// - [Peter Kabal, McGill University](http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/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 /// (August 1991), IBM Corporation and Microsoft Corporation
/// ///
/// ### Implementation of Broadcast Wave Files /// ### Implementation of Broadcast Wave Files
@@ -184,14 +199,13 @@ impl<R: Read + Seek> AudioFrameReader<R> {
/// - Presently in force, adopted by the EBU in [EBU Tech 3306v2][ebu3306v2] (June 2018). /// - 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" /// - [EBU Tech 3306v1][ebu3306v1] (July 2009), "MBWF / RF64: An extended File Format for Audio"
/// - No longer in force, however long-established. /// - No longer in force, however long-established.
/// ///
/// ///
/// [ebu3285]: https://tech.ebu.ch/docs/tech/tech3285.pdf /// [ebu3285]: https://tech.ebu.ch/docs/tech/tech3285.pdf
/// [ebu3306v1]: https://tech.ebu.ch/docs/tech/tech3306v1_1.pdf /// [ebu3306v1]: https://tech.ebu.ch/docs/tech/tech3306v1_1.pdf
/// [ebu3306v2]: https://tech.ebu.ch/docs/tech/tech3306.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 /// [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)] #[derive(Debug)]
pub struct WaveReader<R: Read + Seek> { pub struct WaveReader<R: Read + Seek> {
@@ -199,40 +213,37 @@ pub struct WaveReader<R: Read + Seek> {
} }
impl WaveReader<BufReader<File>> { impl WaveReader<BufReader<File>> {
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self, ParserError> { pub fn open<P: AsRef<Path>>(path: P) -> Result<Self, ParserError> {
let f = File::open(path)?; let f = File::open(path)?;
let inner = BufReader::new(f); let inner = BufReader::new(f);
Ok( Self::new(inner)? ) Ok(Self::new(inner)?)
} }
} }
impl WaveReader<File> { impl WaveReader<File> {
/// Open a file for reading with unbuffered IO.
/// Open a file for reading with unbuffered IO. ///
/// /// A convenience that opens `path` and calls `Self::new()`
/// A convenience that opens `path` and calls `Self::new()`
pub fn open_unbuffered<P: AsRef<Path>>(path: P) -> Result<Self, ParserError> { pub fn open_unbuffered<P: AsRef<Path>>(path: P) -> Result<Self, ParserError> {
let inner = File::open(path)?; let inner = File::open(path)?;
return Ok( Self::new(inner)? ) return Ok(Self::new(inner)?);
} }
} }
impl<R: Read + Seek> WaveReader<R> { impl<R: Read + Seek> WaveReader<R> {
/// Wrap a `Read` struct in a new `WaveReader`. /// Wrap a `Read` struct in a new `WaveReader`.
/// ///
/// This is the primary entry point into the `WaveReader` interface. The /// 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 /// 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. /// file.
/// ///
/// This function does a minimal validation on the provided stream and /// This function does a minimal validation on the provided stream and
/// will return an `Err(errors::Error)` immediately if there is a structural /// will return an `Err(errors::Error)` immediately if there is a structural
/// inconsistency that makes the stream unreadable or if it's missing /// inconsistency that makes the stream unreadable or if it's missing
/// essential components that make interpreting the audio data impossible. /// essential components that make interpreting the audio data impossible.
/// ```rust /// ```rust
/// use std::fs::File; /// use std::fs::File;
/// use std::io::{Error,ErrorKind}; /// use std::io::{Error,ErrorKind};
@@ -249,15 +260,14 @@ impl<R: Read + Seek> WaveReader<R> {
/// } /// }
/// Err(e) => panic!("Unexpected error was returned {:?}", e) /// Err(e) => panic!("Unexpected error was returned {:?}", e)
/// } /// }
/// ///
/// ``` /// ```
pub fn new(inner: R) -> Result<Self,ParserError> { pub fn new(inner: R) -> Result<Self, ParserError> {
let mut retval = Self { inner }; let mut retval = Self { inner };
retval.validate_readable()?; retval.validate_readable()?;
Ok(retval) Ok(retval)
} }
/// Unwrap the inner reader. /// Unwrap the inner reader.
pub fn into_inner(self) -> R { pub fn into_inner(self) -> R {
return self.inner; return self.inner;
@@ -269,18 +279,21 @@ impl<R: Read + Seek> WaveReader<R> {
pub fn audio_frame_reader(mut self) -> Result<AudioFrameReader<R>, ParserError> { pub fn audio_frame_reader(mut self) -> Result<AudioFrameReader<R>, ParserError> {
let format = self.format()?; let format = self.format()?;
let audio_chunk_reader = self.get_chunk_extent_at_index(DATA_SIG, 0)?; 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. /// The count of audio frames in the file.
pub fn frame_length(&mut self) -> Result<u64, ParserError> { pub fn frame_length(&mut self) -> Result<u64, ParserError> {
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()?; 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. /// Sample and frame format of this wave file.
/// ///
pub fn format(&mut self) -> Result<WaveFmt, ParserError> { pub fn format(&mut self) -> Result<WaveFmt, ParserError> {
@@ -290,29 +303,28 @@ impl<R: Read + Seek> WaveReader<R> {
} }
/// The Broadcast-WAV metadata record for this file, if present. /// The Broadcast-WAV metadata record for this file, if present.
/// ///
pub fn broadcast_extension(&mut self) -> Result<Option<Bext>, ParserError> { pub fn broadcast_extension(&mut self) -> Result<Option<Bext>, ParserError> {
let mut bext_buff : Vec<u8> = vec![ ]; let mut bext_buff: Vec<u8> = vec![];
let result = self.read_chunk(BEXT_SIG, 0, &mut bext_buff)?; let result = self.read_chunk(BEXT_SIG, 0, &mut bext_buff)?;
if result > 0 { if result > 0 {
let mut bext_cursor = Cursor::new(bext_buff); let mut bext_cursor = Cursor::new(bext_buff);
Ok( Some( bext_cursor.read_bext()? ) ) Ok(Some(bext_cursor.read_bext()?))
} else { } else {
Ok( None) Ok(None)
} }
} }
/// Describe the channels in this file /// Describe the channels in this file
/// ///
/// Returns a vector of channel descriptors, one for each channel /// Returns a vector of channel descriptors, one for each channel
/// ///
/// ```rust /// ```rust
/// use bwavfile::WaveReader; /// use bwavfile::WaveReader;
/// use bwavfile::ChannelMask; /// use bwavfile::ChannelMask;
/// ///
/// let mut f = WaveReader::open("tests/media/pt_24bit_51.wav").unwrap(); /// let mut f = WaveReader::open("tests/media/pt_24bit_51.wav").unwrap();
/// ///
/// let chans = f.channels().unwrap(); /// let chans = f.channels().unwrap();
/// assert_eq!(chans[0].index, 0); /// assert_eq!(chans[0].index, 0);
/// assert_eq!(chans[0].speaker, ChannelMask::FrontLeft); /// assert_eq!(chans[0].speaker, ChannelMask::FrontLeft);
@@ -321,97 +333,100 @@ impl<R: Read + Seek> WaveReader<R> {
/// assert_eq!(chans[4].speaker, ChannelMask::BackLeft); /// assert_eq!(chans[4].speaker, ChannelMask::BackLeft);
/// ``` /// ```
pub fn channels(&mut self) -> Result<Vec<ChannelDescriptor>, ParserError> { pub fn channels(&mut self) -> Result<Vec<ChannelDescriptor>, ParserError> {
let format = self.format()?; let format = self.format()?;
let channel_masks : Vec<ChannelMask> = match (format.channel_count, format.extended_format) { let channel_masks: Vec<ChannelMask> = match (format.channel_count, format.extended_format) {
(1,_) => vec![ChannelMask::FrontCenter], (1, _) => vec![ChannelMask::FrontCenter],
(2,_) => vec![ChannelMask::FrontLeft, ChannelMask::FrontRight], (2, _) => vec![ChannelMask::FrontLeft, ChannelMask::FrontRight],
(n,Some(x)) => ChannelMask::channels(x.channel_mask, n), (n, Some(x)) => ChannelMask::channels(x.channel_mask, n),
(n,_) => vec![ChannelMask::DirectOut; n as usize] (n, _) => vec![ChannelMask::DirectOut; n as usize],
}; };
Ok( (0..format.channel_count).zip(channel_masks) Ok((0..format.channel_count)
.map(|(i,m)| ChannelDescriptor { index: i, speaker:m, adm_track_audio_ids: vec![] } ) .zip(channel_masks)
.collect() ) .map(|(i, m)| ChannelDescriptor {
index: i,
speaker: m,
adm_track_audio_ids: vec![],
})
.collect())
} }
/// Read cue points. /// Read cue points.
/// ///
/// ```rust /// ```rust
/// use bwavfile::WaveReader; /// use bwavfile::WaveReader;
/// use bwavfile::Cue; /// use bwavfile::Cue;
/// ///
/// let mut f = WaveReader::open("tests/media/izotope_test.wav").unwrap(); /// let mut f = WaveReader::open("tests/media/izotope_test.wav").unwrap();
/// let cue_points = f.cue_points().unwrap(); /// let cue_points = f.cue_points().unwrap();
/// ///
/// assert_eq!(cue_points.len(), 3); /// assert_eq!(cue_points.len(), 3);
/// assert_eq!(cue_points[0].frame, 12532); /// assert_eq!(cue_points[0].frame, 12532);
/// assert_eq!(cue_points[0].length, None); /// assert_eq!(cue_points[0].length, None);
/// assert_eq!(cue_points[0].label, Some(String::from("Marker 1"))); /// 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[0].note, Some(String::from("Marker 1 Comment")));
/// ///
/// assert_eq!(cue_points[1].frame, 20997); /// assert_eq!(cue_points[1].frame, 20997);
/// assert_eq!(cue_points[1].length, None); /// assert_eq!(cue_points[1].length, None);
/// assert_eq!(cue_points[1].label, Some(String::from("Marker 2"))); /// 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].frame, 26711);
/// assert_eq!(cue_points[2].length, Some(6465)); /// assert_eq!(cue_points[2].length, Some(6465));
/// assert_eq!(cue_points[2].label, Some(String::from("Timed Region"))); /// 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<Vec<Cue>,ParserError> { pub fn cue_points(&mut self) -> Result<Vec<Cue>, ParserError> {
let mut cue_buffer : Vec<u8> = vec![]; let mut cue_buffer: Vec<u8> = vec![];
let mut adtl_buffer : Vec<u8> = vec![]; let mut adtl_buffer: Vec<u8> = vec![];
let cue_read = self.read_chunk(CUE__SIG, 0, &mut cue_buffer)?; let cue_read = self.read_chunk(CUE__SIG, 0, &mut cue_buffer)?;
let adtl_read = self.read_list(ADTL_SIG, &mut adtl_buffer)?; let adtl_read = self.read_list(ADTL_SIG, &mut adtl_buffer)?;
match (cue_read, adtl_read) { match (cue_read, adtl_read) {
(0,_) => Ok( vec![] ), (0, _) => Ok(vec![]),
(_,0) => Ok( Cue::collect_from(&cue_buffer, None)? ), (_, 0) => Ok(Cue::collect_from(&cue_buffer, None)?),
(_,_) => Ok( Cue::collect_from(&cue_buffer, Some(&adtl_buffer) )? ) (_, _) => Ok(Cue::collect_from(&cue_buffer, Some(&adtl_buffer))?),
} }
} }
/// Read iXML data. /// Read iXML data.
/// ///
/// The iXML data will be appended to `buffer`. /// 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. /// Ok(0) will be returned.
pub fn read_ixml(&mut self, buffer: &mut Vec<u8>) -> Result<usize, ParserError> { pub fn read_ixml(&mut self, buffer: &mut Vec<u8>) -> Result<usize, ParserError> {
self.read_chunk(IXML_SIG, 0, buffer) self.read_chunk(IXML_SIG, 0, buffer)
} }
/// Read AXML data. /// 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. /// 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 /// Ok(0) will be returned
pub fn read_axml(&mut self, buffer: &mut Vec<u8>) -> Result<usize, ParserError> { pub fn read_axml(&mut self, buffer: &mut Vec<u8>) -> Result<usize, ParserError> {
self.read_chunk(AXML_SIG, 0, buffer) self.read_chunk(AXML_SIG, 0, buffer)
} }
/** /**
* Validate file is readable. * Validate file is readable.
* *
* `Ok(())` if the source meets the minimum standard of * `Ok(())` if the source meets the minimum standard of
* readability by a permissive client: * readability by a permissive client:
* - `fmt` chunk and `data` chunk are present * - `fmt` chunk and `data` chunk are present
* - `fmt` chunk appears before `data` chunk * - `fmt` chunk appears before `data` chunk
*/ */
pub fn validate_readable(&mut self) -> Result<(), ParserError> { 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)?; let (data_pos, _) = self.get_chunk_extent_at_index(DATA_SIG, 0)?;
if fmt_pos < data_pos { if fmt_pos < data_pos {
Ok(()) Ok(())
} else { } else {
Err( ParserError::FmtChunkAfterData) Err(ParserError::FmtChunkAfterData)
} }
} }
@@ -442,35 +457,38 @@ impl<R: Read + Seek> WaveReader<R> {
/// let mut x = WaveReader::open("tests/media/pt_24bit_51.wav").unwrap(); /// let mut x = WaveReader::open("tests/media/pt_24bit_51.wav").unwrap();
/// x.validate_minimal().expect_err("Complex WAV validated minimal!"); /// 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()?; self.validate_readable()?;
let chunk_fourccs : Vec<FourCC> = Parser::make(&mut self.inner)? let chunk_fourccs: Vec<FourCC> = Parser::make(&mut self.inner)?
.into_chunk_list()?.iter().map(|c| c.signature ).collect(); .into_chunk_list()?
.iter()
.map(|c| c.signature)
.collect();
if chunk_fourccs == vec![FMT__SIG, DATA_SIG] { if chunk_fourccs == vec![FMT__SIG, DATA_SIG] {
Ok(()) /* FIXME: finish implementation */ Ok(()) /* FIXME: finish implementation */
} else { } else {
Err( ParserError::NotMinimalWaveFile ) Err(ParserError::NotMinimalWaveFile)
} }
} }
/// Validate Broadcast-WAVE file format /// 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). /// Broadcast-WAV metadata record (a `bext` chunk).
/// ///
/// ### Examples /// ### Examples
/// ///
/// ``` /// ```
/// # use bwavfile::WaveReader; /// # use bwavfile::WaveReader;
/// ///
/// let mut w = WaveReader::open("tests/media/ff_bwav_stereo.wav").unwrap(); /// let mut w = WaveReader::open("tests/media/ff_bwav_stereo.wav").unwrap();
/// w.validate_broadcast_wave().expect("BWAVE file did not validate BWAVE"); /// w.validate_broadcast_wave().expect("BWAVE file did not validate BWAVE");
/// ///
/// let mut x = WaveReader::open("tests/media/pt_24bit.wav").unwrap(); /// let mut x = WaveReader::open("tests/media/pt_24bit.wav").unwrap();
/// x.validate_broadcast_wave().expect("BWAVE file did not validate BWAVE"); /// x.validate_broadcast_wave().expect("BWAVE file did not validate BWAVE");
/// ///
/// let mut y = WaveReader::open("tests/media/audacity_16bit.wav").unwrap(); /// let mut y = WaveReader::open("tests/media/audacity_16bit.wav").unwrap();
/// y.validate_broadcast_wave().expect_err("Plain WAV file DID validate BWAVE"); /// y.validate_broadcast_wave().expect_err("Plain WAV file DID validate BWAVE");
/// ``` /// ```
@@ -479,14 +497,14 @@ impl<R: Read + Seek> WaveReader<R> {
self.validate_readable()?; self.validate_readable()?;
let (_, _) = self.get_chunk_extent_at_index(BEXT_SIG, 0)?; let (_, _) = self.get_chunk_extent_at_index(BEXT_SIG, 0)?;
Ok(()) Ok(())
} }
/// ///
/// Verify data is aligned to a block boundary. /// 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. /// `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()?; self.validate_readable()?;
let (start, _) = self.get_chunk_extent_at_index(DATA_SIG, 0)?; let (start, _) = self.get_chunk_extent_at_index(DATA_SIG, 0)?;
if start == 0x4000 { if start == 0x4000 {
@@ -497,10 +515,10 @@ impl<R: Read + Seek> WaveReader<R> {
} }
/// Verify audio data can be appended immediately to this file. /// Verify audio data can be appended immediately to this file.
/// ///
/// Returns `Ok(())` if: /// Returns `Ok(())` if:
/// - `validate_readable()` /// - `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) /// list adequately large enough to be overwritten by a `ds64` (92 bytes)
/// - `data` is the final chunk /// - `data` is the final chunk
pub fn validate_prepared_for_append(&mut self) -> Result<(), ParserError> { pub fn validate_prepared_for_append(&mut self) -> Result<(), ParserError> {
@@ -509,66 +527,82 @@ impl<R: Read + Seek> WaveReader<R> {
let chunks = Parser::make(&mut self.inner)?.into_chunk_list()?; let chunks = Parser::make(&mut self.inner)?.into_chunk_list()?;
let ds64_space_required = 92; 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); .take_while(|c| c.signature == JUNK_SIG || c.signature == FLLR_SIG);
let filler = eligible_filler_chunks let filler = eligible_filler_chunks
.enumerate() .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 { if filler < ds64_space_required {
Err(ParserError::InsufficientDS64Reservation {expected: ds64_space_required, actual: filler}) Err(ParserError::InsufficientDS64Reservation {
expected: ds64_space_required,
actual: filler,
})
} else { } else {
let data_pos = chunks.iter().position(|c| c.signature == DATA_SIG); let data_pos = chunks.iter().position(|c| c.signature == DATA_SIG);
match data_pos { match data_pos {
Some(p) if p == chunks.len() - 1 => Ok(()), Some(p) if p == chunks.len() - 1 => Ok(()),
_ => Err(ParserError::DataChunkNotPreparedForAppend) _ => Err(ParserError::DataChunkNotPreparedForAppend),
} }
} }
} }
} }
impl<R:Read+Seek> WaveReader<R> { impl<R: Read + Seek> WaveReader<R> {
// Private implementation // 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 // parsing in the raw parser and I'm working around it
// fn chunk_reader(&mut self, signature: FourCC, at_index: u32) -> Result<RawChunkReader<R>, ParserError> { // fn chunk_reader(&mut self, signature: FourCC, at_index: u32) -> Result<RawChunkReader<R>, ParserError> {
// let (start, length) = self.get_chunk_extent_at_index(signature, at_index)?; // let (start, length) = self.get_chunk_extent_at_index(signature, at_index)?;
// Ok( RawChunkReader::new(&mut self.inner, start, length) ) // Ok( RawChunkReader::new(&mut self.inner, start, length) )
// } // }
fn read_list(&mut self, ident: FourCC, buffer: &mut Vec<u8>) -> Result<usize, ParserError> { fn read_list(&mut self, ident: FourCC, buffer: &mut Vec<u8>) -> Result<usize, ParserError> {
if let Some(index) = self.get_list_form(ident)? { if let Some(index) = self.get_list_form(ident)? {
self.read_chunk(LIST_SIG, index, buffer) self.read_chunk(LIST_SIG, index, buffer)
} else { } else {
Ok( 0 ) Ok(0)
} }
} }
fn read_chunk(
fn read_chunk(&mut self, ident: FourCC, at: u32, mut buffer: &mut Vec<u8>) -> Result<usize, ParserError> { &mut self,
ident: FourCC,
at: u32,
mut buffer: &mut Vec<u8>,
) -> Result<usize, ParserError> {
match self.get_chunk_extent_at_index(ident, at) { match self.get_chunk_extent_at_index(ident, at) {
Ok((start, length)) => { Ok((start, length)) => {
buffer.resize(length as usize, 0x0); buffer.resize(length as usize, 0x0);
self.inner.seek(SeekFrom::Start(start))?; self.inner.seek(SeekFrom::Start(start))?;
self.inner.read(&mut buffer).map_err(|e| ParserError::IOError(e)) self.inner
}, .read(&mut buffer)
Err(ParserError::ChunkMissing { signature : _} ) => Ok(0), .map_err(|e| ParserError::IOError(e))
Err( any ) => Err(any.into()) }
Err(ParserError::ChunkMissing { signature: _ }) => Ok(0),
Err(any) => Err(any.into()),
} }
} }
/// Extent of every chunk with the given fourcc /// Extent of every chunk with the given fourcc
fn get_chunks_extents(&mut self, fourcc: FourCC) -> Result<Vec<(u64,u64)>, ParserError> { fn get_chunks_extents(&mut self, fourcc: FourCC) -> Result<Vec<(u64, u64)>, ParserError> {
let p = Parser::make(&mut self.inner)?.into_chunk_list()?; let p = Parser::make(&mut self.inner)?.into_chunk_list()?;
Ok( p.iter().filter(|item| item.signature == fourcc) Ok(p.iter()
.map(|item| (item.start, item.length)).collect() ) .filter(|item| item.signature == fourcc)
.map(|item| (item.start, item.length))
.collect())
} }
/// Index of first LIST for with the given FORM fourcc /// Index of first LIST for with the given FORM fourcc
@@ -577,18 +611,22 @@ impl<R:Read+Seek> WaveReader<R> {
self.inner.seek(SeekFrom::Start(*start as u64))?; self.inner.seek(SeekFrom::Start(*start as u64))?;
let this_fourcc = self.inner.read_fourcc()?; let this_fourcc = self.inner.read_fourcc()?;
if this_fourcc == 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) { if let Some((start, length)) = self.get_chunks_extents(fourcc)?.iter().nth(index as usize) {
Ok ((*start, *length)) Ok((*start, *length))
} else { } else {
Err( ParserError::ChunkMissing { signature : fourcc } ) Err(ParserError::ChunkMissing { signature: fourcc })
} }
} }
} }
@@ -596,10 +634,9 @@ impl<R:Read+Seek> WaveReader<R> {
#[test] #[test]
fn test_list_form() { fn test_list_form() {
let mut f = WaveReader::open("tests/media/izotope_test.wav").unwrap(); let mut f = WaveReader::open("tests/media/izotope_test.wav").unwrap();
let mut buf : Vec<u8> = vec![]; let mut buf: Vec<u8> = vec![];
f.read_list(ADTL_SIG, &mut buf).unwrap(); f.read_list(ADTL_SIG, &mut buf).unwrap();
assert_ne!(buf.len(), 0); assert_ne!(buf.len(), 0);
} }

View File

@@ -1,47 +1,58 @@
use std::fs::File; use std::fs::File;
use std::io::{BufWriter, Cursor, Seek, SeekFrom, Write};
use std::path::Path; 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::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::common_format::CommonFormat;
use super::chunks::WriteBWaveChunks;
use super::bext::Bext; use super::bext::Bext;
use super::chunks::WriteBWaveChunks;
use byteorder::LittleEndian; use byteorder::LittleEndian;
use byteorder::WriteBytesExt; use byteorder::WriteBytesExt;
/// Write audio frames to a `WaveWriter`. /// Write audio frames to a `WaveWriter`.
/// ///
/// ///
pub struct AudioFrameWriter<W> where W: Write + Seek { pub struct AudioFrameWriter<W>
inner : WaveChunkWriter<W> where
W: Write + Seek,
{
inner: WaveChunkWriter<W>,
} }
impl<W> AudioFrameWriter<W> where W: Write + Seek { impl<W> AudioFrameWriter<W>
where
W: Write + Seek,
{
fn new(inner: WaveChunkWriter<W>) -> Self { fn new(inner: WaveChunkWriter<W>) -> Self {
AudioFrameWriter { inner } AudioFrameWriter { inner }
} }
fn write_integer_frames_to_buffer(&self, from_frames :&[i32], to_buffer : &mut [u8]) -> () { 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, assert!(
"frames buffer does not contain a number of samples % channel_count == 0"); 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); self.inner.inner.format.pack_frames(&from_frames, to_buffer);
() ()
} }
/// Write interleaved samples in `buffer` /// Write interleaved samples in `buffer`
/// ///
/// # Panics /// # Panics
/// ///
/// This function will panic if `buffer.len()` modulo the Wave file's channel count /// This function will panic if `buffer.len()` modulo the Wave file's channel count
/// is not zero. /// is not zero.
pub fn write_integer_frames(&mut self, buffer: &[i32]) -> Result<u64,Error> { pub fn write_integer_frames(&mut self, buffer: &[i32]) -> Result<u64, Error> {
let mut write_buffer = self.inner.inner.format let mut write_buffer = self
.inner
.inner
.format
.create_raw_buffer(buffer.len() / self.inner.inner.format.channel_count as usize); .create_raw_buffer(buffer.len() / self.inner.inner.format.channel_count as usize);
self.write_integer_frames_to_buffer(&buffer, &mut write_buffer); self.write_integer_frames_to_buffer(&buffer, &mut write_buffer);
@@ -52,7 +63,7 @@ impl<W> AudioFrameWriter<W> where W: Write + Seek {
} }
/// Finish writing audio frames and unwrap the inner `WaveWriter`. /// Finish writing audio frames and unwrap the inner `WaveWriter`.
/// ///
/// This method must be called when the client has finished writing audio /// This method must be called when the client has finished writing audio
/// data. This will finalize the audio data chunk. /// data. This will finalize the audio data chunk.
pub fn end(self) -> Result<WaveWriter<W>, Error> { pub fn end(self) -> Result<WaveWriter<W>, Error> {
@@ -61,29 +72,39 @@ impl<W> AudioFrameWriter<W> where W: Write + Seek {
} }
/// Write a wave data chunk. /// Write a wave data chunk.
/// ///
/// `WaveChunkWriter` implements `Write` and as bytes are written to it, /// `WaveChunkWriter` implements `Write` and as bytes are written to it,
/// ///
/// ### Important! /// ### Important!
/// ///
/// When you are done writing to a chunk you must call `end()` in order to /// When you are done writing to a chunk you must call `end()` in order to
/// finalize the chunk for storage. /// finalize the chunk for storage.
pub struct WaveChunkWriter<W> where W: Write + Seek { pub struct WaveChunkWriter<W>
ident : FourCC, where
inner : WaveWriter<W>, W: Write + Seek,
content_start_pos : u64, {
length : u64 ident: FourCC,
inner: WaveWriter<W>,
content_start_pos: u64,
length: u64,
} }
impl<W> WaveChunkWriter<W> where W: Write + Seek { impl<W> WaveChunkWriter<W>
where
fn begin(mut inner : WaveWriter<W>, ident : FourCC) -> Result<Self,Error> { W: Write + Seek,
let length : u64 = 0; {
fn begin(mut inner: WaveWriter<W>, ident: FourCC) -> Result<Self, Error> {
let length: u64 = 0;
inner.inner.write_fourcc(ident)?; inner.inner.write_fourcc(ident)?;
inner.inner.write_u32::<LittleEndian>(length as u32)?; inner.inner.write_u32::<LittleEndian>(length as u32)?;
inner.increment_form_length(8)?; inner.increment_form_length(8)?;
let content_start_pos = inner.inner.seek(SeekFrom::End(0))?; 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<WaveWriter<W>, Error> { fn end(mut self) -> Result<WaveWriter<W>, Error> {
@@ -92,75 +113,84 @@ impl<W> WaveChunkWriter<W> where W: Write + Seek {
self.inner.inner.write(&[0u8])?; self.inner.inner.write(&[0u8])?;
self.inner.increment_form_length(1)?; self.inner.increment_form_length(1)?;
} }
Ok( self.inner ) Ok(self.inner)
} }
fn increment_chunk_length(&mut self, amount: u64) -> Result<(), std::io::Error> { fn increment_chunk_length(&mut self, amount: u64) -> Result<(), std::io::Error> {
self.length = self.length + amount; self.length = self.length + amount;
if !self.inner.is_rf64 { if !self.inner.is_rf64 {
self.inner.inner.seek(SeekFrom::Start(self.content_start_pos - 4))?; self.inner
self.inner.inner.write_u32::<LittleEndian>(self.length as u32)?; .inner
.seek(SeekFrom::Start(self.content_start_pos - 4))?;
self.inner
.inner
.write_u32::<LittleEndian>(self.length as u32)?;
} else { } else {
if self.ident == DATA_SIG { if self.ident == DATA_SIG {
let data_chunk_64bit_field_offset = 8 + 4 + 8 + 8; let data_chunk_64bit_field_offset = 8 + 4 + 8 + 8;
self.inner.inner.seek(SeekFrom::Start(self.content_start_pos - 4))?; self.inner
self.inner.inner.write_u32::<LittleEndian>(0xFFFF_FFFF)?; .inner
// this only need to happen once, not every time we increment .seek(SeekFrom::Start(self.content_start_pos - 4))?;
self.inner.inner.write_u32::<LittleEndian>(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::<LittleEndian>(self.length)?; self.inner.inner.write_u64::<LittleEndian>(self.length)?;
} else { } else {
todo!("FIXME RF64 wave writing is not yet supported for chunks other than `data`") todo!("FIXME RF64 wave writing is not yet supported for chunks other than `data`")
} }
} }
Ok(()) Ok(())
} }
} }
impl<W> Write for WaveChunkWriter<W> where W: Write + Seek { impl<W> Write for WaveChunkWriter<W>
where
fn write(&mut self, buffer: &[u8]) -> Result<usize, std::io::Error> { W: Write + Seek,
{
fn write(&mut self, buffer: &[u8]) -> Result<usize, std::io::Error> {
self.inner.inner.seek(SeekFrom::End(0))?; self.inner.inner.seek(SeekFrom::End(0))?;
let written = self.inner.inner.write(buffer)?; let written = self.inner.inner.write(buffer)?;
self.inner.increment_form_length(written as u64)?; self.inner.increment_form_length(written as u64)?;
self.increment_chunk_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() self.inner.inner.flush()
} }
} }
/// Wave, Broadcast-WAV and RF64/BW64 writer. /// Wave, Broadcast-WAV and RF64/BW64 writer.
/// ///
/// A `WaveWriter` creates a new wave file at the given path (with `create()`) /// A `WaveWriter` creates a new wave file at the given path (with `create()`)
/// or into the given `Write`- and `Seek`-able inner writer. /// or into the given `Write`- and `Seek`-able inner writer.
/// ///
/// Audio is added to the wave file by starting the audio data chunk with /// 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 /// `WaveWriter::audio_frame_writer()`. All of the functions that add chunks
/// move the WaveWriter and return it to the host when complete. /// move the WaveWriter and return it to the host when complete.
/// ///
/// # Structure of New Wave Files /// # Structure of New Wave Files
/// ///
/// `WaveWriter` will create a Wave file with two chunks automatically: a 96 /// `WaveWriter` will create a Wave file with two chunks automatically: a 96
/// byte `JUNK` chunk and a standard `fmt ` chunk, which has the extended /// 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 /// 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 /// a reservation for a `ds64` record which will be written over it if
/// the file needs to be upgraded to RF64 format. /// 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 /// `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. /// first byte of the data chunk's content is aligned with 0x4000.
/// ///
/// ``` /// ```
/// use bwavfile::{WaveWriter,WaveFmt}; /// use bwavfile::{WaveWriter,WaveFmt};
/// # use std::io::Cursor; /// # use std::io::Cursor;
/// ///
/// // Write a three-sample wave file to a cursor /// // Write a three-sample wave file to a 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 format = WaveFmt::new_pcm_mono(48000, 24);
@@ -175,10 +205,10 @@ impl<W> Write for WaveChunkWriter<W> where W: Write + Seek {
/// ``` /// ```
/// ///
/// ## Resources /// ## Resources
/// ///
/// ### Implementation of Wave Files /// ### Implementation of Wave Files
/// - [Peter Kabal, McGill University](http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/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 /// (August 1991), IBM Corporation and Microsoft Corporation
/// ///
/// ### Implementation of Broadcast Wave Files /// ### Implementation of Broadcast Wave Files
@@ -191,57 +221,66 @@ impl<W> Write for WaveChunkWriter<W> where W: Write + Seek {
/// - Presently in force, adopted by the EBU in [EBU Tech 3306v2][ebu3306v2] (June 2018). /// - 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" /// - [EBU Tech 3306v1][ebu3306v1] (July 2009), "MBWF / RF64: An extended File Format for Audio"
/// - No longer in force, however long-established. /// - No longer in force, however long-established.
/// ///
/// ///
/// [ebu3285]: https://tech.ebu.ch/docs/tech/tech3285.pdf /// [ebu3285]: https://tech.ebu.ch/docs/tech/tech3285.pdf
/// [ebu3306v1]: https://tech.ebu.ch/docs/tech/tech3306v1_1.pdf /// [ebu3306v1]: https://tech.ebu.ch/docs/tech/tech3306v1_1.pdf
/// [ebu3306v2]: https://tech.ebu.ch/docs/tech/tech3306.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 /// [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
pub struct WaveWriter<W> where W: Write + Seek { pub struct WaveWriter<W>
inner : W, where
W: Write + Seek,
{
inner: W,
form_length: u64, form_length: u64,
/// True if file is RF64 /// True if file is RF64
pub is_rf64: bool, pub is_rf64: bool,
/// Format of the wave file. /// 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<BufWriter<File>> { impl WaveWriter<BufWriter<File>> {
/// Create a new Wave file at `path`. /// Create a new Wave file at `path`.
pub fn create<P: AsRef<Path>>(path : P, format : WaveFmt) -> Result<Self, Error> { pub fn create<P: AsRef<Path>>(path: P, format: WaveFmt) -> Result<Self, Error> {
let f = File::create(path)?; let f = File::create(path)?;
let b = BufWriter::new(f); let b = BufWriter::new(f);
Ok( Self::new(b, format)? ) Ok(Self::new(b, format)?)
} }
} }
impl WaveWriter<File> { impl WaveWriter<File> {
/// Creare a new Wave file with unbuffered IO at `path` /// Creare a new Wave file with unbuffered IO at `path`
pub fn create_unbuffered<P: AsRef<Path>>(path : P, format : WaveFmt) -> Result<Self, Error> { pub fn create_unbuffered<P: AsRef<Path>>(path: P, format: WaveFmt) -> Result<Self, Error> {
let f = File::create(path)?; let f = File::create(path)?;
Ok( Self::new(f, format)? ) Ok(Self::new(f, format)?)
} }
} }
impl<W> WaveWriter<W> where W: Write + Seek { impl<W> WaveWriter<W>
where
W: Write + Seek,
{
/// Wrap a writer in a Wave writer. /// 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` /// written to it along with the format descriptor (and possibly a `fact`
/// chunk if appropriate). /// chunk if appropriate).
pub fn new(mut inner : W, format: WaveFmt) -> Result<Self, Error> { pub fn new(mut inner: W, format: WaveFmt) -> Result<Self, Error> {
inner.write_fourcc(RIFF_SIG)?; inner.write_fourcc(RIFF_SIG)?;
inner.write_u32::<LittleEndian>(0)?; inner.write_u32::<LittleEndian>(0)?;
inner.write_fourcc(WAVE_SIG)?; 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)?; retval.increment_form_length(4)?;
@@ -252,10 +291,10 @@ impl<W> WaveWriter<W> where W: Write + Seek {
chunk.write_wave_fmt(&format)?; chunk.write_wave_fmt(&format)?;
let retval = chunk.end()?; 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.seek(SeekFrom::End(0))?;
self.inner.write_fourcc(ident)?; self.inner.write_fourcc(ident)?;
assert!(data.len() < u32::MAX as usize); assert!(data.len() < u32::MAX as usize);
@@ -271,21 +310,21 @@ impl<W> WaveWriter<W> where W: Write + Seek {
} }
/// Write Broadcast-Wave metadata to the file. /// Write Broadcast-Wave metadata to the file.
/// ///
/// This function will write the metadata chunk immediately to the end of /// 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 /// the file; if you have already written and closed the audio data the
/// bext chunk will be positioned after it. /// 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 //FIXME Implement re-writing
let mut c = Cursor::new(vec![0u8; 0]); let mut c = Cursor::new(vec![0u8; 0]);
c.write_bext(&bext)?; c.write_bext(&bext)?;
let buf = c.into_inner(); let buf = c.into_inner();
self.write_chunk(BEXT_SIG, &buf )?; self.write_chunk(BEXT_SIG, &buf)?;
Ok(()) Ok(())
} }
/// Write iXML metadata /// 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 //FIXME Implement re-writing
self.write_chunk(IXML_SIG, &ixml) self.write_chunk(IXML_SIG, &ixml)
} }
@@ -302,7 +341,7 @@ impl<W> WaveWriter<W> where W: Write + Seek {
self.write_chunk(JUNK_SIG, &filler) 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`. /// `WaveWriter`.
/// ///
pub fn audio_frame_writer(mut self) -> Result<AudioFrameWriter<W>, Error> { pub fn audio_frame_writer(mut self) -> Result<AudioFrameWriter<W>, Error> {
@@ -317,11 +356,11 @@ impl<W> WaveWriter<W> where W: Write + Seek {
chunk.write(&buf)?; chunk.write(&buf)?;
let closed = chunk.end()?; let closed = chunk.end()?;
let inner = closed.chunk(DATA_SIG)?; let inner = closed.chunk(DATA_SIG)?;
Ok( AudioFrameWriter::new(inner) ) Ok(AudioFrameWriter::new(inner))
} }
/// Open a wave chunk writer here /// Open a wave chunk writer here
fn chunk(mut self, ident: FourCC) -> Result<WaveChunkWriter<W>,Error> { fn chunk(mut self, ident: FourCC) -> Result<WaveChunkWriter<W>, Error> {
self.inner.seek(SeekFrom::End(0))?; self.inner.seek(SeekFrom::End(0))?;
WaveChunkWriter::begin(self, ident) WaveChunkWriter::begin(self, ident)
} }
@@ -350,10 +389,10 @@ impl<W> WaveWriter<W> where W: Write + Seek {
self.inner.write_u64::<LittleEndian>(self.form_length)?; self.inner.write_u64::<LittleEndian>(self.form_length)?;
} else if self.form_length < u32::MAX as u64 { } else if self.form_length < u32::MAX as u64 {
self.inner.seek(SeekFrom::Start(4))?; self.inner.seek(SeekFrom::Start(4))?;
self.inner.write_u32::<LittleEndian>(self.form_length as u32)?; self.inner
.write_u32::<LittleEndian>(self.form_length as u32)?;
} else { } else {
self.promote_to_rf64()?; self.promote_to_rf64()?;
} }
Ok(()) Ok(())
} }
@@ -361,11 +400,11 @@ impl<W> WaveWriter<W> where W: Write + Seek {
#[test] #[test]
fn test_new() { fn test_new() {
use std::io::Cursor;
use super::fourcc::ReadFourCC; use super::fourcc::ReadFourCC;
use byteorder::ReadBytesExt; 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); let format = WaveFmt::new_pcm_mono(4800, 24);
WaveWriter::new(&mut cursor, format).unwrap(); WaveWriter::new(&mut cursor, format).unwrap();
@@ -377,7 +416,7 @@ fn test_new() {
assert_eq!(cursor.read_fourcc().unwrap(), JUNK_SIG); assert_eq!(cursor.read_fourcc().unwrap(), JUNK_SIG);
let junk_size = cursor.read_u32::<LittleEndian>().unwrap(); let junk_size = cursor.read_u32::<LittleEndian>().unwrap();
assert_eq!(junk_size,96); assert_eq!(junk_size, 96);
cursor.seek(SeekFrom::Current(junk_size as i64)).unwrap(); cursor.seek(SeekFrom::Current(junk_size as i64)).unwrap();
assert_eq!(cursor.read_fourcc().unwrap(), FMT__SIG); assert_eq!(cursor.read_fourcc().unwrap(), FMT__SIG);
@@ -387,14 +426,14 @@ fn test_new() {
#[test] #[test]
fn test_write_audio() { fn test_write_audio() {
use std::io::Cursor;
use super::fourcc::ReadFourCC; use super::fourcc::ReadFourCC;
use byteorder::ReadBytesExt; 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 format = WaveFmt::new_pcm_mono(48000, 24);
let w = WaveWriter::new(&mut cursor, format).unwrap(); let w = WaveWriter::new(&mut cursor, format).unwrap();
let mut frame_writer = w.audio_frame_writer().unwrap(); let mut frame_writer = w.audio_frame_writer().unwrap();
frame_writer.write_integer_frames(&[0i32]).unwrap(); frame_writer.write_integer_frames(&[0i32]).unwrap();
@@ -430,14 +469,17 @@ fn test_write_audio() {
let tell = cursor.seek(SeekFrom::Current(0)).unwrap(); let tell = cursor.seek(SeekFrom::Current(0)).unwrap();
assert!(tell % 0x4000 == 0); 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] #[test]
fn test_write_bext() { fn test_write_bext() {
use std::io::Cursor; 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 format = WaveFmt::new_pcm_mono(48000, 24);
let mut w = WaveWriter::new(&mut cursor, format).unwrap(); let mut w = WaveWriter::new(&mut cursor, format).unwrap();
@@ -469,14 +511,13 @@ fn test_write_bext() {
frame_writer.end().unwrap(); frame_writer.end().unwrap();
} }
// NOTE! This test of RF64 writing takes several minutes to complete. // NOTE! This test of RF64 writing takes several minutes to complete.
#[test] #[test]
fn test_create_rf64() { fn test_create_rf64() {
use super::fourcc::ReadFourCC; use super::fourcc::ReadFourCC;
use byteorder::ReadBytesExt; 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 format = WaveFmt::new_pcm_stereo(48000, 24);
let w = WaveWriter::new(&mut cursor, format).unwrap(); let w = WaveWriter::new(&mut cursor, format).unwrap();
@@ -493,7 +534,10 @@ fn test_create_rf64() {
} }
af.end().unwrap(); 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; let expected_data_length = four_and_a_half_hours_of_frames * format.block_alignment as u64;
cursor.seek(SeekFrom::Start(0)).unwrap(); cursor.seek(SeekFrom::Start(0)).unwrap();
@@ -506,20 +550,33 @@ fn test_create_rf64() {
let form_size = cursor.read_u64::<LittleEndian>().unwrap(); let form_size = cursor.read_u64::<LittleEndian>().unwrap();
let data_size = cursor.read_u64::<LittleEndian>().unwrap(); let data_size = cursor.read_u64::<LittleEndian>().unwrap();
assert_eq!(data_size, expected_data_length); 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); assert_eq!(cursor.read_fourcc().unwrap(), FMT__SIG);
let fmt_size = cursor.read_u32::<LittleEndian>().unwrap(); let fmt_size = cursor.read_u32::<LittleEndian>().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); assert_eq!(cursor.read_fourcc().unwrap(), ELM1_SIG);
let elm1_size = cursor.read_u32::<LittleEndian>().unwrap(); let elm1_size = cursor.read_u32::<LittleEndian>().unwrap();
let data_start = cursor.seek(SeekFrom::Current((elm1_size + elm1_size % 2) as i64)).unwrap(); let data_start = cursor
.seek(SeekFrom::Current((elm1_size + elm1_size % 2) as i64))
assert!((data_start + 8) % 0x4000 == 0, "data content start is not aligned, starts at {}", data_start + 8); .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_fourcc().unwrap(), DATA_SIG);
assert_eq!(cursor.read_u32::<LittleEndian>().unwrap(), 0xFFFF_FFFF); assert_eq!(cursor.read_u32::<LittleEndian>().unwrap(), 0xFFFF_FFFF);
cursor.seek(SeekFrom::Current(data_size as i64)).unwrap(); 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
)
} }

View File

@@ -1,7 +1,6 @@
extern crate serde_json; extern crate serde_json;
use core::fmt::Debug; use core::fmt::Debug;
use serde_json::{Value, from_str}; use serde_json::{from_str, Value};
use std::fs::File; use std::fs::File;
use std::io::Read; use std::io::Read;
@@ -13,43 +12,42 @@ use bwavfile::WaveReader;
// as read by `WaveReader`. // as read by `WaveReader`.
// This is rickety but we're going with it // This is rickety but we're going with it
fn assert_match_stream<T>(stream_key: &str, fn assert_match_stream<T>(stream_key: &str, other: impl Fn(&mut WaveReader<File>) -> T)
other: impl Fn(&mut WaveReader<File>) -> T) where
where T: PartialEq + Debug, T: PartialEq + Debug,
T: Into<Value> T: Into<Value>,
{ {
let mut json_file = File::open("tests/ffprobe_media_tests.json").unwrap(); let mut json_file = File::open("tests/ffprobe_media_tests.json").unwrap();
let mut s = String::new(); let mut s = String::new();
json_file.read_to_string(&mut s).unwrap(); 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() v.iter()
.filter(|value| { .filter(|value| !value["format"]["filename"].is_null())
!value["format"]["filename"].is_null()
})
.for_each(|value| { .for_each(|value| {
let filen : &str = value["format"]["filename"].as_str().unwrap(); let filen: &str = value["format"]["filename"].as_str().unwrap();
let json_value : &Value = &value["streams"][0][stream_key]; let json_value: &Value = &value["streams"][0][stream_key];
let mut wavfile = WaveReader::open_unbuffered(filen).unwrap(); let mut wavfile = WaveReader::open_unbuffered(filen).unwrap();
let wavfile_value: T = other(&mut wavfile); let wavfile_value: T = other(&mut wavfile);
println!("asserting {} for {}",stream_key, filen); println!("asserting {} for {}", stream_key, filen);
assert_eq!(Into::<Value>::into(wavfile_value), *json_value); assert_eq!(Into::<Value>::into(wavfile_value), *json_value);
}) })
} }
} }
#[test] #[test]
fn test_frame_count() { fn test_frame_count() {
assert_match_stream("duration_ts", |w| w.frame_length().unwrap() ); assert_match_stream("duration_ts", |w| w.frame_length().unwrap());
} }
#[test] #[test]
fn test_sample_rate() { 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] #[test]
fn test_channel_count() { fn test_channel_count() {
assert_match_stream("channels", |w| w.format().unwrap().channel_count ); assert_match_stream("channels", |w| w.format().unwrap().channel_count);
} }

View File

@@ -1,17 +1,15 @@
extern crate bwavfile; extern crate bwavfile;
use bwavfile::WaveReader; use bwavfile::ChannelMask;
use bwavfile::Error; use bwavfile::Error;
use bwavfile::{ ChannelMask}; use bwavfile::WaveReader;
#[test] #[test]
fn test_open() { fn test_open() {
let path = "tests/media/ff_silence.wav"; let path = "tests/media/ff_silence.wav";
match WaveReader::open(path) { match WaveReader::open(path) {
Ok(_) => { Ok(_) => (),
()
},
Err(x) => { Err(x) => {
assert!(false, "Opened error.wav with unexpected error {:?}", x) assert!(false, "Opened error.wav with unexpected error {:?}", x)
} }
@@ -19,7 +17,7 @@ fn test_open() {
} }
#[test] #[test]
fn test_format_silence() -> Result<(),Error> { fn test_format_silence() -> Result<(), Error> {
let path = "tests/media/ff_silence.wav"; let path = "tests/media/ff_silence.wav";
let mut w = WaveReader::open(path)?; 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.sample_rate, 44100);
assert_eq!(format.channel_count, 1); assert_eq!(format.channel_count, 1);
assert_eq!(format.tag as u16, 1); assert_eq!(format.tag as u16, 1);
Ok( () ) Ok(())
} }
#[test] #[test]
@@ -44,18 +42,18 @@ fn test_format_error() {
} }
#[test] #[test]
fn test_frame_count() -> Result<(),Error> { fn test_frame_count() -> Result<(), Error> {
let path = "tests/media/ff_silence.wav"; let path = "tests/media/ff_silence.wav";
let mut w = WaveReader::open(path)?; let mut w = WaveReader::open(path)?;
let l = w.frame_length()?; let l = w.frame_length()?;
assert_eq!(l, 44100); assert_eq!(l, 44100);
Ok( () ) Ok(())
} }
#[test] #[test]
fn test_minimal_wave() { fn test_minimal_wave() {
let path = "tests/media/ff_silence.wav"; let path = "tests/media/ff_silence.wav";
let mut w = WaveReader::open(path).expect("Failure opening file"); 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(); let mut reader = w.audio_frame_reader().unwrap();
assert_eq!(reader.read_integer_frame(&mut buffer).unwrap(), 1); assert_eq!(reader.read_integer_frame(&mut buffer).unwrap(), 1);
assert_eq!(buffer[0], -2823_i32); assert_eq!(buffer[0], -2823_i32);
assert_eq!(reader.read_integer_frame(&mut buffer).unwrap(), 1); assert_eq!(reader.read_integer_frame(&mut buffer).unwrap(), 1);
assert_eq!(buffer[0], 2012_i32); assert_eq!(buffer[0], 2012_i32);
assert_eq!(reader.read_integer_frame(&mut buffer).unwrap(), 1); assert_eq!(reader.read_integer_frame(&mut buffer).unwrap(), 1);
assert_eq!(buffer[0], 4524_i32); assert_eq!(buffer[0], 4524_i32);
} }
#[test] #[test]
@@ -126,10 +122,10 @@ fn test_channels_stereo() {
let channels = w.channels().unwrap(); let channels = w.channels().unwrap();
assert_eq!(channels.len(), 2); assert_eq!(channels.len(), 2);
assert_eq!(channels[0].index,0); assert_eq!(channels[0].index, 0);
assert_eq!(channels[1].index,1); assert_eq!(channels[1].index, 1);
assert_eq!(channels[0].speaker,ChannelMask::FrontLeft); assert_eq!(channels[0].speaker, ChannelMask::FrontLeft);
assert_eq!(channels[1].speaker,ChannelMask::FrontRight); assert_eq!(channels[1].speaker, ChannelMask::FrontRight);
} }
#[test] #[test]
@@ -140,8 +136,8 @@ fn test_channels_mono_no_extended() {
let channels = w.channels().unwrap(); let channels = w.channels().unwrap();
assert_eq!(channels.len(), 1); assert_eq!(channels.len(), 1);
assert_eq!(channels[0].index,0); assert_eq!(channels[0].index, 0);
assert_eq!(channels[0].speaker,ChannelMask::FrontCenter); assert_eq!(channels[0].speaker, ChannelMask::FrontCenter);
} }
#[test] #[test]
@@ -152,19 +148,21 @@ fn test_channels_stereo_no_fmt_extended() {
let channels = w.channels().unwrap(); let channels = w.channels().unwrap();
assert_eq!(channels.len(), 2); assert_eq!(channels.len(), 2);
assert_eq!(channels[0].index,0); assert_eq!(channels[0].index, 0);
assert_eq!(channels[1].index,1); assert_eq!(channels[1].index, 1);
assert_eq!(channels[0].speaker,ChannelMask::FrontLeft); assert_eq!(channels[0].speaker, ChannelMask::FrontLeft);
assert_eq!(channels[1].speaker,ChannelMask::FrontRight); assert_eq!(channels[1].speaker, ChannelMask::FrontRight);
} }
///See issue 6 and 7 ///See issue 6 and 7
#[test] #[test]
fn test_frame_reader_consumes_reader() { fn test_frame_reader_consumes_reader() {
// Issue #6 // Issue #6
use bwavfile::{WaveFmt, AudioFrameReader}; use bwavfile::{AudioFrameReader, WaveFmt};
use std::fs::File; use std::fs::File;
fn from_wav_filename(wav_filename: &str) -> Result<(WaveFmt, AudioFrameReader<std::io::BufReader<File>>), ()> { fn from_wav_filename(
wav_filename: &str,
) -> Result<(WaveFmt, AudioFrameReader<std::io::BufReader<File>>), ()> {
if let Ok(mut r) = WaveReader::open(&wav_filename) { if let Ok(mut r) = WaveReader::open(&wav_filename) {
let format = r.format().unwrap(); let format = r.format().unwrap();
let frame_reader = r.audio_frame_reader().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].label, None);
assert_eq!(cue_points[0].note, None); assert_eq!(cue_points[0].note, None);
assert_eq!(cue_points[0].offset, 90112); assert_eq!(cue_points[0].offset, 90112);
assert_eq!(cue_points[1].frame, 0); assert_eq!(cue_points[1].frame, 0);
assert_eq!(cue_points[1].length, None); assert_eq!(cue_points[1].length, None);
assert_eq!(cue_points[1].label, 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[1].offset, 176128);
assert_eq!(cue_points[2].frame, 0); assert_eq!(cue_points[2].frame, 0);
assert_eq!(cue_points[2].length, None); assert_eq!(cue_points[2].length, None);
assert_eq!(cue_points[2].label, 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[2].offset, 237568);
assert_eq!(cue_points[3].frame, 0); assert_eq!(cue_points[3].frame, 0);
assert_eq!(cue_points[3].length, None); assert_eq!(cue_points[3].length, None);
assert_eq!(cue_points[3].label, 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[3].offset, 294912);
assert_eq!(cue_points[4].frame, 0); assert_eq!(cue_points[4].frame, 0);
assert_eq!(cue_points[4].length, None); assert_eq!(cue_points[4].length, None);
assert_eq!(cue_points[4].label, 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[4].offset, 380928);
assert_eq!(cue_points[5].frame, 0); assert_eq!(cue_points[5].frame, 0);
assert_eq!(cue_points[5].length, None); assert_eq!(cue_points[5].length, None);
assert_eq!(cue_points[5].label, 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); assert_eq!(cue_points[5].offset, 385024);
} }