use super::{Result, *};
use crate::daq::RawStreamData;
use snafu::prelude::*;
#[derive(Debug, Clone)]
#[cfg_attr(feature = "python-bindings", gen_stub_pyclass, pyclass(from_py_object))]
pub struct WavInfo {
pub sample_rate: u32,
pub nchannels: usize,
pub bits_per_sample: u16,
pub sample_format: String,
pub format_str: String,
pub nframes: usize,
pub duration: f64,
}
#[cfg(feature = "python-bindings")]
#[cfg_attr(feature = "python-bindings", gen_stub_pymethods, pymethods)]
impl WavInfo {
#[getter]
fn sample_rate(&self) -> u32 {
self.sample_rate
}
#[getter]
fn nchannels(&self) -> usize {
self.nchannels
}
#[getter]
fn bits_per_sample(&self) -> u16 {
self.bits_per_sample
}
#[getter]
fn sample_format(&self) -> String {
self.sample_format.clone()
}
#[getter]
fn format_str(&self) -> String {
self.format_str.clone()
}
#[getter]
fn nframes(&self) -> usize {
self.nframes
}
#[getter]
fn duration(&self) -> f64 {
self.duration
}
}
#[cfg_attr(
feature = "python-bindings",
gen_stub_pyclass_enum,
pyclass(eq, eq_int, from_py_object)
)]
#[derive(Debug, Clone, Copy, PartialEq)]
#[repr(u32)]
pub enum FrameMismatchPolicy {
AppendZeros = 0,
PrependZeros = 1,
}
#[macro_export]
macro_rules! read_wav_samples {
($reader:expr, $spec_fmt:expr, $bits:expr) => {
match ($spec_fmt, $bits) {
(hound::SampleFormat::Int, 8) => {
let samples = $crate::measurement::wav_common::collect_wav_samples::<i8>($reader)?;
RawStreamData::new(samples)
}
(hound::SampleFormat::Int, 16) => {
let samples = $crate::measurement::wav_common::collect_wav_samples::<i16>($reader)?;
RawStreamData::new(samples)
}
(hound::SampleFormat::Int, 24) | (hound::SampleFormat::Int, 32) => {
let samples = $crate::measurement::wav_common::collect_wav_samples::<i32>($reader)?;
RawStreamData::new(samples)
}
(hound::SampleFormat::Float, 32) => {
let samples = $crate::measurement::wav_common::collect_wav_samples::<f32>($reader)?;
RawStreamData::new(samples)
}
(fmt, bits) => {
return WAVImportSnafu {
msg: format!("Unsupported WAV format: {fmt:?} with {bits} bits per sample"),
}
.fail();
}
}
};
}
#[macro_export]
macro_rules! read_and_interleave_wav_samples {
($readers:expr, $channels_per_file:expr, $frames_per_file:expr,
$total_channels:expr, $total_frames:expr, $policy:expr,
$spec_fmt:expr, $bits:expr) => {
match ($spec_fmt, $bits) {
(hound::SampleFormat::Int, 8) => {
let samples = read_and_interleave::<i8>(
$readers,
$channels_per_file,
$frames_per_file,
$total_channels,
$total_frames,
$policy,
)?;
RawStreamData::new(samples)
}
(hound::SampleFormat::Int, 16) => {
let samples = read_and_interleave::<i16>(
$readers,
$channels_per_file,
$frames_per_file,
$total_channels,
$total_frames,
$policy,
)?;
RawStreamData::new(samples)
}
(hound::SampleFormat::Int, 32) => {
let samples = read_and_interleave::<i32>(
$readers,
$channels_per_file,
$frames_per_file,
$total_channels,
$total_frames,
$policy,
)?;
RawStreamData::new(samples)
}
(hound::SampleFormat::Float, 32) => {
let samples = read_and_interleave::<f32>(
$readers,
$channels_per_file,
$frames_per_file,
$total_channels,
$total_frames,
$policy,
)?;
RawStreamData::new(samples)
}
(fmt, bits) => {
return WAVImportSnafu {
msg: format!("Unsupported WAV format: {fmt:?} with {bits} bits per sample"),
}
.fail();
}
}
};
}
pub fn wav_data_type(spec: &hound::WavSpec) -> Result<DataType> {
match spec.sample_format {
hound::SampleFormat::Int => match spec.bits_per_sample {
8 => Ok(DataType::I8),
16 => Ok(DataType::I16),
24 => Ok(DataType::I24),
32 => Ok(DataType::I32),
bits => WAVImportSnafu {
msg: format!("Unsupported bits per sample for integer WAV file: {bits}"),
}
.fail(),
},
hound::SampleFormat::Float => match spec.bits_per_sample {
32 => Ok(DataType::F32),
bits => WAVImportSnafu {
msg: format!(
"Unsupported bits per sample for floating point WAV file: \
{bits}. Only 32-bit float is supported."
),
}
.fail(),
},
}
}
pub fn wav_spec(ch: usize, sr: StrictlyPositive, dt: &DataType) -> hound::WavSpec {
let (sample_format, bits_per_sample) = match dt {
DataType::I8 => (hound::SampleFormat::Int, 8),
DataType::I16 => (hound::SampleFormat::Int, 16),
DataType::I24 => (hound::SampleFormat::Int, 24),
DataType::I32 => (hound::SampleFormat::Int, 32),
DataType::F32 => (hound::SampleFormat::Float, 32),
DataType::F64 => (hound::SampleFormat::Float, 64),
};
hound::WavSpec {
channels: ch as u16,
sample_rate: *sr as u32,
bits_per_sample,
sample_format,
}
}
#[cfg(feature = "python-bindings")]
#[pyfunction]
#[cfg_attr(feature = "python-bindings", gen_stub_pyfunction)]
pub fn read_wav_info(path: &str) -> PyResult<WavInfo> {
use hound::WavReader;
let reader = WavReader::open(path).map_err(|e| {
pyo3::exceptions::PyIOError::new_err(format!("Failed to open WAV file: {e}"))
})?;
let spec = reader.spec();
let total_samples = reader.len() as usize;
let nchannels = spec.channels as usize;
let nframes = total_samples.checked_div(nchannels).unwrap_or(0);
let duration = if spec.sample_rate > 0 {
nframes as f64 / spec.sample_rate as f64
} else {
0.0
};
let sample_format = match spec.sample_format {
hound::SampleFormat::Int => "int",
hound::SampleFormat::Float => "float",
};
let format_str = match spec.sample_format {
hound::SampleFormat::Int => match spec.bits_per_sample {
8 => "int8",
16 => "int16",
24 => "int24",
32 => "int32",
b => {
return Err(pyo3::exceptions::PyValueError::new_err(format!(
"Unsupported integer bits per sample: {b}"
)));
}
},
hound::SampleFormat::Float => match spec.bits_per_sample {
32 => "float32",
64 => "float64",
b => {
return Err(pyo3::exceptions::PyValueError::new_err(format!(
"Unsupported float bits per sample: {b}"
)));
}
},
};
Ok(WavInfo {
sample_rate: spec.sample_rate,
nchannels,
bits_per_sample: spec.bits_per_sample,
sample_format: sample_format.to_string(),
format_str: format_str.to_string(),
nframes,
duration,
})
}
pub fn collect_wav_samples<T: hound::Sample>(
reader: hound::WavReader<std::io::BufReader<std::fs::File>>,
) -> Result<Vec<T>> {
let wav_err = |e: hound::Error| {
WAVImportSnafu {
msg: format!("Error reading WAV sample: {e}"),
}
.build()
};
reader
.into_samples::<T>()
.map(|s| s.map_err(wav_err))
.collect::<Result<Vec<T>>>()
}