use crate::{AudioFormat, PcmReaderError, PcmSpecs};
use winnow::binary::{le_u16, le_u32};
use winnow::token::{literal, take};
use winnow::{ModalResult, Parser};
#[derive(Debug, PartialEq, Default)]
pub(super) enum ChunkId {
Fmt, Fact, Peak, Data, Junk,
List,
IDv3,
#[default]
Unknown,
}
impl TryFrom<&[u8]> for ChunkId {
type Error = ();
fn try_from(v: &[u8]) -> Result<Self, Self::Error> {
if v.len() != 4 {
return Err(());
}
match v {
b"fmt " => Ok(ChunkId::Fmt),
b"fact" => Ok(ChunkId::Fact),
b"PEAK" => Ok(ChunkId::Peak),
b"data" => Ok(ChunkId::Data),
b"junk" => Ok(ChunkId::Junk),
b"JUNK" => Ok(ChunkId::Junk),
b"IDv3" => Ok(ChunkId::IDv3),
b"LIST" => Ok(ChunkId::List),
_ => Ok(ChunkId::Unknown),
}
}
}
#[derive(Debug, Default)]
pub(super) struct Chunk<'a> {
pub id: ChunkId,
#[allow(dead_code)]
pub size: u32,
pub data: &'a [u8],
}
#[derive(Debug, PartialEq)]
enum WaveFormatTag {
LinearPcm = 0x01, IeeeFloat = 0x03, ImaAdpcm = 0x11, }
impl TryFrom<u16> for WaveFormatTag {
type Error = ();
fn try_from(v: u16) -> Result<Self, Self::Error> {
match v {
x if x == WaveFormatTag::LinearPcm as u16 => Ok(WaveFormatTag::LinearPcm),
x if x == WaveFormatTag::IeeeFloat as u16 => Ok(WaveFormatTag::IeeeFloat),
x if x == WaveFormatTag::ImaAdpcm as u16 => Ok(WaveFormatTag::ImaAdpcm),
_ => Err(()),
}
}
}
#[derive(Debug)]
pub(super) struct RiffHeader {
pub size: u32,
}
pub(super) fn parse_riff_header(input: &mut &[u8]) -> ModalResult<RiffHeader> {
literal(b"RIFF").parse_next(input)?;
let size = le_u32.parse_next(input)?;
literal(b"WAVE").parse_next(input)?;
Ok(RiffHeader { size })
}
pub(super) fn parse_chunk<'a>(input: &mut &'a [u8]) -> ModalResult<Chunk<'a>> {
let id: ChunkId = take(4usize)
.map(|id: &'a [u8]| {
let id: ChunkId = id.try_into().unwrap();
id
})
.parse_next(input)?;
let size = le_u32.parse_next(input)?;
let data = take(size).parse_next(input)?;
Ok(Chunk { id, size, data })
}
#[derive(Debug, Default)]
pub(super) struct WavFmtSpecs {
pub audio_format: AudioFormat,
pub num_channels: u16,
pub sample_rate: u32,
pub bit_depth: u16,
pub ima_adpcm_num_block_align: Option<u16>,
pub ima_adpcm_num_samples_per_block: Option<u16>,
}
pub(super) fn parse_fmt(input: &mut &[u8]) -> ModalResult<WavFmtSpecs> {
let wave_format_tag = le_u16.parse_next(input)?;
let audio_format = match wave_format_tag.try_into().unwrap() {
WaveFormatTag::LinearPcm => AudioFormat::LinearPcmLe,
WaveFormatTag::IeeeFloat => AudioFormat::IeeeFloatLe,
WaveFormatTag::ImaAdpcm => AudioFormat::ImaAdpcmLe,
};
let num_channels = le_u16.parse_next(input)?;
let sample_rate = le_u32.parse_next(input)?;
let _bytes_per_seconds = le_u32.parse_next(input)?;
let block_size = le_u16
.verify(|block_size| {
match audio_format {
AudioFormat::ImaAdpcmLe => *block_size % 4 == 0,
_ => true,
}
})
.parse_next(input)?;
let bit_depth = le_u16.parse_next(input)?;
if audio_format == AudioFormat::ImaAdpcmLe {
let num_block_align = block_size;
let _cb_size = le_u16.verify(|cb_size| *cb_size == 2).parse_next(input)?;
let num_samples_per_block = le_u16
.verify(|num_samples_per_block| {
*num_samples_per_block
== ((num_block_align - (4 * num_channels)) * 8) / (bit_depth * num_channels) + 1
})
.parse_next(input)?;
return Ok(WavFmtSpecs {
audio_format,
num_channels,
sample_rate,
bit_depth,
ima_adpcm_num_block_align: Some(block_size),
ima_adpcm_num_samples_per_block: Some(num_samples_per_block),
});
}
Ok(WavFmtSpecs {
audio_format,
num_channels,
sample_rate,
bit_depth,
ima_adpcm_num_block_align: None,
ima_adpcm_num_samples_per_block: None,
})
}
pub(super) fn calc_num_samples_per_channel(
data_chunk_size_in_bytes: u32,
spec: &PcmSpecs,
) -> Result<u32, PcmReaderError> {
if spec.audio_format == AudioFormat::ImaAdpcmLe {
return Err(PcmReaderError::UnsupportedAudioFormat);
}
Ok(data_chunk_size_in_bytes / (spec.bit_depth / 8u16 * spec.num_channels) as u32)
}
#[cfg(test)]
mod tests {
use crate::{PcmSpecs, wav::ChunkId, wav::calc_num_samples_per_channel};
use super::WaveFormatTag;
#[test]
fn calc_num_samples() {
let spec = PcmSpecs {
audio_format: crate::AudioFormat::LinearPcmLe,
bit_depth: 16,
num_channels: 2,
..Default::default()
};
let n = calc_num_samples_per_channel(192000, &spec).unwrap();
assert_eq!(n, 48000);
let spec = PcmSpecs {
audio_format: crate::AudioFormat::ImaAdpcmLe,
bit_depth: 4,
num_channels: 1,
..Default::default()
};
let e = calc_num_samples_per_channel(2041, &spec);
assert!(e.is_err());
}
#[test]
fn wave_format_tag_test() {
let b = 0x01;
let tag: WaveFormatTag = b.try_into().unwrap();
assert_eq!(tag, WaveFormatTag::LinearPcm);
let b = 0x03;
let tag: WaveFormatTag = b.try_into().unwrap();
assert_eq!(tag, WaveFormatTag::IeeeFloat);
let b = 0x11;
let tag: WaveFormatTag = b.try_into().unwrap();
assert_eq!(tag, WaveFormatTag::ImaAdpcm);
let b = 0xFF;
let e: Result<WaveFormatTag, ()> = b.try_into();
assert_eq!(e, Err(()));
}
#[test]
fn chunk_id_test() {
let b = b"fmt ";
let chunk: ChunkId = b.as_slice().try_into().unwrap();
assert_eq!(chunk, ChunkId::Fmt);
let b = b"fact";
let chunk: ChunkId = b.as_slice().try_into().unwrap();
assert_eq!(chunk, ChunkId::Fact);
let b = b"PEAK";
let chunk: ChunkId = b.as_slice().try_into().unwrap();
assert_eq!(chunk, ChunkId::Peak);
let b = b"data";
let chunk: ChunkId = b.as_slice().try_into().unwrap();
assert_eq!(chunk, ChunkId::Data);
let b = b"JUNK";
let chunk: ChunkId = b.as_slice().try_into().unwrap();
assert_eq!(chunk, ChunkId::Junk);
let b = b"junk";
let chunk: ChunkId = b.as_slice().try_into().unwrap();
assert_eq!(chunk, ChunkId::Junk);
let b = b"IDv3";
let chunk: ChunkId = b.as_slice().try_into().unwrap();
assert_eq!(chunk, ChunkId::IDv3);
let b = b"LIST";
let chunk: ChunkId = b.as_slice().try_into().unwrap();
assert_eq!(chunk, ChunkId::List);
let b = b"HOGE";
let chunk: ChunkId = b.as_slice().try_into().unwrap();
assert_eq!(chunk, ChunkId::Unknown);
let b = b"FOO";
let e: Result<ChunkId, ()> = b.as_slice().try_into();
assert_eq!(e, Err(()));
}
}