From 02c950ed6eaf8ea8ecdf21da2c5f71ffb91f706f Mon Sep 17 00:00:00 2001 From: Kevin Clark Date: Fri, 9 Aug 2024 18:35:53 -0700 Subject: [PATCH 1/3] Replace anyhow with embedded_error_chain Lib is now no-alloc --- Cargo.toml | 2 +- examples/beep_imaadpcm.rs | 2 +- examples/beep_imaadpcm_stereo.rs | 2 +- src/error.rs | 27 +++++++++++++ src/imaadpcm.rs | 32 +++++++--------- src/lib.rs | 65 +++++++++++++++++--------------- src/wav.rs | 13 +++---- 7 files changed, 84 insertions(+), 59 deletions(-) create mode 100644 src/error.rs diff --git a/Cargo.toml b/Cargo.toml index 3bd62af..7253d76 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,11 +12,11 @@ edition = "2021" rust-version = "1.79.0" [dependencies] -anyhow = { version = "1.0.86", default-features = false } arbitrary-int = { version = "1.2.7", default-features = false } fixed = "1.23.1" heapless = "0.8.0" nom = { version = "7.1.3", default-features = false } +embedded-error-chain = "1.0.0" [dev-dependencies] cpal = "0.15.2" diff --git a/examples/beep_imaadpcm.rs b/examples/beep_imaadpcm.rs index 2421a1e..03b89fb 100644 --- a/examples/beep_imaadpcm.rs +++ b/examples/beep_imaadpcm.rs @@ -34,7 +34,7 @@ fn main() { } } Err(e) => { - println!("{e}"); + println!("{e:?}"); let _result = complete_tx.try_send(()); } } diff --git a/examples/beep_imaadpcm_stereo.rs b/examples/beep_imaadpcm_stereo.rs index be1a103..b809736 100644 --- a/examples/beep_imaadpcm_stereo.rs +++ b/examples/beep_imaadpcm_stereo.rs @@ -38,7 +38,7 @@ fn main() { } } Err(e) => { - println!("{e}"); + println!("{e:?}"); let _result = complete_tx.try_send(()); } } diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..cf5ab88 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,27 @@ +use embedded_error_chain::ErrorCategory; + +#[derive(Clone, Copy, ErrorCategory)] +#[repr(u8)] +pub enum DecodingError { + UnknownFormat, + UnsupportedBitDepth, + UnsupportedFormat +} + + +#[derive(Clone, Copy, ErrorCategory)] +#[error_category(links(DecodingError))] +#[repr(u8)] +pub enum ReaderError { + InvalidChannel, + InvalidSample, + DecodingError +} + +#[derive(Clone, Copy, ErrorCategory)] +#[repr(u8)] +pub enum PlayerError { + InvalidOutputBufferLength, + InvalidData, + FinishedPlaying +} \ No newline at end of file diff --git a/src/imaadpcm.rs b/src/imaadpcm.rs index aa41eff..796b7e5 100644 --- a/src/imaadpcm.rs +++ b/src/imaadpcm.rs @@ -16,13 +16,11 @@ //! } //! ``` -use crate::{AudioFormat, PcmReader, PcmSpecs}; -use anyhow::ensure; +use crate::{error, AudioFormat, PcmReader, PcmSpecs}; use arbitrary_int::u4; // use fixed::types::I1F15; use heapless::spsc::Queue; use nom::bits::{bits, complete::take}; -use nom::error::Error; use nom::number::complete::{le_i16, le_i8, le_u8}; use nom::sequence::tuple; use nom::IResult; @@ -119,11 +117,11 @@ fn compute_step_size(nibble: u4, mut step_size_table_index: i8) -> i8 { pub(crate) fn calc_num_samples_per_channel( data_chunk_size_in_bytes: u32, spec: &PcmSpecs, -) -> anyhow::Result { - ensure!( - spec.audio_format == AudioFormat::ImaAdpcmLe, - "IMA-ADPCM only" - ); +) -> Result { + if spec.audio_format != AudioFormat::ImaAdpcmLe { + return Err(error::DecodingError::UnsupportedFormat); + } + let num_block_align = spec.ima_adpcm_num_block_align.unwrap() as u32; let num_samples_per_block = spec.ima_adpcm_num_samples_per_block.unwrap() as u32; let num_blocks = data_chunk_size_in_bytes / num_block_align; @@ -163,20 +161,18 @@ impl<'a> ImaAdpcmPlayer<'a> { /// Return samples value of the next frame. /// * 'out' - Output buffer which the sample values are written. Number of elements must be equal to or greater than the number of channels in the PCM file. - pub fn get_next_frame(&mut self, out: &mut [I1F15]) -> anyhow::Result<()> { + pub fn get_next_frame(&mut self, out: &mut [I1F15]) -> Result<(), error::PlayerError> { let num_channels = self.reader.specs.num_channels; // outバッファーのチャンネル数が不足 - ensure!( - out.len() >= num_channels as usize, - "Number of elements in \"out\" must be greater than or equal to the number of IMA-ADPCM channels" - ); + if !(out.len() >= num_channels as usize) { + return Err(error::PlayerError::InvalidData); + } // 再生終了 - ensure!( - self.frame_index < self.reader.specs.num_samples, - "Played to the end." - ); + if ! (self.frame_index < self.reader.specs.num_samples) { + return Err(error::PlayerError::FinishedPlaying); + } //IMA-ADPCMのBlock切り替わりかどうか判定 if self.reading_block.is_empty() && self.nibble_queue[0].is_empty() { @@ -257,7 +253,7 @@ type DataWordNibbles = (u8, u8, u8, u8, u8, u8, u8, u8); /// IMA-ADPCMのBlockのData word(32bit長)を8つのnibble(4bit長)にパースする. fn parse_data_word(input: &[u8]) -> IResult<&[u8], DataWordNibbles> { - bits::<_, _, Error<(&[u8], usize)>, _, _>(tuple(( + bits::<_, _, nom::error::Error<(&[u8], usize)>, _, _>(tuple(( take(4usize), take(4usize), take(4usize), diff --git a/src/lib.rs b/src/lib.rs index cd6b395..78ae55d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,10 +26,10 @@ //! ``` #![cfg_attr(not(test), no_std)] -use anyhow::{bail, ensure}; use core::f32; +use embedded_error_chain::prelude::*; use heapless::Vec; -use nom::error::Error; +use nom::error::Error as NomError; use nom::number::complete::{ be_f32, be_f64, be_i16, be_i24, be_i32, le_f32, le_f64, le_i16, le_i24, le_i32, }; @@ -37,6 +37,7 @@ use nom::Finish; use nom::{multi::fold_many1, IResult}; mod aiff; +mod error; pub mod imaadpcm; mod wav; @@ -218,15 +219,20 @@ impl<'a> PcmReader<'a> { /// Returns the value of a sample at an arbitrary position. /// Returns a normalized value in the range +/-1.0 regardless of AudioFormat. - pub fn read_sample(&self, channel: u32, sample: u32) -> anyhow::Result { - ensure!(channel < self.specs.num_channels as u32, "Invalid channel"); - ensure!(sample < self.specs.num_samples, "Invalid sample"); + pub fn read_sample(&self, channel: u32, sample: u32) -> Result> { + if !(channel < self.specs.num_channels as u32) { + return Err(error::ReaderError::InvalidChannel)?; + } + + if !(sample < self.specs.num_samples) { + return Err(error::ReaderError::InvalidSample)?; + } let byte_depth = self.specs.bit_depth as u32 / 8u32; let byte_offset = ((byte_depth * sample * self.specs.num_channels as u32) + (byte_depth * channel)) as usize; let data = &self.data[byte_offset..]; - decode_sample(&self.specs, data) + Ok(decode_sample(&self.specs, data).chain_err(error::ReaderError::DecodingError)?) } } @@ -235,90 +241,88 @@ impl<'a> PcmReader<'a> { /// TODO return not only f32 but also Q15, Q23, f64, etc. /// Or make it possible to select f32 or f64. /// It may be better to use a function like read_raw_sample() to get fixed-point numbers. -fn decode_sample(specs: &PcmSpecs, data: &[u8]) -> anyhow::Result { +fn decode_sample(specs: &PcmSpecs, data: &[u8]) -> Result { match specs.audio_format { AudioFormat::Unknown => { - bail!("Unknown audio format"); + return Err(error::DecodingError::UnknownFormat); } AudioFormat::LinearPcmLe => { match specs.bit_depth { 16 => { const MAX: u32 = 2u32.pow(15); //normalize factor: 2^(BitDepth-1) - let (_remains, sample) = le_i16::<_, Error<_>>(data).finish().unwrap(); + let (_remains, sample) = le_i16::<_, NomError<_>>(data).finish().unwrap(); let sample = sample as f32 / MAX as f32; Ok(sample) } 24 => { const MAX: u32 = 2u32.pow(23); //normalize factor: 2^(BitDepth-1) - let (_remains, sample) = le_i24::<_, Error<_>>(data).finish().unwrap(); + let (_remains, sample) = le_i24::<_, NomError<_>>(data).finish().unwrap(); let sample = sample as f32 / MAX as f32; Ok(sample) } 32 => { const MAX: u32 = 2u32.pow(31); //normalize factor: 2^(BitDepth-1) - let (_remains, sample) = le_i32::<_, Error<_>>(data).finish().unwrap(); + let (_remains, sample) = le_i32::<_, NomError<_>>(data).finish().unwrap(); let sample = sample as f32 / MAX as f32; Ok(sample) } - _ => bail!("Unsupported bit-depth"), + _ => return Err(error::DecodingError::UnsupportedBitDepth), } } AudioFormat::LinearPcmBe => { match specs.bit_depth { 16 => { const MAX: u32 = 2u32.pow(15); //normalize factor: 2^(BitDepth-1) - let (_remains, sample) = be_i16::<_, Error<_>>(data).finish().unwrap(); + let (_remains, sample) = be_i16::<_, NomError<_>>(data).finish().unwrap(); let sample = sample as f32 / MAX as f32; Ok(sample) } 24 => { const MAX: u32 = 2u32.pow(23); //normalize factor: 2^(BitDepth-1) - let (_remains, sample) = be_i24::<_, Error<_>>(data).finish().unwrap(); + let (_remains, sample) = be_i24::<_, NomError<_>>(data).finish().unwrap(); let sample = sample as f32 / MAX as f32; Ok(sample) } 32 => { const MAX: u32 = 2u32.pow(31); //normalize factor: 2^(BitDepth-1) - let (_remains, sample) = be_i32::<_, Error<_>>(data).finish().unwrap(); + let (_remains, sample) = be_i32::<_, NomError<_>>(data).finish().unwrap(); let sample = sample as f32 / MAX as f32; Ok(sample) } - _ => bail!("Unsupported bit-depth"), + _ => return Err(error::DecodingError::UnsupportedBitDepth), } } AudioFormat::IeeeFloatLe => { match specs.bit_depth { 32 => { //32bit float - let (_remains, sample) = le_f32::<_, Error<_>>(data).finish().unwrap(); + let (_remains, sample) = le_f32::<_, NomError<_>>(data).finish().unwrap(); Ok(sample) } 64 => { //64bit float - let (_remains, sample) = le_f64::<_, Error<_>>(data).finish().unwrap(); + let (_remains, sample) = le_f64::<_, NomError<_>>(data).finish().unwrap(); Ok(sample as f32) // TODO f32にダウンキャストするべきなのか検討 } - _ => bail!("Unsupported bit-depth"), + _ => return Err(error::DecodingError::UnsupportedBitDepth), } } AudioFormat::IeeeFloatBe => { match specs.bit_depth { 32 => { //32bit float - let (_remains, sample) = be_f32::<_, Error<_>>(data).finish().unwrap(); + let (_remains, sample) = be_f32::<_, NomError<_>>(data).finish().unwrap(); Ok(sample) } 64 => { //64bit float - let (_remains, sample) = be_f64::<_, Error<_>>(data).finish().unwrap(); + let (_remains, sample) = be_f64::<_, NomError<_>>(data).finish().unwrap(); Ok(sample as f32) // TODO f32にダウンキャストするべきなのか検討 } - _ => bail!("Unsupported bit-depth"), + _ => return Err(error::DecodingError::UnsupportedBitDepth), } } - AudioFormat::ImaAdpcmLe => { - bail!("IMA-ADPCM is not supported in decode_sample(). Use ImaAdpcmPlayer.") - } + AudioFormat::ImaAdpcmLe => return Err(error::DecodingError::UnsupportedFormat), } } @@ -360,19 +364,18 @@ impl<'a> PcmPlayer<'a> { /// Return samples value of the next frame. /// * ‘out’ - Output buffer which the sample values are written. Number of elements must be equal to or greater than the number of channels in the PCM file. - pub fn get_next_frame(&mut self, out: &mut [f32]) -> anyhow::Result<()> { + pub fn get_next_frame(&mut self, out: &mut [f32]) -> Result<(), error::PlayerError> { let byte_depth = self.reader.specs.bit_depth / 8; - ensure!( - out.len() >= self.reader.specs.num_channels as usize, - "Invalid output buffer length" - ); + if !(out.len() >= self.reader.specs.num_channels as usize) { + return Err(error::PlayerError::InvalidOutputBufferLength); + } if self.reading_data.is_empty() { if self.loop_playing { self.set_position(0); } else { - bail!("Finished playing"); + return Err(error::PlayerError::FinishedPlaying); } } diff --git a/src/wav.rs b/src/wav.rs index 4c4d06d..5911872 100644 --- a/src/wav.rs +++ b/src/wav.rs @@ -1,5 +1,4 @@ -use crate::{AudioFormat, PcmSpecs}; -use anyhow::ensure; +use crate::{error, AudioFormat, PcmSpecs}; use core::convert::TryInto; use nom::bytes::complete::{tag, take}; use nom::number::complete::{le_u16, le_u32}; @@ -175,11 +174,11 @@ pub(super) fn parse_fmt(input: &[u8]) -> IResult<&[u8], WavFmtSpecs> { pub(super) fn calc_num_samples_per_channel( data_chunk_size_in_bytes: u32, spec: &PcmSpecs, -) -> anyhow::Result { - ensure!( - spec.audio_format != AudioFormat::ImaAdpcmLe, - "IMA-ADPCM is not supported in calc_num_samples_per_channel" - ); +) -> Result { + if spec.audio_format == AudioFormat::ImaAdpcmLe { + return Err(error::DecodingError::UnsupportedFormat); + } + Ok(data_chunk_size_in_bytes / (spec.bit_depth / 8u16 * spec.num_channels) as u32) } From 77517bbe6d8a9a12d7c63bd7da3365958c8ad159 Mon Sep 17 00:00:00 2001 From: Kevin Clark Date: Fri, 9 Aug 2024 18:39:28 -0700 Subject: [PATCH 2/3] Bump version to 0.5.0 to reflect API changes --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 7253d76..bb9872d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pacmog" -version = "0.4.2" +version = "0.5.0" authors = ["Akiyuki Okayasu "] description = "PCM decording library" categories = ["multimedia::audio", "no-std", "embedded", "multimedia::encoding"] From c74e9719b05973629b1cf784381135eecc08d861 Mon Sep 17 00:00:00 2001 From: Kevin Clark Date: Fri, 9 Aug 2024 18:48:41 -0700 Subject: [PATCH 3/3] Run cargo fmt. --- src/error.rs | 9 ++++----- src/imaadpcm.rs | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/error.rs b/src/error.rs index cf5ab88..8202e4a 100644 --- a/src/error.rs +++ b/src/error.rs @@ -5,17 +5,16 @@ use embedded_error_chain::ErrorCategory; pub enum DecodingError { UnknownFormat, UnsupportedBitDepth, - UnsupportedFormat + UnsupportedFormat, } - #[derive(Clone, Copy, ErrorCategory)] #[error_category(links(DecodingError))] #[repr(u8)] pub enum ReaderError { InvalidChannel, InvalidSample, - DecodingError + DecodingError, } #[derive(Clone, Copy, ErrorCategory)] @@ -23,5 +22,5 @@ pub enum ReaderError { pub enum PlayerError { InvalidOutputBufferLength, InvalidData, - FinishedPlaying -} \ No newline at end of file + FinishedPlaying, +} diff --git a/src/imaadpcm.rs b/src/imaadpcm.rs index 796b7e5..2912c60 100644 --- a/src/imaadpcm.rs +++ b/src/imaadpcm.rs @@ -170,7 +170,7 @@ impl<'a> ImaAdpcmPlayer<'a> { } // 再生終了 - if ! (self.frame_index < self.reader.specs.num_samples) { + if !(self.frame_index < self.reader.specs.num_samples) { return Err(error::PlayerError::FinishedPlaying); }