From 1f8542a7efb481da076120bf8107032c5b48889d Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Sat, 26 Dec 2020 21:25:41 -0800 Subject: [PATCH 01/21] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 188032c..e306325 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ This is currently a work-in-progress! However many features presently work: | Feature |Read |Write| |---------------------------------------|:---:|:-----:| -| Standard .wav files | ☑️ | ☑ ️ | +| Standard .wav files | ☑️ | ☑️ | | Transparent promotion to RF64/BW64 | ☑️ | ☑️ | | Unified interface for regular and extended Wave format | ☑️ | ☑️ | | Channel/speaker map metadata | ☑️ | ☑️ | From f978eb95ed863693e653af12b6f822d531d0501b Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Sat, 26 Dec 2020 21:28:47 -0800 Subject: [PATCH 02/21] Update README.md Twiddles, RF64 writing test case note. --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e306325..171cbec 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ This is currently a work-in-progress! However many features presently work: | Broadcast-WAVE Level overview `levl` metadata | | | | Cue list metadata | ☑️ | | | Sampler and instrument metadata | | | -| Enhanced Wave file form validation | ☑ ️ | | +| Enhanced Wave file form validation | ☑️ | | ## Use Examples @@ -69,3 +69,9 @@ All of the media for the integration tests is committed to the respository in zipped form. Before you can run tests, you need to `cd` into the `tests` directory and run the `create_test_media.sh` script. Note that one of the test files (the RF64 test case) is over four gigs in size. + +Likewise, [the RF64 _writing_ test case][rf64test] writes an RF64 wave file +to memory and is very time-intensive, so is commented-out in the code but +can be un-commented if you want to run it on your system. + +[rf64test]: https://github.com/iluvcapra/bwavfile/blob/1f8542a7efb481da076120bf8107032c5b48889d/src/wavewriter.rs#L399 From 087d98b2280f0de81039522d6e1b0e81cd9d2c4b Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Sat, 26 Dec 2020 22:22:59 -0800 Subject: [PATCH 03/21] Reduced commenting of rf64 test case ...to just the line we want to comment-out --- src/wavewriter.rs | 74 +++++++++++++++++++++++------------------------ 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/src/wavewriter.rs b/src/wavewriter.rs index 04b69f9..afe598f 100644 --- a/src/wavewriter.rs +++ b/src/wavewriter.rs @@ -397,53 +397,53 @@ fn test_write_bext() { // nearly 5 mins to run I have omitted it from the source for now... // #[test] -// fn test_create_rf64() { -// use std::io::Cursor; -// use super::fourcc::ReadFourCC; -// use byteorder::ReadBytesExt; +fn test_create_rf64() { + use std::io::Cursor; + use super::fourcc::ReadFourCC; + use byteorder::ReadBytesExt; -// let mut cursor = Cursor::new(vec![0u8;0]); -// let format = WaveFmt::new_pcm_stereo(48000, 24); -// let w = WaveWriter::new(&mut cursor, format).unwrap(); + let mut cursor = Cursor::new(vec![0u8;0]); + let format = WaveFmt::new_pcm_stereo(48000, 24); + let w = WaveWriter::new(&mut cursor, format).unwrap(); -// let buf = format.create_frame_buffer(); + let buf = format.create_frame_buffer(); -// let four_and_a_half_hours = 48000 * 16_200; // 4,665,600,000 bytes / 777,600,000 frames + let four_and_a_half_hours = 48000 * 16_200; // 4,665,600,000 bytes / 777,600,000 frames -// let mut af = w.audio_frame_writer().unwrap(); + let mut af = w.audio_frame_writer().unwrap(); -// for _ in 0..four_and_a_half_hours { -// af.write_integer_frame(&buf).unwrap(); -// } -// af.end().unwrap(); + for _ in 0..four_and_a_half_hours { + af.write_integer_frame(&buf).unwrap(); + } + af.end().unwrap(); -// let expected_data_length = four_and_a_half_hours * format.block_alignment as u64; + let expected_data_length = four_and_a_half_hours * format.block_alignment as u64; -// cursor.seek(SeekFrom::Start(0)).unwrap(); -// assert_eq!(cursor.read_fourcc().unwrap(), RF64_SIG); -// assert_eq!(cursor.read_u32::().unwrap(), 0xFFFF_FFFF); -// assert_eq!(cursor.read_fourcc().unwrap(), WAVE_SIG); + cursor.seek(SeekFrom::Start(0)).unwrap(); + assert_eq!(cursor.read_fourcc().unwrap(), RF64_SIG); + assert_eq!(cursor.read_u32::().unwrap(), 0xFFFF_FFFF); + assert_eq!(cursor.read_fourcc().unwrap(), WAVE_SIG); -// assert_eq!(cursor.read_fourcc().unwrap(), DS64_SIG); -// let ds64_size = cursor.read_u32::().unwrap(); -// let form_size = cursor.read_u64::().unwrap(); -// let data_size = cursor.read_u64::().unwrap(); -// assert_eq!(data_size, expected_data_length); -// cursor.seek(SeekFrom::Current(ds64_size as i64 - 16)).unwrap(); + assert_eq!(cursor.read_fourcc().unwrap(), DS64_SIG); + let ds64_size = cursor.read_u32::().unwrap(); + let form_size = cursor.read_u64::().unwrap(); + let data_size = cursor.read_u64::().unwrap(); + assert_eq!(data_size, expected_data_length); + cursor.seek(SeekFrom::Current(ds64_size as i64 - 16)).unwrap(); -// assert_eq!(cursor.read_fourcc().unwrap(), FMT__SIG); -// let fmt_size = cursor.read_u32::().unwrap(); -// cursor.seek(SeekFrom::Current((fmt_size + fmt_size % 2) as i64)).unwrap(); + assert_eq!(cursor.read_fourcc().unwrap(), FMT__SIG); + let fmt_size = cursor.read_u32::().unwrap(); + cursor.seek(SeekFrom::Current((fmt_size + fmt_size % 2) as i64)).unwrap(); -// assert_eq!(cursor.read_fourcc().unwrap(), ELM1_SIG); -// let elm1_size = cursor.read_u32::().unwrap(); -// let data_start = cursor.seek(SeekFrom::Current((elm1_size + elm1_size % 2) as i64)).unwrap(); + assert_eq!(cursor.read_fourcc().unwrap(), ELM1_SIG); + let elm1_size = cursor.read_u32::().unwrap(); + let data_start = cursor.seek(SeekFrom::Current((elm1_size + elm1_size % 2) as i64)).unwrap(); -// assert!((data_start + 8) % 0x4000 == 0, "data content start is not aligned, starts at {}", data_start + 8); -// assert_eq!(cursor.read_fourcc().unwrap(), DATA_SIG); -// assert_eq!(cursor.read_u32::().unwrap(), 0xFFFF_FFFF); -// cursor.seek(SeekFrom::Current(data_size as i64)).unwrap(); + assert!((data_start + 8) % 0x4000 == 0, "data content start is not aligned, starts at {}", data_start + 8); + assert_eq!(cursor.read_fourcc().unwrap(), DATA_SIG); + assert_eq!(cursor.read_u32::().unwrap(), 0xFFFF_FFFF); + cursor.seek(SeekFrom::Current(data_size as i64)).unwrap(); -// assert_eq!(4 + 8 + ds64_size as u64 + 8 + data_size + 8 + fmt_size as u64 + 8 + elm1_size as u64, form_size) -// } \ No newline at end of file + assert_eq!(4 + 8 + ds64_size as u64 + 8 + data_size + 8 + fmt_size as u64 + 8 + elm1_size as u64, form_size) +} \ No newline at end of file From cbfcce235c0f3f572475ddfeebaec7e2540ee45e Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Sun, 27 Dec 2020 11:34:12 -0800 Subject: [PATCH 04/21] Frame writing methods to make go faster --- src/fmt.rs | 8 ++++ src/wavewriter.rs | 105 +++++++++++++++++++++++++++++----------------- 2 files changed, 74 insertions(+), 39 deletions(-) diff --git a/src/fmt.rs b/src/fmt.rs index 1c1b53b..c789c1a 100644 --- a/src/fmt.rs +++ b/src/fmt.rs @@ -182,6 +182,14 @@ pub struct WaveFmt { impl WaveFmt { + pub fn valid_bits_per_sample(&self) -> u16 { + if let Some(ext) = self.extended_format { + ext.valid_bits_per_sample + } else { + self.bits_per_sample + } + } + /// Create a new integer PCM format for a monoaural audio stream. pub fn new_pcm_mono(sample_rate: u32, bits_per_sample: u16) -> Self { Self::new_pcm_multichannel(sample_rate, bits_per_sample, 0x4) diff --git a/src/wavewriter.rs b/src/wavewriter.rs index afe598f..cdec19a 100644 --- a/src/wavewriter.rs +++ b/src/wavewriter.rs @@ -1,5 +1,5 @@ use std::fs::File; -use std::io::{Write,Seek,SeekFrom}; +use std::io::{Write,Seek,SeekFrom,Cursor}; use super::Error; use super::fourcc::{FourCC, WriteFourCC, RIFF_SIG, RF64_SIG, DS64_SIG, @@ -16,35 +16,60 @@ use byteorder::WriteBytesExt; /// /// pub struct AudioFrameWriter where W: Write + Seek { - inner : WaveChunkWriter + inner : WaveChunkWriter, + framed_bits_per_sample : u16, + bits_per_sample: u16, + channel_count: u16, + block_alignment: u16, } impl AudioFrameWriter where W: Write + Seek { - /// Write one audio frame. - /// - pub fn write_integer_frame(&mut self, buffer: &[i32]) -> Result { - let format = self.inner.inner.format; - assert!(buffer.len() as u16 == format.channel_count, - "read_integer_frame was called with a mis-sized buffer, expected {}, was {}", - format.channel_count, buffer.len()); - - let framed_bits_per_sample = format.block_alignment * 8 / format.channel_count; - - for n in 0..(format.channel_count as usize) { - match (format.bits_per_sample, framed_bits_per_sample) { - (0..=8,8) => self.inner.write_u8((buffer[n] + 0x80) as u8 )?, // EBU 3285 §A2.2 - (9..=16,16) => self.inner.write_i16::(buffer[n] as i16)?, - (10..=24,24) => self.inner.write_i24::(buffer[n])?, - (25..=32,32) => self.inner.write_i32::(buffer[n])?, - (b,_)=> panic!("Unrecognized integer format, bits per sample {}, channels {}, block_alignment {}", - b, format.channel_count, format.block_alignment) - } + fn new(inner: WaveChunkWriter) -> Self { + let fbps = inner.inner.format.bits_per_sample; + let ba = inner.inner.format.block_alignment; + let cc = inner.inner.format.channel_count; + let bps = inner.inner.format.valid_bits_per_sample(); + AudioFrameWriter { + inner, + framed_bits_per_sample: fbps, + bits_per_sample: bps, + channel_count: cc, + block_alignment: ba, } - self.inner.flush()?; - Ok(1) } + fn write_integer_frames_to_buffer(&self, from_frames :&[i32], to_buffer : &mut Vec) -> () { + let mut write_cursor = Cursor::new(to_buffer); + + assert!(from_frames.len() % self.channel_count as usize == 0, + "frames buffer does not contain a number of samples % channel_count == 0"); + + for n in 0..from_frames.len() { + match (self.bits_per_sample, self.framed_bits_per_sample) { + (0..=8,8) => write_cursor.write_u8((from_frames[n] + 0x80) as u8 ).unwrap(), // EBU 3285 §A2.2 + (9..=16,16) => write_cursor.write_i16::(from_frames[n] as i16).unwrap(), + (10..=24,24) => write_cursor.write_i24::(from_frames[n]).unwrap(), + (25..=32,32) => write_cursor.write_i32::(from_frames[n]).unwrap(), + (b,_)=> panic!("Unrecognized integer format, bits per sample {}, channels {}, block_alignment {}", + b, self.channel_count, self.block_alignment) + } + } + () + } + + pub fn write_integer_frames(&mut self, buffer: &[i32]) -> Result { + let mut write_buffer = vec![0u8; 0]; + + self.write_integer_frames_to_buffer(&buffer, &mut write_buffer); + + self.inner.write(&write_buffer)?; + self.inner.flush()?; + Ok(write_buffer.len() as u64 / self.channel_count as u64) + } + + + /// Finish writing audio frames and unwrap the inner `WaveWriter`. /// /// This method must be called when the client has finished writing audio @@ -162,9 +187,9 @@ impl Write for WaveChunkWriter where W: Write + Seek { /// /// let mut frame_writer = w.audio_frame_writer().unwrap(); /// -/// frame_writer.write_integer_frame(&[0i32]).unwrap(); -/// frame_writer.write_integer_frame(&[0i32]).unwrap(); -/// frame_writer.write_integer_frame(&[0i32]).unwrap(); +/// frame_writer.write_integer_frames(&[0i32]).unwrap(); +/// frame_writer.write_integer_frames(&[0i32]).unwrap(); +/// frame_writer.write_integer_frames(&[0i32]).unwrap(); /// frame_writer.end().unwrap(); /// ``` pub struct WaveWriter where W: Write + Seek { @@ -262,7 +287,7 @@ impl WaveWriter where W: Write + Seek { chunk.write(&buf)?; let closed = chunk.end()?; let inner = closed.chunk(DATA_SIG)?; - Ok( AudioFrameWriter { inner } ) + Ok( AudioFrameWriter::new(inner) ) } fn increment_form_length(&mut self, amount: u64) -> Result<(), std::io::Error> { @@ -320,9 +345,9 @@ fn test_write_audio() { let mut frame_writer = w.audio_frame_writer().unwrap(); - frame_writer.write_integer_frame(&[0i32]).unwrap(); - frame_writer.write_integer_frame(&[0i32]).unwrap(); - frame_writer.write_integer_frame(&[0i32]).unwrap(); + frame_writer.write_integer_frames(&[0i32]).unwrap(); + frame_writer.write_integer_frames(&[0i32]).unwrap(); + frame_writer.write_integer_frames(&[0i32]).unwrap(); frame_writer.end().unwrap(); @@ -385,9 +410,9 @@ fn test_write_bext() { let mut frame_writer = w.audio_frame_writer().unwrap(); - frame_writer.write_integer_frame(&[0i32]).unwrap(); - frame_writer.write_integer_frame(&[0i32]).unwrap(); - frame_writer.write_integer_frame(&[0i32]).unwrap(); + frame_writer.write_integer_frames(&[0i32]).unwrap(); + frame_writer.write_integer_frames(&[0i32]).unwrap(); + frame_writer.write_integer_frames(&[0i32]).unwrap(); frame_writer.end().unwrap(); } @@ -396,7 +421,7 @@ fn test_write_bext() { // NOTE! This test of RF64 writing passes on my machine but because it takes // nearly 5 mins to run I have omitted it from the source for now... -// #[test] +#[test] fn test_create_rf64() { use std::io::Cursor; use super::fourcc::ReadFourCC; @@ -406,19 +431,21 @@ fn test_create_rf64() { let format = WaveFmt::new_pcm_stereo(48000, 24); let w = WaveWriter::new(&mut cursor, format).unwrap(); + let buflen = 16000 as u64; - let buf = format.create_frame_buffer(); + let buf = vec![0i32; buflen as usize]; - let four_and_a_half_hours = 48000 * 16_200; // 4,665,600,000 bytes / 777,600,000 frames + let four_and_a_half_hours_of_frames = 48000 * 16_200; let mut af = w.audio_frame_writer().unwrap(); - for _ in 0..four_and_a_half_hours { - af.write_integer_frame(&buf).unwrap(); + for _ in 0..(four_and_a_half_hours_of_frames * format.channel_count as u64 / buflen) { + af.write_integer_frames(&buf).unwrap(); } af.end().unwrap(); - let expected_data_length = four_and_a_half_hours * format.block_alignment as u64; + assert!(cursor.seek(SeekFrom::End(0)).unwrap() > 0xFFFF_FFFFu64, "internal test error, Created file is not long enough to be RF64" ); + let expected_data_length = four_and_a_half_hours_of_frames * format.block_alignment as u64; cursor.seek(SeekFrom::Start(0)).unwrap(); assert_eq!(cursor.read_fourcc().unwrap(), RF64_SIG); From 84366089ba942541f4165d04ac3b8d6487116190 Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Sun, 27 Dec 2020 12:05:21 -0800 Subject: [PATCH 05/21] Implementation --- src/fmt.rs | 46 +++++++++++++++++++++++++++++++++++++++++++++- src/wavewriter.rs | 41 +++++++---------------------------------- 2 files changed, 52 insertions(+), 35 deletions(-) diff --git a/src/fmt.rs b/src/fmt.rs index c789c1a..aec3bab 100644 --- a/src/fmt.rs +++ b/src/fmt.rs @@ -1,5 +1,9 @@ use uuid::Uuid; use super::common_format::{CommonFormat, UUID_PCM,UUID_BFORMAT_PCM}; +use std::io::Cursor; + +use byteorder::LittleEndian; +use byteorder::WriteBytesExt; #[allow(dead_code)] @@ -181,7 +185,7 @@ pub struct WaveFmt { impl WaveFmt { - + pub fn valid_bits_per_sample(&self) -> u16 { if let Some(ext) = self.extended_format { ext.valid_bits_per_sample @@ -274,6 +278,46 @@ impl WaveFmt { vec![0i32; self.channel_count as usize] } + /// Calculate the size of a byte buffer needed to hold audio data of this + /// format for a given number of frames + pub fn buffer_length(&self, frame_count: u64) -> usize { + (self.block_alignment as u64 * frame_count) as usize + } + + // Write frames into a byte vector + pub fn pack_frames(&self, from_frames: &[i32], into_bytes: &mut Vec) -> () { + let mut write_cursor = Cursor::new(into_bytes); + + assert!(from_frames.len() % self.channel_count as usize == 0, + "frames buffer does not contain a number of samples % channel_count == 0"); + + for n in 0..from_frames.len() { + match (self.valid_bits_per_sample(), self.bits_per_sample) { + (0..=8,8) => write_cursor.write_u8((from_frames[n] + 0x80) as u8 ).unwrap(), // EBU 3285 §A2.2 + (9..=16,16) => write_cursor.write_i16::(from_frames[n] as i16).unwrap(), + (10..=24,24) => write_cursor.write_i24::(from_frames[n]).unwrap(), + (25..=32,32) => write_cursor.write_i32::(from_frames[n]).unwrap(), + (b,_)=> panic!("Unrecognized integer format, bits per sample {}, channels {}, block_alignment {}", + b, self.channel_count, self.block_alignment) + } + } + () + } + + /// Read bytes into frames + // pub fn unpack_frames(&self, from_bytes: &[u8], into_frames: &mut Vec) -> () { + // for n in 0..(from_bytes.len()) { + // buffer[n] = match (self.format.bits_per_sample, framed_bits_per_sample) { + // (0..=8,8) => self.inner.read_u8()? as i32 - 0x80_i32, // EBU 3285 §A2.2 + // (9..=16,16) => self.inner.read_i16::()? as i32, + // (10..=24,24) => self.inner.read_i24::()?, + // (25..=32,32) => self.inner.read_i32::()?, + // (b,_)=> panic!("Unrecognized integer format, bits per sample {}, channels {}, block_alignment {}", + // b, self.format.channel_count, self.format.block_alignment) + // } + // } + // } + /// Channel descriptors for each channel. pub fn channels(&self) -> Vec { diff --git a/src/wavewriter.rs b/src/wavewriter.rs index cdec19a..151f9c9 100644 --- a/src/wavewriter.rs +++ b/src/wavewriter.rs @@ -16,48 +16,23 @@ use byteorder::WriteBytesExt; /// /// pub struct AudioFrameWriter where W: Write + Seek { - inner : WaveChunkWriter, - framed_bits_per_sample : u16, - bits_per_sample: u16, - channel_count: u16, - block_alignment: u16, + inner : WaveChunkWriter } impl AudioFrameWriter where W: Write + Seek { fn new(inner: WaveChunkWriter) -> Self { - let fbps = inner.inner.format.bits_per_sample; - let ba = inner.inner.format.block_alignment; - let cc = inner.inner.format.channel_count; - let bps = inner.inner.format.valid_bits_per_sample(); - AudioFrameWriter { - inner, - framed_bits_per_sample: fbps, - bits_per_sample: bps, - channel_count: cc, - block_alignment: ba, - } + AudioFrameWriter { inner } } fn write_integer_frames_to_buffer(&self, from_frames :&[i32], to_buffer : &mut Vec) -> () { - let mut write_cursor = Cursor::new(to_buffer); - - assert!(from_frames.len() % self.channel_count as usize == 0, + assert!(from_frames.len() % self.inner.inner.format.channel_count as usize == 0, "frames buffer does not contain a number of samples % channel_count == 0"); - - for n in 0..from_frames.len() { - match (self.bits_per_sample, self.framed_bits_per_sample) { - (0..=8,8) => write_cursor.write_u8((from_frames[n] + 0x80) as u8 ).unwrap(), // EBU 3285 §A2.2 - (9..=16,16) => write_cursor.write_i16::(from_frames[n] as i16).unwrap(), - (10..=24,24) => write_cursor.write_i24::(from_frames[n]).unwrap(), - (25..=32,32) => write_cursor.write_i32::(from_frames[n]).unwrap(), - (b,_)=> panic!("Unrecognized integer format, bits per sample {}, channels {}, block_alignment {}", - b, self.channel_count, self.block_alignment) - } - } + self.inner.inner.format.pack_frames(&from_frames, to_buffer); () } + /// Write interleaved samples in `buffer` pub fn write_integer_frames(&mut self, buffer: &[i32]) -> Result { let mut write_buffer = vec![0u8; 0]; @@ -65,11 +40,9 @@ impl AudioFrameWriter where W: Write + Seek { self.inner.write(&write_buffer)?; self.inner.flush()?; - Ok(write_buffer.len() as u64 / self.channel_count as u64) + Ok(write_buffer.len() as u64 / self.inner.inner.format.channel_count as u64) } - - /// Finish writing audio frames and unwrap the inner `WaveWriter`. /// /// This method must be called when the client has finished writing audio @@ -421,7 +394,7 @@ fn test_write_bext() { // NOTE! This test of RF64 writing passes on my machine but because it takes // nearly 5 mins to run I have omitted it from the source for now... -#[test] +//#[test] fn test_create_rf64() { use std::io::Cursor; use super::fourcc::ReadFourCC; From d43ddf63383ebbc3e3d08f6e7d56bd28a93cac86 Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Tue, 29 Dec 2020 12:09:00 -0800 Subject: [PATCH 06/21] Reorganized documentation --- src/bext.rs | 8 +++-- src/cue.rs | 4 +++ src/fmt.rs | 35 ++++++++++++++++----- src/levl.rs | 3 ++ src/lib.rs | 77 ---------------------------------------------- src/sampler.rs | 2 ++ src/wavereader.rs | 68 +++++++++++++++++++++++++++------------- src/wavewriter.rs | 27 +++++++++++++++- tests/Untitled.txt | 5 --- 9 files changed, 115 insertions(+), 114 deletions(-) create mode 100644 src/levl.rs create mode 100644 src/sampler.rs delete mode 100644 tests/Untitled.txt diff --git a/src/bext.rs b/src/bext.rs index 4cc27b0..5e17a2e 100644 --- a/src/bext.rs +++ b/src/bext.rs @@ -14,8 +14,12 @@ pub type Decibels = f32; /// For a Wave file to be a complaint "Broadcast-WAV" file, it must contain /// a `bext` metadata record. /// -/// For reference on the structure and use of the BEXT record -/// check out [EBU Tech 3285](https://tech.ebu.ch/docs/tech/tech3285.pdf). +/// ## Resources +/// - [EBU Tech 3285](https://tech.ebu.ch/docs/tech/tech3285.pdf). +/// - [EBU Tech R098](https://tech.ebu.ch/docs/r/r098.pdf) (1999) "Format for the <CodingHistory> field in Broadcast Wave Format files, BWF" +/// - [EBU Tech R099](https://tech.ebu.ch/docs/r/r099.pdf) (October 2011) "‘Unique’ Source Identifier (USID) for use in the +/// <OriginatorReference> field of the Broadcast Wave Format" + #[derive(Debug)] pub struct Bext { diff --git a/src/cue.rs b/src/cue.rs index 02f3ba5..9d42411 100644 --- a/src/cue.rs +++ b/src/cue.rs @@ -190,7 +190,11 @@ impl AdtlMemberSearch for Vec { /// A cue point recorded in the `cue` and `adtl` metadata. /// +/// ## Resources +/// - [Cue list, label and other metadata](https://sites.google.com/site/musicgapi/technical-documents/wav-file-format#smpl) /// +/// ### Not Implemented +/// - [EBU 3285 Supplement 2](https://tech.ebu.ch/docs/tech/tech3285s2.pdf) (July 2001): Quality chunk and cuesheet pub struct Cue { /// Unique numeric identifier for this cue diff --git a/src/fmt.rs b/src/fmt.rs index aec3bab..db1fc43 100644 --- a/src/fmt.rs +++ b/src/fmt.rs @@ -24,6 +24,9 @@ pub struct ADMAudioID { } /// Describes a single channel in a WAV file. +/// +/// This information is correlated from the Wave format ChannelMap field and +/// the `chna` chunk, if present. pub struct ChannelDescriptor { /// Index, the offset of this channel's samples in one frame. pub index: u16, @@ -128,14 +131,30 @@ pub struct WaveFmtExtended { pub type_guid : Uuid, } -/** - * WAV file data format record. - * - * The `fmt` record contains essential information describing the binary - * structure of the data segment of the WAVE file, such as sample - * rate, sample binary format, channel count, etc. - * - */ +/// +/// WAV file data format record. +/// +/// The `fmt` record contains essential information describing the binary +/// structure of the data segment of the WAVE file, such as sample +/// rate, sample binary format, channel count, etc. +/// +/// +/// ## Resources +/// +/// ### Implementation of Wave format `fmt` chunk +/// - [MSDN WAVEFORMATEX](https://docs.microsoft.com/en-us/windows/win32/api/mmeapi/ns-mmeapi-waveformatex) +/// - [MSDN WAVEFORMATEXTENSIBLE](https://docs.microsoft.com/en-us/windows/win32/api/mmreg/ns-mmreg-waveformatextensible) +/// +/// ### Other resources +/// - [RFC 3261][rfc3261] (June 1998) "WAVE and AVI Codec Registries" +/// - [Sampler Metadata](http://www.piclist.com/techref/io/serial/midi/wave.html) +/// - [Peter Kabal, McGill University](http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html) +/// - [Multimedia Programming Interface and Data Specifications 1.0](http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/Docs/riffmci.pdf) +/// (August 1991), IBM Corporation and Microsoft Corporation +/// +/// [rfc3261]: https://tools.ietf.org/html/rfc2361 + + #[derive(Debug, Copy, Clone)] pub struct WaveFmt { diff --git a/src/levl.rs b/src/levl.rs new file mode 100644 index 0000000..aa511e3 --- /dev/null +++ b/src/levl.rs @@ -0,0 +1,3 @@ +/// Resources +/// +/// [EBU 3285 Supplement 3](https://tech.ebu.ch/docs/tech/tech3285s3.pdf) (July 2001): Peak Metadata \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index b1aebf2..4a92710 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,10 +2,6 @@ # bwavfile Rust Wave File Reader/Writer with Broadcast-WAV, MBWF and RF64 Support - -__(Note: This crate is still in an alpha or pre-alpha stage of development. Reading of -files works however the interfaces may change significantly. Stay up-to-date on the -status of this project at [Github][github].)__ ## Objectives and Roadmap @@ -19,79 +15,6 @@ Apps we test against: - FFMpeg - Audacity -Wave features we want to support with maximum reliability and ease of use: - -- Large file size, RF64 support -- Multichannel audio formats -- Embedded metadata - -In addition to reading the audio, we want to support all of the different -metadata planes you are liable to need to use. - -- Broadcast-WAV metadata (including the SMPTE UMID and EBU v2 extensions) -- iXML Production recorder metadata -- ADM XML (with associated `chna` mappings) -- Dolby metadata block - -Things that are _not_ necessarily in the scope of this package: - -- Broad codec support. There are a little more than one-hundred - [registered wave codecs][rfc3261], but because this library is targeting - professional formats being created today, we only plan on supporting - two of them: tag 0x0001 (Integer Linear PCM) and tag 0x0003 (IEEE Float - Linear PCM). -- Music library metadata. There are several packages that can read ID3 - metadata and it's not particuarly common in wave files in any case. INFO - metadata is more common though in professional applications it tends not - to be used by many applications. - - -## Resources - -### Implementation of Broadcast Wave Files -- [EBU Tech 3285][ebu3285] (May 2011), "Specification of the Broadcast Wave Format (BWF)" - - [Supplement 1](https://tech.ebu.ch/docs/tech/tech3285s1.pdf) (July 1997): MPEG Audio -- [EBU Rec 68](https://tech.ebu.ch/docs/r/r068.pdf): Signal modulation and format constraints - - -### Implementation of 64-bit Wave Files -- [ITU-R 2088][itu2088] (October 2019), "Long-form file format for the international exchange of audio programme materials with metadata" - - Presently in force, adopted by the EBU in [EBU Tech 3306v2][ebu3306v2] (June 2018). -- [EBU Tech 3306v1][ebu3306v1] (July 2009), "MBWF / RF64: An extended File Format for Audio" - - No longer in force, however long-established. - - -### Implementation of Wave format `fmt` chunk -- [MSDN WAVEFORMATEX](https://docs.microsoft.com/en-us/windows/win32/api/mmeapi/ns-mmeapi-waveformatex) -- [MSDN WAVEFORMATEXTENSIBLE](https://docs.microsoft.com/en-us/windows/win32/api/mmreg/ns-mmreg-waveformatextensible) - - -### Other resources -- [RFC 3261][rfc3261] (June 1998) "WAVE and AVI Codec Registries" -- [Sampler Metadata](http://www.piclist.com/techref/io/serial/midi/wave.html) -- [Cue list, label and other metadata](https://sites.google.com/site/musicgapi/technical-documents/wav-file-format#smpl) -- [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) - (August 1991), IBM Corporation and Microsoft Corporation - - -### Formatting of Specific Metadatums -- [iXML Metadata Specification](http://www.gallery.co.uk/ixml/) (April 2019) -- EBU 3285 Supplements: - - [Supplement 2](https://tech.ebu.ch/docs/tech/tech3285s2.pdf) (July 2001): Quality chunk and cuesheet - - [Supplement 3](https://tech.ebu.ch/docs/tech/tech3285s3.pdf) (July 2001): Peak Metadata - - [Supplement 4](https://tech.ebu.ch/docs/tech/tech3285s4.pdf) (April 2003): Link Metadata - - [Supplement 5](https://tech.ebu.ch/docs/tech/tech3285s5.pdf) (May 2018): ADM Metadata - - [Supplement 6](https://tech.ebu.ch/docs/tech/tech3285s6.pdf) (October 2009): Dolby Metadata -- [EBU Tech R099](https://tech.ebu.ch/docs/r/r099.pdf) (October 2011) "‘Unique’ Source Identifier (USID) for use in the - field of the Broadcast Wave Format" -- [EBU Tech R098](https://tech.ebu.ch/docs/r/r098.pdf) (1999) "Format for the <CodingHistory> field in Broadcast Wave Format files, BWF" - -[ebu3285]: https://tech.ebu.ch/docs/tech/tech3285.pdf -[ebu3306v1]: https://tech.ebu.ch/docs/tech/tech3306v1_1.pdf -[ebu3306v2]: https://tech.ebu.ch/docs/tech/tech3306.pdf -[itu2088]: https://www.itu.int/dms_pubrec/itu-r/rec/bs/R-REC-BS.2088-1-201910-I!!PDF-E.pdf -[rfc3261]: https://tools.ietf.org/html/rfc2361 [github]: https://github.com/iluvcapra/bwavfile */ diff --git a/src/sampler.rs b/src/sampler.rs new file mode 100644 index 0000000..044eb2c --- /dev/null +++ b/src/sampler.rs @@ -0,0 +1,2 @@ +/// ## Resources +/// - [Sampler Metadata](http://www.piclist.com/techref/io/serial/midi/wave.html) diff --git a/src/wavereader.rs b/src/wavereader.rs index 0331fc4..33464e1 100644 --- a/src/wavereader.rs +++ b/src/wavereader.rs @@ -15,27 +15,53 @@ use std::io::Cursor; use std::io::{Read, Seek}; -/** - * Wave, Broadcast-WAV and RF64/BW64 parser/reader. - * - * ``` - * use bwavfile::WaveReader; - * let mut r = WaveReader::open("tests/media/ff_silence.wav").unwrap(); - * - * let format = r.format().unwrap(); - * assert_eq!(format.sample_rate, 44100); - * assert_eq!(format.channel_count, 1); - * - * let mut frame_reader = r.audio_frame_reader().unwrap(); - * let mut buffer = format.create_frame_buffer(); - * - * let read = frame_reader.read_integer_frame(&mut buffer).unwrap(); - * - * assert_eq!(buffer, [0i32]); - * assert_eq!(read, 1); - * - * ``` -*/ + +/// Wave, Broadcast-WAV and RF64/BW64 parser/reader. +/// +/// ``` +/// use bwavfile::WaveReader; +/// let mut r = WaveReader::open("tests/media/ff_silence.wav").unwrap(); +/// +/// let format = r.format().unwrap(); +/// assert_eq!(format.sample_rate, 44100); +/// assert_eq!(format.channel_count, 1); +/// +/// let mut frame_reader = r.audio_frame_reader().unwrap(); +/// let mut buffer = format.create_frame_buffer(); +/// +/// let read = frame_reader.read_integer_frame(&mut buffer).unwrap(); +/// +/// assert_eq!(buffer, [0i32]); +/// assert_eq!(read, 1); +/// +/// ``` +/// +/// ## Resources +/// +/// ### Implementation of Wave Files +/// - [Peter Kabal, McGill University](http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html) +/// - [Multimedia Programming Interface and Data Specifications 1.0](http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/Docs/riffmci.pdf) +/// (August 1991), IBM Corporation and Microsoft Corporation +/// +/// ### Implementation of Broadcast Wave Files +/// - [EBU Tech 3285][ebu3285] (May 2011), "Specification of the Broadcast Wave Format (BWF)" +/// - [Supplement 1](https://tech.ebu.ch/docs/tech/tech3285s1.pdf) (July 1997): MPEG Audio +/// - [EBU Rec 68](https://tech.ebu.ch/docs/r/r068.pdf): Signal modulation and format constraints +/// +/// ### Implementation of 64-bit Wave Files +/// - [ITU-R 2088][itu2088] (October 2019), "Long-form file format for the international exchange of audio programme materials with metadata" +/// - Presently in force, adopted by the EBU in [EBU Tech 3306v2][ebu3306v2] (June 2018). +/// - [EBU Tech 3306v1][ebu3306v1] (July 2009), "MBWF / RF64: An extended File Format for Audio" +/// - No longer in force, however long-established. +/// +/// +/// [ebu3285]: https://tech.ebu.ch/docs/tech/tech3285.pdf +/// [ebu3306v1]: https://tech.ebu.ch/docs/tech/tech3306v1_1.pdf +/// [ebu3306v2]: https://tech.ebu.ch/docs/tech/tech3306.pdf +/// [itu2088]: https://www.itu.int/dms_pubrec/itu-r/rec/bs/R-REC-BS.2088-1-201910-I!!PDF-E.pdf +/// [rfc3261]: https://tools.ietf.org/html/rfc2361 + + #[derive(Debug)] pub struct WaveReader { pub inner: R, diff --git a/src/wavewriter.rs b/src/wavewriter.rs index 151f9c9..dc5f89a 100644 --- a/src/wavewriter.rs +++ b/src/wavewriter.rs @@ -164,7 +164,32 @@ impl Write for WaveChunkWriter where W: Write + Seek { /// frame_writer.write_integer_frames(&[0i32]).unwrap(); /// frame_writer.write_integer_frames(&[0i32]).unwrap(); /// frame_writer.end().unwrap(); -/// ``` +/// ``` +/// +/// ## Resources +/// +/// ### Implementation of Wave Files +/// - [Peter Kabal, McGill University](http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html) +/// - [Multimedia Programming Interface and Data Specifications 1.0](http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/Docs/riffmci.pdf) +/// (August 1991), IBM Corporation and Microsoft Corporation +/// +/// ### Implementation of Broadcast Wave Files +/// - [EBU Tech 3285][ebu3285] (May 2011), "Specification of the Broadcast Wave Format (BWF)" +/// - [Supplement 1](https://tech.ebu.ch/docs/tech/tech3285s1.pdf) (July 1997): MPEG Audio +/// - [EBU Rec 68](https://tech.ebu.ch/docs/r/r068.pdf): Signal modulation and format constraints +/// +/// ### Implementation of 64-bit Wave Files +/// - [ITU-R 2088][itu2088] (October 2019), "Long-form file format for the international exchange of audio programme materials with metadata" +/// - Presently in force, adopted by the EBU in [EBU Tech 3306v2][ebu3306v2] (June 2018). +/// - [EBU Tech 3306v1][ebu3306v1] (July 2009), "MBWF / RF64: An extended File Format for Audio" +/// - No longer in force, however long-established. +/// +/// +/// [ebu3285]: https://tech.ebu.ch/docs/tech/tech3285.pdf +/// [ebu3306v1]: https://tech.ebu.ch/docs/tech/tech3306v1_1.pdf +/// [ebu3306v2]: https://tech.ebu.ch/docs/tech/tech3306.pdf +/// [itu2088]: https://www.itu.int/dms_pubrec/itu-r/rec/bs/R-REC-BS.2088-1-201910-I!!PDF-E.pdf +/// [rfc3261]: https://tools.ietf.org/html/rfc2361 pub struct WaveWriter where W: Write + Seek { inner : W, form_length: u64, diff --git a/tests/Untitled.txt b/tests/Untitled.txt deleted file mode 100644 index a2ea4d2..0000000 --- a/tests/Untitled.txt +++ /dev/null @@ -1,5 +0,0 @@ -Marker file version: 1 -Time format: Time -Marker 1 00:00:00.28417234 Marker 1 Comment -Marker 2 00:00:00.47612245 Marker 2 Comment -Timed Region 00:00:00.60569161 00:00:00.75226757 Region Comment From 5e4c2c7da9de4f34ea33431b4517a6775a852072 Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Tue, 29 Dec 2020 12:17:31 -0800 Subject: [PATCH 07/21] Doc --- src/lib.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 4a92710..41236cf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,23 @@ Rust Wave File Reader/Writer with Broadcast-WAV, MBWF and RF64 Support +## Interfaces + +### `WaveReader` + +`WaveReader` can open and parse a Wave, Broadcast-Wave, or RF64/BW64 64-bit +wave file. Metadata can be accessed and parsed in arbitrary order and audio +samples can be accessed using the `AudioFrameReader` type, created by an +accessor method of `WaveReader`. + +### `WaveWriter` + +`WaveWriter` can create a new Wave, Broadcast-Wave, or RF64/BW64 64-bit wave +file. Metadata chunks and audio samples are added sequentially, write-only, to +a Wave file which is automatically promoted from standard Wave to RF64 wave +when the total WAVE form size exceeds 0xFFFFFFFF bytes. + + ## Objectives and Roadmap This package aims to support read and writing any kind of WAV file you are likely From 78ad1ae1140bb058f08fdde944435726bc73ec1c Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Tue, 29 Dec 2020 13:03:20 -0800 Subject: [PATCH 08/21] More docs, improved bext writer interface --- src/wavewriter.rs | 69 ++++++++++++++++++++++++++++++----------------- 1 file changed, 44 insertions(+), 25 deletions(-) diff --git a/src/wavewriter.rs b/src/wavewriter.rs index dc5f89a..3247c34 100644 --- a/src/wavewriter.rs +++ b/src/wavewriter.rs @@ -33,6 +33,11 @@ impl AudioFrameWriter where W: Write + Seek { } /// Write interleaved samples in `buffer` + /// + /// # Panics + /// + /// This function will panic if `buffer.len()` modulo the Wave file's channel count + /// is not zero. pub fn write_integer_frames(&mut self, buffer: &[i32]) -> Result { let mut write_buffer = vec![0u8; 0]; @@ -237,36 +242,32 @@ impl WaveWriter where W: Write + Seek { Ok( retval ) } - fn promote_to_rf64(&mut self) -> Result<(), std::io::Error> { - if !self.is_rf64 { - self.inner.seek(SeekFrom::Start(0))?; - self.inner.write_fourcc(RF64_SIG)?; - self.inner.write_u32::(0xFFFF_FFFF)?; - self.inner.seek(SeekFrom::Start(12))?; - - self.inner.write_fourcc(DS64_SIG)?; - self.inner.seek(SeekFrom::Current(4))?; - self.inner.write_u64::(self.form_length)?; - self.is_rf64 = true; + fn write_chunk(&mut self, ident: FourCC, data : &[u8]) -> Result<(),Error> { + self.inner.seek(SeekFrom::End(0))?; + self.inner.write_fourcc(ident)?; + assert!(data.len() < u32::MAX as usize); + self.inner.write_u32::(data.len() as u32)?; + self.inner.write(data)?; + if data.len() % 2 == 0 { + self.increment_form_length(data.len() as u64)?; + } else { + self.inner.write(&[0u8])?; + self.increment_form_length(data.len() as u64 + 1)?; } Ok(()) } - - fn chunk(mut self, ident: FourCC) -> Result,Error> { - self.inner.seek(SeekFrom::End(0))?; - WaveChunkWriter::begin(self, ident) - } - /// Write Broadcast-Wave metadata to the file. /// /// This function will write the metadata chunk immediately to the end of /// the file; if you have already written and closed the audio data the /// bext chunk will be positioned after it. - fn write_broadcast_metadata(self, bext: &Bext) -> Result { - let mut b = self.chunk(BEXT_SIG)?; - b.write_bext(bext)?; - Ok(b.end()?) + pub fn write_broadcast_metadata(&mut self, bext: &Bext) -> Result<(),Error> { + let mut c = Cursor::new(vec![0u8; 0]); + c.write_bext(&bext)?; + let buf = c.into_inner(); + self.write_chunk(BEXT_SIG, &buf )?; + Ok(()) } /// Create an audio frame writer, which takes possession of the callee @@ -288,6 +289,26 @@ impl WaveWriter where W: Write + Seek { Ok( AudioFrameWriter::new(inner) ) } + fn chunk(mut self, ident: FourCC) -> Result,Error> { + self.inner.seek(SeekFrom::End(0))?; + WaveChunkWriter::begin(self, ident) + } + + fn promote_to_rf64(&mut self) -> Result<(), std::io::Error> { + if !self.is_rf64 { + self.inner.seek(SeekFrom::Start(0))?; + self.inner.write_fourcc(RF64_SIG)?; + self.inner.write_u32::(0xFFFF_FFFF)?; + self.inner.seek(SeekFrom::Start(12))?; + + self.inner.write_fourcc(DS64_SIG)?; + self.inner.seek(SeekFrom::Current(4))?; + self.inner.write_u64::(self.form_length)?; + self.is_rf64 = true; + } + Ok(()) + } + fn increment_form_length(&mut self, amount: u64) -> Result<(), std::io::Error> { self.form_length = self.form_length + amount; if self.is_rf64 { @@ -302,7 +323,6 @@ impl WaveWriter where W: Write + Seek { } Ok(()) } - } #[test] @@ -385,7 +405,7 @@ fn test_write_bext() { let mut cursor = Cursor::new(vec![0u8;0]); let format = WaveFmt::new_pcm_mono(48000, 24); - let w = WaveWriter::new(&mut cursor, format).unwrap(); + let mut w = WaveWriter::new(&mut cursor, format).unwrap(); let bext = Bext { description: String::from("Test description"), @@ -404,7 +424,7 @@ fn test_write_bext() { coding_history: String::from(""), }; - let w = w.write_broadcast_metadata(&bext).unwrap(); + w.write_broadcast_metadata(&bext).unwrap(); let mut frame_writer = w.audio_frame_writer().unwrap(); @@ -421,7 +441,6 @@ fn test_write_bext() { //#[test] fn test_create_rf64() { - use std::io::Cursor; use super::fourcc::ReadFourCC; use byteorder::ReadBytesExt; From 84942a4186e219935f6b1a223019022e93a977d6 Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Tue, 29 Dec 2020 14:26:34 -0800 Subject: [PATCH 09/21] Implented AXML/iXML writing functions --- src/fourcc.rs | 2 ++ src/wavereader.rs | 9 ++++----- src/wavewriter.rs | 13 ++++++++++++- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/fourcc.rs b/src/fourcc.rs index 53486e9..6ea2c31 100644 --- a/src/fourcc.rs +++ b/src/fourcc.rs @@ -103,6 +103,8 @@ pub const FMT__SIG: FourCC = FourCC::make(b"fmt "); pub const BEXT_SIG: FourCC = FourCC::make(b"bext"); //pub const FACT_SIG: FourCC = FourCC::make(b"fact"); +pub const IXML_SIG: FourCC = FourCC::make(b"iXML"); +pub const AXML_SIG: FourCC = FourCC::make(b"axml"); pub const JUNK_SIG: FourCC = FourCC::make(b"JUNK"); pub const FLLR_SIG: FourCC = FourCC::make(b"FLLR"); diff --git a/src/wavereader.rs b/src/wavereader.rs index 33464e1..4849ca2 100644 --- a/src/wavereader.rs +++ b/src/wavereader.rs @@ -3,7 +3,8 @@ use std::io::SeekFrom; use std::fs::File; 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}; +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; @@ -250,8 +251,7 @@ impl WaveReader { /// If there are no iXML metadata present in the file, /// Ok(0) will be returned. pub fn read_ixml(&mut self, buffer: &mut Vec) -> Result { - let ixml_fourcc = FourCC::make(b"iXML"); - self.read_chunk(ixml_fourcc, 0, buffer) + self.read_chunk(IXML_SIG, 0, buffer) } /// Read AXML data. @@ -262,8 +262,7 @@ impl WaveReader { /// If there are no axml metadata present in the file, /// Ok(0) will be returned pub fn read_axml(&mut self, buffer: &mut Vec) -> Result { - let axml_fourcc = FourCC::make(b"axml"); - self.read_chunk(axml_fourcc, 0, buffer) + self.read_chunk(AXML_SIG, 0, buffer) } diff --git a/src/wavewriter.rs b/src/wavewriter.rs index 3247c34..8fed11f 100644 --- a/src/wavewriter.rs +++ b/src/wavewriter.rs @@ -3,7 +3,8 @@ use std::io::{Write,Seek,SeekFrom,Cursor}; 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}; + WAVE_SIG, FMT__SIG, DATA_SIG, ELM1_SIG, JUNK_SIG, BEXT_SIG,AXML_SIG, + IXML_SIG}; use super::fmt::WaveFmt; //use super::common_format::CommonFormat; use super::chunks::WriteBWaveChunks; @@ -270,6 +271,16 @@ impl WaveWriter where W: Write + Seek { Ok(()) } + /// Write iXML metadata + pub fn write_ixml(&mut self, ixml: &[u8]) -> Result<(),Error> { + self.write_chunk(IXML_SIG, &ixml) + } + + /// Write axml/ADM metadata + pub fn write_axml(&mut self, axml: &[u8]) -> Result<(), Error> { + self.write_chunk(AXML_SIG, &axml) + } + /// Create an audio frame writer, which takes possession of the callee /// `WaveWriter`. /// From f63d6279c3f8ffdf02c2c1a713dfbd959f989a8c Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Tue, 29 Dec 2020 14:27:13 -0800 Subject: [PATCH 10/21] Update README.md iXML/AXML writing support noted --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 171cbec..512edea 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ This is currently a work-in-progress! However many features presently work: | Channel/speaker map metadata | ☑️ | ☑️ | | Ambisonic B-format metadata | ☑️ | ☑️ | | EBU Broadcast-WAVE metadata | ☑️ | ☑️ | -| Basic iXML/ADM metadata | ☑️ | | +| Basic iXML/ADM metadata | ☑️ | ☑️ | | Enhanced iXML metadata support | | | | Broadcast-WAVE Level overview `levl` metadata | | | | Cue list metadata | ☑️ | | From 41977adb832cb6a6b58551ce61634e928cb83a3e Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Tue, 29 Dec 2020 14:38:05 -0800 Subject: [PATCH 11/21] Fixed a bug in write_chunk --- src/wavewriter.rs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/wavewriter.rs b/src/wavewriter.rs index 8fed11f..7065ccf 100644 --- a/src/wavewriter.rs +++ b/src/wavewriter.rs @@ -232,9 +232,7 @@ impl WaveWriter where W: Write + Seek { retval.increment_form_length(4)?; - let mut chunk = retval.chunk(JUNK_SIG)?; - chunk.write(&[0u8; 96])?; - let retval = chunk.end()?; + retval.write_junk(96)?; let mut chunk = retval.chunk(FMT__SIG)?; chunk.write_wave_fmt(&format)?; @@ -250,10 +248,10 @@ impl WaveWriter where W: Write + Seek { self.inner.write_u32::(data.len() as u32)?; self.inner.write(data)?; if data.len() % 2 == 0 { - self.increment_form_length(data.len() as u64)?; + self.increment_form_length(8 + data.len() as u64)?; } else { self.inner.write(&[0u8])?; - self.increment_form_length(data.len() as u64 + 1)?; + self.increment_form_length(8 + data.len() as u64 + 1)?; } Ok(()) } @@ -281,10 +279,15 @@ impl WaveWriter where W: Write + Seek { self.write_chunk(AXML_SIG, &axml) } + /// Write a `JUNK` filler chunk + pub fn write_junk(&mut self, length: u32) -> Result<(), Error> { + let filler = vec![0u8; length as usize]; + self.write_chunk(JUNK_SIG, &filler) + } + /// Create an audio frame writer, which takes possession of the callee /// `WaveWriter`. - /// - /// + /// pub fn audio_frame_writer(mut self) -> Result, Error> { // append elm1 chunk From 7ea7ac5ce766fecb791853be00f77c93e3b9cf17 Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Tue, 29 Dec 2020 14:42:11 -0800 Subject: [PATCH 12/21] DS64 constant --- src/wavewriter.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/wavewriter.rs b/src/wavewriter.rs index 7065ccf..025221d 100644 --- a/src/wavewriter.rs +++ b/src/wavewriter.rs @@ -207,6 +207,8 @@ pub struct WaveWriter where W: Write + Seek { pub format: WaveFmt } +const DS64_RESERVATION_LENGTH : u32 = 96; + impl WaveWriter { /// Create a new Wave file at `path`. @@ -232,7 +234,8 @@ impl WaveWriter where W: Write + Seek { retval.increment_form_length(4)?; - retval.write_junk(96)?; + // write ds64_reservation + retval.write_junk(DS64_RESERVATION_LENGTH)?; let mut chunk = retval.chunk(FMT__SIG)?; chunk.write_wave_fmt(&format)?; From dfaf55955da10dd9dffc3685633cf3a55c72795c Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Tue, 29 Dec 2020 21:10:50 -0800 Subject: [PATCH 13/21] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 512edea..a3dab5c 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ This is currently a work-in-progress! However many features presently work: | EBU Broadcast-WAVE metadata | ☑️ | ☑️ | | Basic iXML/ADM metadata | ☑️ | ☑️ | | Enhanced iXML metadata support | | | +| ADM `chna` channel metadata | | | | Broadcast-WAVE Level overview `levl` metadata | | | | Cue list metadata | ☑️ | | | Sampler and instrument metadata | | | From aca56558bcb864e2b9b021774d495bb7ddbaffea Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Thu, 31 Dec 2020 14:19:57 -0800 Subject: [PATCH 14/21] Added examples --- examples/blits/main.rs | 0 examples/bwavefile_probe/main.rs | 0 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 examples/blits/main.rs create mode 100644 examples/bwavefile_probe/main.rs diff --git a/examples/blits/main.rs b/examples/blits/main.rs new file mode 100644 index 0000000..e69de29 diff --git a/examples/bwavefile_probe/main.rs b/examples/bwavefile_probe/main.rs new file mode 100644 index 0000000..e69de29 From daeb69c08c796f5024a925a12196ce422e4dd086 Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Thu, 31 Dec 2020 14:22:31 -0800 Subject: [PATCH 15/21] added main functions --- examples/blits/main.rs | 3 +++ examples/bwavefile_probe/main.rs | 3 +++ 2 files changed, 6 insertions(+) diff --git a/examples/blits/main.rs b/examples/blits/main.rs index e69de29..ea7c778 100644 --- a/examples/blits/main.rs +++ b/examples/blits/main.rs @@ -0,0 +1,3 @@ +fn main() -> () { + +} \ No newline at end of file diff --git a/examples/bwavefile_probe/main.rs b/examples/bwavefile_probe/main.rs index e69de29..ea7c778 100644 --- a/examples/bwavefile_probe/main.rs +++ b/examples/bwavefile_probe/main.rs @@ -0,0 +1,3 @@ +fn main() -> () { + +} \ No newline at end of file From ef8a3adf6927c5b90c990aca378e09e0c3e5ed14 Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Thu, 31 Dec 2020 15:31:55 -0800 Subject: [PATCH 16/21] Removed example folders --- examples/{blits/main.rs => blits.rs} | 0 examples/{bwavefile_probe/main.rs => bwavefile_probe.rs} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename examples/{blits/main.rs => blits.rs} (100%) rename examples/{bwavefile_probe/main.rs => bwavefile_probe.rs} (100%) diff --git a/examples/blits/main.rs b/examples/blits.rs similarity index 100% rename from examples/blits/main.rs rename to examples/blits.rs diff --git a/examples/bwavefile_probe/main.rs b/examples/bwavefile_probe.rs similarity index 100% rename from examples/bwavefile_probe/main.rs rename to examples/bwavefile_probe.rs From 1f56e0f3800ffd414591ec79cbd145c99891842e Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Fri, 1 Jan 2021 11:59:28 -0800 Subject: [PATCH 17/21] Documentation --- src/wavewriter.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/wavewriter.rs b/src/wavewriter.rs index 7065ccf..9cfd28a 100644 --- a/src/wavewriter.rs +++ b/src/wavewriter.rs @@ -303,11 +303,13 @@ impl WaveWriter where W: Write + Seek { Ok( AudioFrameWriter::new(inner) ) } + /// Open a wave chunk writer here fn chunk(mut self, ident: FourCC) -> Result,Error> { self.inner.seek(SeekFrom::End(0))?; WaveChunkWriter::begin(self, ident) } + /// Upgrade this file to RF64 fn promote_to_rf64(&mut self) -> Result<(), std::io::Error> { if !self.is_rf64 { self.inner.seek(SeekFrom::Start(0))?; @@ -323,6 +325,7 @@ impl WaveWriter where W: Write + Seek { Ok(()) } + /// Add `amount` to the RIFF/RF64 form length fn increment_form_length(&mut self, amount: u64) -> Result<(), std::io::Error> { self.form_length = self.form_length + amount; if self.is_rf64 { From 28b927245669feccd75d119d8e22f8dadc71c293 Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Fri, 1 Jan 2021 12:09:26 -0800 Subject: [PATCH 18/21] Updated doucmentation to proper comment style --- src/fmt.rs | 1 + src/wavereader.rs | 210 ++++++++++++++++++++++------------------------ 2 files changed, 100 insertions(+), 111 deletions(-) diff --git a/src/fmt.rs b/src/fmt.rs index db1fc43..6d0040e 100644 --- a/src/fmt.rs +++ b/src/fmt.rs @@ -5,6 +5,7 @@ use std::io::Cursor; use byteorder::LittleEndian; use byteorder::WriteBytesExt; +// Need more test cases for ADMAudioID #[allow(dead_code)] /// ADM Audio ID record. diff --git a/src/wavereader.rs b/src/wavereader.rs index 4849ca2..5e85411 100644 --- a/src/wavereader.rs +++ b/src/wavereader.rs @@ -69,12 +69,11 @@ pub struct WaveReader { } impl WaveReader { - /** - * Open a file for reading. - * - * A convenience that opens `path` and calls `Self::new()` - * - */ + + /// Open a file for reading. + /// + /// A convenience that opens `path` and calls `Self::new()` + pub fn open(path: &str) -> Result { let inner = File::open(path)?; return Ok( Self::new(inner)? ) @@ -82,48 +81,45 @@ impl WaveReader { } impl WaveReader { - /** - * Wrap a `Read` struct in a new `WaveReader`. - * - * This is the primary entry point into the `WaveReader` interface. The - * stream passed as `inner` must be at the beginning of the header of the - * WAVE data. For a .wav file, this means it must be at the start of the - * file. - * - * This function does a minimal validation on the provided stream and - * will return an `Err(errors::Error)` immediately if there is a structural - * inconsistency that makes the stream unreadable or if it's missing - * essential components that make interpreting the audio data impossible. - * - * ```rust - * use std::fs::File; - * use std::io::{Error,ErrorKind}; - * use bwavfile::{WaveReader, Error as WavError}; - * - * let f = File::open("tests/media/error.wav").unwrap(); - * - * let reader = WaveReader::new(f); - * - * match reader { - * Ok(_) => panic!("error.wav should not be openable"), - * Err( WavError::IOError( e ) ) => { - * assert_eq!(e.kind(), ErrorKind::UnexpectedEof) - * } - * Err(e) => panic!("Unexpected error was returned {:?}", e) - * } - * - * ``` - * - */ + + /// Wrap a `Read` struct in a new `WaveReader`. + /// + /// This is the primary entry point into the `WaveReader` interface. The + /// stream passed as `inner` must be at the beginning of the header of the + /// WAVE data. For a .wav file, this means it must be at the start of the + /// file. + /// + /// This function does a minimal validation on the provided stream and + /// will return an `Err(errors::Error)` immediately if there is a structural + /// inconsistency that makes the stream unreadable or if it's missing + /// essential components that make interpreting the audio data impossible. + + /// ```rust + /// use std::fs::File; + /// use std::io::{Error,ErrorKind}; + /// use bwavfile::{WaveReader, Error as WavError}; + /// + /// let f = File::open("tests/media/error.wav").unwrap(); + /// + /// let reader = WaveReader::new(f); + /// + /// match reader { + /// Ok(_) => panic!("error.wav should not be openable"), + /// Err( WavError::IOError( e ) ) => { + /// assert_eq!(e.kind(), ErrorKind::UnexpectedEof) + /// } + /// Err(e) => panic!("Unexpected error was returned {:?}", e) + /// } + /// + /// ``` pub fn new(inner: R) -> Result { let mut retval = Self { inner }; retval.validate_readable()?; Ok(retval) } - /** - * Unwrap the inner reader. - */ + + /// Unwrap the inner reader. pub fn into_inner(self) -> R { return self.inner; } @@ -137,9 +133,8 @@ impl WaveReader { 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 { let (_, data_length ) = self.get_chunk_extent_at_index(DATA_SIG, 0)?; let format = self.format()?; @@ -155,7 +150,6 @@ impl WaveReader { self.inner.read_wave_fmt() } - /// /// The Broadcast-WAV metadata record for this file, if present. /// pub fn broadcast_extension(&mut self) -> Result, ParserError> { @@ -285,35 +279,33 @@ impl WaveReader { } } - /** - * Validate minimal WAVE file. - * - * `Ok(())` if the source is `validate_readable()` AND - * - * - Contains _only_ a `fmt` chunk and `data` chunk, with no other chunks present - * - `fmt` chunk is exactly 16 bytes long and begins _exactly_ at file offset 12 - * - `data` content begins _exactly_ at file offset 36 - * - is not an RF64/BW64 - * - * Some clients require a WAVE file to only contain format and data without any other - * metadata and this function is provided to validate this condition. - * - * ### Examples - * - * ``` - * # use bwavfile::WaveReader; - * - * let mut w = WaveReader::open("tests/media/ff_minimal.wav").unwrap(); - * w.validate_minimal().expect("Minimal wav did not validate not minimal!"); - * ``` - * - * ``` - * # use bwavfile::WaveReader; - * - * let mut x = WaveReader::open("tests/media/pt_24bit_51.wav").unwrap(); - * x.validate_minimal().expect_err("Complex WAV validated minimal!"); - * ``` - */ + /// Validate minimal WAVE file. + /// + /// `Ok(())` if the source is `validate_readable()` AND + /// + /// - Contains _only_ a `fmt` chunk and `data` chunk, with no other chunks present + /// - `fmt` chunk is exactly 16 bytes long and begins _exactly_ at file offset 12 + /// - `data` content begins _exactly_ at file offset 36 + /// - is not an RF64/BW64 + /// + /// Some clients require a WAVE file to only contain format and data without any other + /// metadata and this function is provided to validate this condition. + /// + /// ### Examples + /// + /// ``` + /// # use bwavfile::WaveReader; + /// + /// let mut w = WaveReader::open("tests/media/ff_minimal.wav").unwrap(); + /// w.validate_minimal().expect("Minimal wav did not validate not minimal!"); + /// ``` + /// + /// ``` + /// # use bwavfile::WaveReader; + /// + /// let mut x = WaveReader::open("tests/media/pt_24bit_51.wav").unwrap(); + /// x.validate_minimal().expect_err("Complex WAV validated minimal!"); + /// ``` pub fn validate_minimal(&mut self) -> Result<(), ParserError> { self.validate_readable()?; @@ -327,39 +319,37 @@ impl WaveReader { } } - /** - * Validate Broadcast-WAVE file format - * - * Returns `Ok(())` if `validate_readable()` and file contains a - * Broadcast-WAV metadata record (a `bext` chunk). - * - * ### Examples - * - * ``` - * # use bwavfile::WaveReader; - * - * let mut w = WaveReader::open("tests/media/ff_bwav_stereo.wav").unwrap(); - * w.validate_broadcast_wave().expect("BWAVE file did not validate BWAVE"); - * - * let mut x = WaveReader::open("tests/media/pt_24bit.wav").unwrap(); - * x.validate_broadcast_wave().expect("BWAVE file did not validate BWAVE"); - * - * let mut y = WaveReader::open("tests/media/audacity_16bit.wav").unwrap(); - * y.validate_broadcast_wave().expect_err("Plain WAV file DID validate BWAVE"); - * ``` - */ + /// Validate Broadcast-WAVE file format + /// + /// Returns `Ok(())` if `validate_readable()` and file contains a + /// Broadcast-WAV metadata record (a `bext` chunk). + /// + /// ### Examples + /// + /// ``` + /// # use bwavfile::WaveReader; + /// + /// let mut w = WaveReader::open("tests/media/ff_bwav_stereo.wav").unwrap(); + /// w.validate_broadcast_wave().expect("BWAVE file did not validate BWAVE"); + /// + /// let mut x = WaveReader::open("tests/media/pt_24bit.wav").unwrap(); + /// x.validate_broadcast_wave().expect("BWAVE file did not validate BWAVE"); + /// + /// let mut y = WaveReader::open("tests/media/audacity_16bit.wav").unwrap(); + /// y.validate_broadcast_wave().expect_err("Plain WAV file DID validate BWAVE"); + /// ``` + /// pub fn validate_broadcast_wave(&mut self) -> Result<(), ParserError> { self.validate_readable()?; let (_, _) = self.get_chunk_extent_at_index(BEXT_SIG, 0)?; Ok(()) } - /** - * Verify data is aligned to a block boundary. - * - * Returns `Ok(())` if `validate_readable()` and the start of the - * `data` chunk's content begins at 0x4000. - */ + /// + /// Verify data is aligned to a block boundary. + /// + /// Returns `Ok(())` if `validate_readable()` and the start of the + /// `data` chunk's content begins at 0x4000. pub fn validate_data_chunk_alignment(&mut self) -> Result<() , ParserError> { self.validate_readable()?; let (start, _) = self.get_chunk_extent_at_index(DATA_SIG, 0)?; @@ -370,15 +360,13 @@ impl WaveReader { } } - /** - * Verify audio data can be appended immediately to this file. - * - * Returns `Ok(())` if: - * - `validate_readable()` - * - there is a `JUNK` or `FLLR` immediately at the beginning of the chunk - * list adequately large enough to be overwritten by a `ds64` (92 bytes) - * - `data` is the final chunk - */ + /// Verify audio data can be appended immediately to this file. + /// + /// Returns `Ok(())` if: + /// - `validate_readable()` + /// - there is a `JUNK` or `FLLR` immediately at the beginning of the chunk + /// list adequately large enough to be overwritten by a `ds64` (92 bytes) + /// - `data` is the final chunk pub fn validate_prepared_for_append(&mut self) -> Result<(), ParserError> { self.validate_readable()?; From a855410d6f91df70556dfaa1666f5a998e8406bc Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Fri, 1 Jan 2021 12:14:06 -0800 Subject: [PATCH 19/21] Added rf64 test back in ...and will push to github see if the action completes --- src/wavewriter.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wavewriter.rs b/src/wavewriter.rs index 381eec9..350c190 100644 --- a/src/wavewriter.rs +++ b/src/wavewriter.rs @@ -459,7 +459,7 @@ fn test_write_bext() { // NOTE! This test of RF64 writing passes on my machine but because it takes // nearly 5 mins to run I have omitted it from the source for now... -//#[test] +#[test] fn test_create_rf64() { use super::fourcc::ReadFourCC; use byteorder::ReadBytesExt; From 1cb7174861081debb6fef6cb5b2f028aba3ff624 Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Fri, 1 Jan 2021 12:14:51 -0800 Subject: [PATCH 20/21] Comment --- src/wavewriter.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/wavewriter.rs b/src/wavewriter.rs index 350c190..bd2987e 100644 --- a/src/wavewriter.rs +++ b/src/wavewriter.rs @@ -456,9 +456,7 @@ fn test_write_bext() { } -// NOTE! This test of RF64 writing passes on my machine but because it takes -// nearly 5 mins to run I have omitted it from the source for now... - +// NOTE! This test of RF64 writing takes several minutes to complete. #[test] fn test_create_rf64() { use super::fourcc::ReadFourCC; From e14bcd8c7676eb983ba49445e96e3babf05af33e Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Fri, 1 Jan 2021 12:26:16 -0800 Subject: [PATCH 21/21] Update version --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4da1bf3..a8f99dd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,7 +2,7 @@ # It is not intended for manual editing. [[package]] name = "bwavfile" -version = "0.9.1" +version = "0.9.2" dependencies = [ "byteorder", "encoding", diff --git a/Cargo.toml b/Cargo.toml index 93ed821..6e53999 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bwavfile" -version = "0.9.1" +version = "0.9.2" authors = ["Jamie Hardt "] edition = "2018" license = "MIT"