From c2cff2a89328c7b9d0c6b158287526b43498b548 Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Sun, 3 Jan 2021 10:49:05 -0800 Subject: [PATCH 1/6] Consolidated AudioFrameWriter --- src/audio_frame_reader.rs | 109 -------------------------------------- src/lib.rs | 5 +- src/wavereader.rs | 109 +++++++++++++++++++++++++++++++++++++- tests/integration_test.rs | 3 +- 4 files changed, 110 insertions(+), 116 deletions(-) delete mode 100644 src/audio_frame_reader.rs diff --git a/src/audio_frame_reader.rs b/src/audio_frame_reader.rs deleted file mode 100644 index e9b13ef..0000000 --- a/src/audio_frame_reader.rs +++ /dev/null @@ -1,109 +0,0 @@ -use std::io::{Read, Seek}; -use std::io::SeekFrom::{Start,Current,}; - -use byteorder::LittleEndian; -use byteorder::ReadBytesExt; - -use super::fmt::{WaveFmt}; -use super::errors::Error; -use super::CommonFormat; - -/// Read audio frames -/// -/// The inner reader is interpreted as a raw audio data -/// bitstream having a format specified by `format`. -/// -#[derive(Debug)] -pub struct AudioFrameReader { - inner : R, - format: WaveFmt, - start: u64, - length: u64 -} - -impl AudioFrameReader { - - /// Create a new `AudioFrameReader` - /// - /// ### Panics - /// - /// This method does a few sanity checks on the provided format - /// parameter to confirm the `block_alignment` law is fulfilled - /// and the format tag is readable by this implementation (only - /// format 0x01 is supported at this time.) - pub fn new(mut inner: R, format: WaveFmt, start: u64, length: u64) -> Result { - assert!(format.block_alignment * 8 == format.bits_per_sample * format.channel_count, - "Unable to read audio frames from packed formats: block alignment is {}, should be {}", - format.block_alignment, (format.bits_per_sample / 8 ) * format.channel_count); - - assert!(format.common_format() == CommonFormat::IntegerPCM , - "Unsupported format tag {:?}", format.tag); - - inner.seek(Start(start))?; - Ok( AudioFrameReader { inner , format , start, length} ) - } - - /// Unwrap the inner reader. - pub fn into_inner(self) -> R { - self.inner - } - - /// Locate the read position to a different frame - /// - /// Seeks within the audio stream. - /// - /// Returns the new location of the read position. - /// - /// locate() behaves similarly to Read methods in that - /// seeking after the end of the audio data is not an error. - pub fn locate(&mut self, to :u64) -> Result { - let position = to * self.format.block_alignment as u64; - let seek_result = self.inner.seek(Start(self.start + position))?; - Ok( (seek_result - self.start) / self.format.block_alignment as u64 ) - } - - - /// Read a frame - /// - /// A single frame is read from the audio stream and the read location - /// is advanced one frame. - /// - /// Regardless of the number of bits in the audio sample, this method - /// always writes `i32` samples back to the buffer. These samples are - /// written back "right-aligned" so samples that are shorter than i32 - /// will leave the MSB bits empty. - /// - /// For example: A full-code sample in 16 bit (0xFFFF) will be written - /// back to the buffer as 0x0000FFFF. - /// - /// - /// ### Panics - /// - /// The `buffer` must have a number of elements equal to the number of - /// channels and this method will panic if this is not the case. - pub fn read_integer_frame(&mut self, buffer:&mut [i32]) -> Result { - assert!(buffer.len() as u16 == self.format.channel_count, - "read_integer_frame was called with a mis-sized buffer, expected {}, was {}", - self.format.channel_count, buffer.len()); - - let framed_bits_per_sample = self.format.block_alignment * 8 / self.format.channel_count; - - let tell = self.inner.seek(Current(0))?; - - if (tell - self.start) < self.length { - for n in 0..(self.format.channel_count as usize) { - 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) - } - } - Ok( 1 ) - } else { - Ok( 0 ) - } - } -} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 41236cf..2f2103f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -50,8 +50,6 @@ mod errors; mod common_format; mod parser; - -mod audio_frame_reader; mod list_form; mod chunks; @@ -63,10 +61,9 @@ mod wavereader; mod wavewriter; pub use errors::Error; -pub use wavereader::WaveReader; +pub use wavereader::{WaveReader, AudioFrameReader}; pub use wavewriter::{WaveWriter, AudioFrameWriter}; pub use bext::Bext; pub use fmt::{WaveFmt, WaveFmtExtended, ChannelDescriptor, ChannelMask, ADMAudioID}; pub use common_format::CommonFormat; -pub use audio_frame_reader::AudioFrameReader; pub use cue::Cue; \ No newline at end of file diff --git a/src/wavereader.rs b/src/wavereader.rs index b3ddac9..b92d7f3 100644 --- a/src/wavereader.rs +++ b/src/wavereader.rs @@ -8,14 +8,121 @@ use super::fourcc::{FourCC, ReadFourCC, FMT__SIG,DATA_SIG, BEXT_SIG, LIST_SIG, J use super::errors::Error as ParserError; use super::fmt::{WaveFmt, ChannelDescriptor, ChannelMask}; use super::bext::Bext; -use super::audio_frame_reader::AudioFrameReader; + use super::chunks::ReadBWaveChunks; use super::cue::Cue; use std::io::Cursor; use std::io::{Read, Seek, BufReader}; +use std::io::SeekFrom::{Start,Current,}; +use byteorder::LittleEndian; +use byteorder::ReadBytesExt; + +use super::errors::Error; +use super::CommonFormat; + + +/// Read audio frames +/// +/// The inner reader is interpreted as a raw audio data +/// bitstream having a format specified by `format`. +/// +#[derive(Debug)] +pub struct AudioFrameReader { + inner : R, + format: WaveFmt, + start: u64, + length: u64 +} + +impl AudioFrameReader { + + /// Create a new `AudioFrameReader` + /// + /// ### Panics + /// + /// This method does a few sanity checks on the provided format + /// parameter to confirm the `block_alignment` law is fulfilled + /// and the format tag is readable by this implementation (only + /// format 0x01 is supported at this time.) + pub fn new(mut inner: R, format: WaveFmt, start: u64, length: u64) -> Result { + assert!(format.block_alignment * 8 == format.bits_per_sample * format.channel_count, + "Unable to read audio frames from packed formats: block alignment is {}, should be {}", + format.block_alignment, (format.bits_per_sample / 8 ) * format.channel_count); + + assert!(format.common_format() == CommonFormat::IntegerPCM , + "Unsupported format tag {:?}", format.tag); + + inner.seek(Start(start))?; + Ok( AudioFrameReader { inner , format , start, length} ) + } + + /// Unwrap the inner reader. + pub fn into_inner(self) -> R { + self.inner + } + + /// Locate the read position to a different frame + /// + /// Seeks within the audio stream. + /// + /// Returns the new location of the read position. + /// + /// locate() behaves similarly to Read methods in that + /// seeking after the end of the audio data is not an error. + pub fn locate(&mut self, to :u64) -> Result { + let position = to * self.format.block_alignment as u64; + let seek_result = self.inner.seek(Start(self.start + position))?; + Ok( (seek_result - self.start) / self.format.block_alignment as u64 ) + } + + + /// Read a frame + /// + /// A single frame is read from the audio stream and the read location + /// is advanced one frame. + /// + /// Regardless of the number of bits in the audio sample, this method + /// always writes `i32` samples back to the buffer. These samples are + /// written back "right-aligned" so samples that are shorter than i32 + /// will leave the MSB bits empty. + /// + /// For example: A full-code sample in 16 bit (0xFFFF) will be written + /// back to the buffer as 0x0000FFFF. + /// + /// + /// ### Panics + /// + /// The `buffer` must have a number of elements equal to the number of + /// channels and this method will panic if this is not the case. + pub fn read_integer_frame(&mut self, buffer:&mut [i32]) -> Result { + assert!(buffer.len() as u16 == self.format.channel_count, + "read_integer_frame was called with a mis-sized buffer, expected {}, was {}", + self.format.channel_count, buffer.len()); + + let framed_bits_per_sample = self.format.block_alignment * 8 / self.format.channel_count; + + let tell = self.inner.seek(Current(0))?; + + if (tell - self.start) < self.length { + for n in 0..(self.format.channel_count as usize) { + 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) + } + } + Ok( 1 ) + } else { + Ok( 0 ) + } + } +} /// Wave, Broadcast-WAV and RF64/BW64 parser/reader. /// diff --git a/tests/integration_test.rs b/tests/integration_test.rs index dcbdd53..a8f9ca7 100644 --- a/tests/integration_test.rs +++ b/tests/integration_test.rs @@ -162,8 +162,7 @@ fn test_channels_stereo_no_fmt_extended() { #[test] fn test_frame_reader_consumes_reader() { // Issue #6 - use bwavfile::WaveFmt; - use bwavfile::AudioFrameReader; + use bwavfile::{WaveFmt, AudioFrameReader}; use std::fs::File; fn from_wav_filename(wav_filename: &str) -> Result<(WaveFmt, AudioFrameReader>), ()> { if let Ok(mut r) = WaveReader::open(&wav_filename) { From e941358c8d4a5a67639e852eb7c9682d4e53b407 Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Sun, 3 Jan 2021 11:05:02 -0800 Subject: [PATCH 2/6] reorganized use --- src/wavereader.rs | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/wavereader.rs b/src/wavereader.rs index b92d7f3..73a2bf1 100644 --- a/src/wavereader.rs +++ b/src/wavereader.rs @@ -1,27 +1,25 @@ -use std::io::SeekFrom; use std::fs::File; +use std::io::SeekFrom; +use std::io::Cursor; +use std::io::{Read, Seek, BufReader}; +use std::io::SeekFrom::{Start,Current,}; + 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::fourcc::{FourCC, ReadFourCC, FMT__SIG, DATA_SIG, BEXT_SIG, LIST_SIG, + JUNK_SIG, FLLR_SIG, CUE__SIG, ADTL_SIG, AXML_SIG, IXML_SIG}; use super::errors::Error as ParserError; use super::fmt::{WaveFmt, ChannelDescriptor, ChannelMask}; use super::bext::Bext; - use super::chunks::ReadBWaveChunks; use super::cue::Cue; - -use std::io::Cursor; -use std::io::{Read, Seek, BufReader}; - -use std::io::SeekFrom::{Start,Current,}; +use super::errors::Error; +use super::CommonFormat; use byteorder::LittleEndian; use byteorder::ReadBytesExt; -use super::errors::Error; -use super::CommonFormat; /// Read audio frames From 0a307fbf05331ca0314d1144d0adfa4f9245269c Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Sun, 3 Jan 2021 11:27:49 -0800 Subject: [PATCH 3/6] Removed Cue.ident field from interface --- src/cue.rs | 13 +++++++++++-- src/wavereader.rs | 3 --- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/cue.rs b/src/cue.rs index 9d42411..0bc4ba7 100644 --- a/src/cue.rs +++ b/src/cue.rs @@ -20,6 +20,15 @@ struct RawCue { } impl RawCue { + + fn write_to(cues : Vec) -> Vec { + let mut writer = Cursor::new(vec![0u8; 0]); + + + todo!() + + } + fn read_from(data : &[u8]) -> Result,Error> { let mut rdr = Cursor::new(data); let count = rdr.read_u32::()?; @@ -198,7 +207,7 @@ impl AdtlMemberSearch for Vec { pub struct Cue { /// Unique numeric identifier for this cue - pub ident : u32, + //pub ident : u32, /// The time of this marker pub frame : u32, @@ -236,7 +245,7 @@ impl Cue { raw_cues.iter() .map(|i| { Cue { - ident : i.cue_point_id, + //ident : i.cue_point_id, frame : i.frame, length: { raw_adtl.ltxt_for_cue_point(i.cue_point_id).first() diff --git a/src/wavereader.rs b/src/wavereader.rs index 73a2bf1..f583bba 100644 --- a/src/wavereader.rs +++ b/src/wavereader.rs @@ -320,19 +320,16 @@ impl WaveReader { /// let cue_points = f.cue_points().unwrap(); /// /// assert_eq!(cue_points.len(), 3); - /// assert_eq!(cue_points[0].ident, 1); /// assert_eq!(cue_points[0].frame, 12532); /// assert_eq!(cue_points[0].length, None); /// assert_eq!(cue_points[0].label, Some(String::from("Marker 1"))); /// assert_eq!(cue_points[0].note, Some(String::from("Marker 1 Comment"))); /// - /// assert_eq!(cue_points[1].ident, 2); /// assert_eq!(cue_points[1].frame, 20997); /// assert_eq!(cue_points[1].length, None); /// assert_eq!(cue_points[1].label, Some(String::from("Marker 2"))); /// assert_eq!(cue_points[1].note, Some(String::from("Marker 2 Comment"))); /// - /// assert_eq!(cue_points[2].ident, 3); /// assert_eq!(cue_points[2].frame, 26711); /// assert_eq!(cue_points[2].length, Some(6465)); /// assert_eq!(cue_points[2].label, Some(String::from("Timed Region"))); From e826628096098a732ec83c7188680beca16fe0c6 Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Sun, 3 Jan 2021 13:16:36 -0800 Subject: [PATCH 4/6] FIXME comments --- src/wavewriter.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/wavewriter.rs b/src/wavewriter.rs index 5e153fb..8a7932f 100644 --- a/src/wavewriter.rs +++ b/src/wavewriter.rs @@ -274,6 +274,7 @@ impl WaveWriter where W: Write + Seek { /// the file; if you have already written and closed the audio data the /// bext chunk will be positioned after it. pub fn write_broadcast_metadata(&mut self, bext: &Bext) -> Result<(),Error> { + //FIXME Implement re-writing let mut c = Cursor::new(vec![0u8; 0]); c.write_bext(&bext)?; let buf = c.into_inner(); @@ -283,11 +284,13 @@ impl WaveWriter where W: Write + Seek { /// Write iXML metadata pub fn write_ixml(&mut self, ixml: &[u8]) -> Result<(),Error> { + //FIXME Implement re-writing self.write_chunk(IXML_SIG, &ixml) } /// Write axml/ADM metadata pub fn write_axml(&mut self, axml: &[u8]) -> Result<(), Error> { + //FIXME Implement re-writing self.write_chunk(AXML_SIG, &axml) } From 814b54a6e80c0c4fb49771c9895dea0f70c6efdf Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Sun, 3 Jan 2021 13:16:47 -0800 Subject: [PATCH 5/6] Cue writing impl Needs tests --- src/cue.rs | 136 +++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 127 insertions(+), 9 deletions(-) diff --git a/src/cue.rs b/src/cue.rs index 0bc4ba7..9a84076 100644 --- a/src/cue.rs +++ b/src/cue.rs @@ -1,13 +1,14 @@ -use super::fourcc::{FourCC,ReadFourCC, LABL_SIG, NOTE_SIG, LTXT_SIG}; +use super::fourcc::{FourCC,ReadFourCC, WriteFourCC, LABL_SIG, NOTE_SIG, + ADTL_SIG, LTXT_SIG, DATA_SIG}; use super::list_form::collect_list_form; -use byteorder::{ReadBytesExt, LittleEndian}; +use byteorder::{WriteBytesExt, ReadBytesExt, LittleEndian}; -use encoding::{DecoderTrap}; +use encoding::{DecoderTrap,EncoderTrap}; use encoding::{Encoding}; use encoding::all::ASCII; -use std::io::{Cursor, Error, Read}; +use std::io::{Cursor, Error, Read, Write}; #[derive(Copy,Clone, Debug)] struct RawCue { @@ -24,9 +25,17 @@ impl RawCue { fn write_to(cues : Vec) -> Vec { let mut writer = Cursor::new(vec![0u8; 0]); - - todo!() + writer.write_u32::(cues.len() as u32).unwrap(); + for cue in cues.iter() { + writer.write_u32::(cue.cue_point_id).unwrap(); + writer.write_u32::(cue.frame).unwrap(); + writer.write_fourcc(cue.chunk_id).unwrap(); + writer.write_u32::(cue.chunk_start).unwrap(); + writer.write_u32::(cue.block_start).unwrap(); + writer.write_u32::(cue.frame_offset).unwrap(); + } + writer.into_inner() } fn read_from(data : &[u8]) -> Result,Error> { @@ -56,6 +65,14 @@ struct RawLabel { } impl RawLabel { + + fn write_to(&self) -> Vec { + let mut writer = Cursor::new(vec![0u8;0]); + writer.write_u32::(self.cue_point_id as u32).unwrap(); + writer.write(&self.text).unwrap(); + writer.into_inner() + } + fn read_from(data : &[u8]) -> Result { let mut rdr = Cursor::new(data); let length = data.len(); @@ -78,6 +95,14 @@ struct RawNote { } impl RawNote { + + fn write_to(&self) -> Vec { + let mut writer = Cursor::new(vec![0u8; 0]); + writer.write_u32::(self.cue_point_id).unwrap(); + writer.write(&self.text).unwrap(); + writer.into_inner() + } + fn read_from(data : &[u8]) -> Result { let mut rdr = Cursor::new(data); let length = data.len(); @@ -106,6 +131,22 @@ struct RawLtxt { } impl RawLtxt { + + fn write_to(&self) -> Vec { + let mut writer = Cursor::new(vec![0u8; 0]); + writer.write_u32::(self.cue_point_id).unwrap(); + writer.write_u32::(self.frame_length).unwrap(); + writer.write_fourcc(self.purpose).unwrap(); + writer.write_u16::(self.country).unwrap(); + writer.write_u16::(self.language).unwrap(); + writer.write_u16::(self.dialect).unwrap(); + writer.write_u16::(self.code_page).unwrap(); + if let Some(ext_text) = &self.text { + writer.write(ext_text).unwrap(); + } + writer.into_inner() + } + fn read_from(data : &[u8]) -> Result { let mut rdr = Cursor::new(data); let length = data.len(); @@ -140,6 +181,30 @@ enum RawAdtlMember { } impl RawAdtlMember { + fn compile_adtl(members : &[Self]) -> Vec { + let mut w = Cursor::new(vec![0u8; 0]); + // It seems like all this casing could be done with traits + for member in members.iter() { + let (fcc, buf) = match member { + RawAdtlMember::Label(l) => ( (LABL_SIG, l.write_to()) ), + RawAdtlMember::Note(n) => ( (NOTE_SIG, n.write_to()) ), + RawAdtlMember::LabeledText(t) => ( (LTXT_SIG, t.write_to()) ), + RawAdtlMember::Unrecognized(f) => (*f, vec![0u8;0] ) // <-- this is a dopey case but here for completeness + }; + w.write_fourcc(fcc).unwrap(); + w.write_u32::(buf.len() as u32).unwrap(); + w.write(&buf).unwrap(); + if buf.len() % 2 == 1 { w.write_u8(0).unwrap(); } + } + + let chunk_content = w.into_inner(); + let mut writer = Cursor::new(vec![0u8; 0]); + writer.write_fourcc(ADTL_SIG).unwrap(); + writer.write_u32::(chunk_content.len() as u32).unwrap(); + writer.write(&chunk_content).unwrap(); + writer.into_inner() + } + fn collect_from(chunk : &[u8]) -> Result,Error> { let chunks = collect_list_form(chunk)?; let mut retval : Vec = vec![]; @@ -206,9 +271,6 @@ impl AdtlMemberSearch for Vec { /// - [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 - //pub ident : u32, - /// The time of this marker pub frame : u32, @@ -228,8 +290,64 @@ fn convert_to_cue_string(buffer : &[u8]) -> String { ASCII.decode(&trimmed, DecoderTrap::Ignore).expect("Error decoding text") } +fn convert_from_cue_string(val : &str) -> Vec { + ASCII.encode(&val, EncoderTrap::Ignore).expect("Error encoding text") +} + impl Cue { + /// Take a list of `Cue`s and convert it into `RawCue` and `RawAdtlMember`s + fn compile_to(cues : &[Cue]) -> (Vec, Vec) { + cues.iter().enumerate() + .map(|(n, cue)| { + let raw_cue = RawCue { + cue_point_id: n as u32, + frame : cue.frame, + chunk_id: DATA_SIG, + chunk_start: 0, + block_start: 0, + frame_offset: cue.frame + }; + + let raw_label = cue.label.as_ref().map(|val| { + RawLabel { + cue_point_id : n as u32, + text: convert_from_cue_string(&val) + } + }); + + let raw_note = cue.note.as_ref().map(|val| { + RawNote { + cue_point_id: n as u32, + text : convert_from_cue_string(&val) + } + }); + + let raw_ltxt = cue.length.map(|val| { + RawLtxt { + cue_point_id : n as u32, + frame_length : val, + purpose : FourCC::make(b"rgn "), + country : 0, + language : 0, + dialect : 0, + code_page : 0, + text : None + } + }); + + (raw_cue, raw_label, raw_note, raw_ltxt) + }) + .fold((Vec::::new(), Vec::::new()), + |(mut cues, mut adtls) , (cue, label, note, ltxt)| { + cues.push(cue); + label.map(|l| adtls.push( RawAdtlMember::Label(l))); + note.map(|n| adtls.push(RawAdtlMember::Note(n))); + ltxt.map(|m| adtls.push(RawAdtlMember::LabeledText(m))); + (cues, adtls) + }) + } + pub fn collect_from(cue_chunk : &[u8], adtl_chunk : Option<&[u8]>) -> Result, Error> { let raw_cues = RawCue::read_from(cue_chunk)?; let raw_adtl : Vec; From 72d6be406e75ca0c40c134311f558cbec9c9f356 Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Sun, 3 Jan 2021 13:20:04 -0800 Subject: [PATCH 6/6] Twiddles --- examples/blits.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/examples/blits.rs b/examples/blits.rs index a988412..5d13375 100644 --- a/examples/blits.rs +++ b/examples/blits.rs @@ -250,8 +250,12 @@ fn main() -> io::Result<()> { ) .get_matches(); - let sample_rate = matches.value_of("sample_rate").unwrap().parse::().expect("Failed to read sample rate"); - let bits_per_sample = matches.value_of("bit_depth").unwrap().parse::().expect("Failed to read bit depth"); + let sample_rate = matches.value_of("sample_rate").unwrap().parse::() + .expect("Failed to read sample rate"); + + let bits_per_sample = matches.value_of("bit_depth").unwrap().parse::() + .expect("Failed to read bit depth"); + let filename = matches.value_of("OUTPUT").unwrap(); match create_blits_file(&filename, sample_rate, bits_per_sample) {