Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make pacmog no-alloc #49

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "pacmog"
version = "0.4.2"
version = "0.5.0"
authors = ["Akiyuki Okayasu <[email protected]>"]
description = "PCM decording library"
categories = ["multimedia::audio", "no-std", "embedded", "multimedia::encoding"]
Expand All @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion examples/beep_imaadpcm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ fn main() {
}
}
Err(e) => {
println!("{e}");
println!("{e:?}");
let _result = complete_tx.try_send(());
}
}
Expand Down
2 changes: 1 addition & 1 deletion examples/beep_imaadpcm_stereo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ fn main() {
}
}
Err(e) => {
println!("{e}");
println!("{e:?}");
let _result = complete_tx.try_send(());
}
}
Expand Down
26 changes: 26 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
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,
}
32 changes: 14 additions & 18 deletions src/imaadpcm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<u32> {
ensure!(
spec.audio_format == AudioFormat::ImaAdpcmLe,
"IMA-ADPCM only"
);
) -> Result<u32, error::DecodingError> {
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;
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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),
Expand Down
65 changes: 34 additions & 31 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,18 @@
//! ```
#![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,
};
use nom::Finish;
use nom::{multi::fold_many1, IResult};

mod aiff;
mod error;
pub mod imaadpcm;
mod wav;

Expand Down Expand Up @@ -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<f32> {
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<f32, Error<error::ReaderError>> {
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)?)
}
}

Expand All @@ -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<f32> {
fn decode_sample(specs: &PcmSpecs, data: &[u8]) -> Result<f32, error::DecodingError> {
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),
}
}

Expand Down Expand Up @@ -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);
}
}

Expand Down
13 changes: 6 additions & 7 deletions src/wav.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down Expand Up @@ -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<u32> {
ensure!(
spec.audio_format != AudioFormat::ImaAdpcmLe,
"IMA-ADPCM is not supported in calc_num_samples_per_channel"
);
) -> Result<u32, error::DecodingError> {
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)
}

Expand Down
Loading