#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(not(feature = "std"))]
extern crate alloc;
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use binrw::{binrw, until_exclusive, BinRead, BinWrite};
#[cfg(not(feature = "std"))]
use binrw::io;
#[cfg(feature = "std")]
use std::io;
pub type Result<T> = core::result::Result<T, WaverlyError>;
#[derive(Debug)]
pub enum WaverlyError {
IoError(io::Error),
ParseError(binrw::Error),
}
impl From<io::Error> for WaverlyError {
fn from(error: io::Error) -> Self {
WaverlyError::IoError(error)
}
}
impl From<binrw::Error> for WaverlyError {
fn from(error: binrw::Error) -> Self {
WaverlyError::ParseError(error)
}
}
#[binrw]
#[derive(Debug)]
struct MyFile {
#[br(parse_with = until_exclusive(|byte| byte == &Chunk::Eof))]
chunks: Vec<Chunk>,
}
#[binrw]
#[derive(Debug, PartialEq)]
enum Chunk {
Riff(RiffChunk),
Format(FormatChunk),
Fact(FactChunk),
Peak(PeakChunk),
Data(DataChunk),
#[brw(magic = b"\0")]
Empty,
#[brw(magic = b"")]
Eof,
}
#[binrw]
#[brw(repr = u16)]
#[derive(Debug, PartialEq)]
pub enum WaveFormat {
Pcm = 0x01,
IeeeFloat = 0x03,
Alaw = 0x06,
Mulaw = 0x07,
Extensible = 0x08,
}
#[binrw]
#[brw(repr = u16)]
#[derive(Debug, PartialEq, Copy, Clone)]
pub enum BitDepth {
Eight = 0x08,
Sixteen = 0x10,
TwentyFour = 0x18,
ThirtyTwo = 0x20,
SixtyFour = 0x40,
}
#[binrw]
#[brw(magic = b"WAVEfmt ")]
#[derive(Debug, PartialEq)]
pub struct FormatChunk {
#[br(little)]
pub size: u32,
#[br(little)]
pub audio_format: WaveFormat,
#[br(little)]
pub num_channels: u16,
#[br(little)]
pub sample_rate: u32,
#[br(little)]
pub byte_rate: u32,
#[br(little)]
pub block_align: u16,
#[br(little)]
pub bits_per_sample: BitDepth,
#[br(little, if(audio_format == WaveFormat::Pcm))]
pub extensible: Option<ExtensibleFormat>,
}
#[binrw]
#[derive(Debug, PartialEq)]
pub struct ExtensibleFormat {
#[br(little)]
pub size: u16,
#[br(little)]
pub valid_bits_per_sample: u16,
#[br(little)]
pub channel_mask: u32,
#[br(little)]
pub sub_format_guid: [u8; 16],
}
#[binrw]
#[brw(magic = b"fact")]
#[derive(Debug, PartialEq)]
pub struct FactChunk {
#[br(little)]
pub size: u32,
#[br(little)]
pub data: u32,
}
#[binrw]
#[brw(magic = b"data")]
#[derive(Debug, PartialEq)]
pub struct DataChunk {
#[br(little)]
pub size: u32,
#[br(count = size)]
pub data: Vec<u8>,
}
#[binrw]
#[brw(magic = b"PEAK")]
#[derive(Debug, PartialEq)]
pub struct PeakChunk {
#[br(little)]
pub size: u32,
#[br(little)]
pub version: u32,
#[br(little)]
pub timestamp: u32,
#[br(count = 2)]
pub peaks: Vec<Peak>,
}
#[binrw]
#[derive(Clone, Debug, PartialEq)]
pub struct Peak {
#[br(little)]
pub value: f32,
#[br(little)]
pub position: u32,
}
#[binrw]
#[brw(magic = b"RIFF")]
#[derive(Debug, PartialEq)]
struct RiffChunk {
#[br(little)]
size: u32,
}
#[binrw]
#[derive(Debug, PartialEq)]
pub struct Wave {
riff: RiffChunk,
pub format: FormatChunk,
pub data: DataChunk,
pub fact: Option<FactChunk>,
pub peak: Option<PeakChunk>,
}
impl Wave {
pub fn from_reader<T: io::Seek + io::Read>(mut reader: T) -> Result<Wave> {
let my_file: MyFile = MyFile::read(&mut reader)?;
let mut riff = None;
let mut format = None;
let mut data = None;
let mut fact = None;
let mut peak = None;
for chunk in my_file.chunks {
match chunk {
Chunk::Riff(chunk) => riff = Some(chunk),
Chunk::Data(chunk) => data = Some(chunk),
Chunk::Format(chunk) => format = Some(chunk),
Chunk::Fact(chunk) => fact = Some(chunk),
Chunk::Peak(chunk) => peak = Some(chunk),
Chunk::Empty => (),
Chunk::Eof => (),
}
}
if riff == None {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"RIFF chunk was not found in file.",
)
.into());
}
if format == None {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"FORMAT chunk was not found in file.",
)
.into());
}
if data == None {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"DATA chunk was not found in file.",
)
.into());
}
let format = format.unwrap();
if format.audio_format != WaveFormat::Pcm && fact == None {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"FACT format is required for non-PCM WAV formats",
)
.into());
}
Ok(Wave {
riff: riff.unwrap(),
data: data.unwrap(),
format,
fact,
peak,
})
}
pub fn write<T: io::Seek + io::Write>(self, mut writer: T) -> Result<()> {
self.write_to(&mut writer)?;
Ok(())
}
}
#[cfg(test)]
mod tests {
extern crate std;
use super::*;
use std::fs;
use std::fs::File;
use std::io::Cursor;
#[cfg(feature = "std")]
#[test]
fn it_reads_format() -> Result<()> {
let file = File::open("./meta/16bit-2ch-float-peak.wav")?;
let wave: Wave = Wave::from_reader(file)?;
let f = &wave.format;
assert_eq!(f.sample_rate, 44100);
assert_eq!(f.bits_per_sample, BitDepth::SixtyFour);
assert_eq!(f.num_channels, 2);
assert_eq!(f.audio_format, WaveFormat::IeeeFloat);
let block_align = f.num_channels * (f.bits_per_sample as u16) / 8;
let byte_rate = f.sample_rate * block_align as u32;
assert_eq!(f.byte_rate, byte_rate);
assert_eq!(f.byte_rate, 705600);
assert_eq!(f.block_align, block_align);
assert_eq!(f.block_align, 16);
assert_eq!(f.extensible, None);
Ok(())
}
#[cfg(feature = "std")]
#[test]
fn it_writes_data_correctly() -> Result<()> {
let filename = "./meta/16bit-2ch-float-peak.wav";
let file = File::open(filename)?;
let wave: Wave = Wave::from_reader(file)?;
let metadata = fs::metadata(filename)?;
let mut virt_file = Cursor::new(Vec::new());
wave.write(&mut virt_file)?;
let buf = virt_file.into_inner();
assert_eq!(buf.len(), metadata.len() as usize);
assert_ne!(buf.len(), 0);
let buf_iter = buf.into_iter();
let riff_magic: Vec<u8> = buf_iter.take(4).collect();
assert_eq!([82, 73, 70, 70], riff_magic[..]);
Ok(())
}
}