use crate::error::{CharonError, Result};
use hound::{WavSpec, WavWriter};
use ndarray::Array2;
use rubato::{
Resampler, SincFixedIn, SincInterpolationParameters, SincInterpolationType, WindowFunction,
};
use std::path::Path;
use symphonia::core::audio::{AudioBufferRef, Signal};
use symphonia::core::codecs::{DecoderOptions, CODEC_TYPE_NULL};
use symphonia::core::conv::IntoSample;
use symphonia::core::formats::FormatOptions;
use symphonia::core::io::MediaSourceStream;
use symphonia::core::meta::MetadataOptions;
use symphonia::core::probe::Hint;
#[derive(Debug, Clone)]
pub struct AudioBuffer {
pub data: Array2<f32>,
pub sample_rate: u32,
}
impl AudioBuffer {
pub fn new(data: Array2<f32>, sample_rate: u32) -> Self {
Self { data, sample_rate }
}
pub fn channels(&self) -> usize {
self.data.nrows()
}
pub fn samples(&self) -> usize {
self.data.ncols()
}
pub fn duration(&self) -> f64 {
self.samples() as f64 / self.sample_rate as f64
}
pub fn to_mono(&self) -> Array2<f32> {
let mono = self.data.mean_axis(ndarray::Axis(0)).unwrap();
mono.insert_axis(ndarray::Axis(0))
}
pub fn resample(&self, target_rate: u32) -> Result<Self> {
if self.sample_rate == target_rate {
return Ok(self.clone());
}
let params = SincInterpolationParameters {
sinc_len: 256,
f_cutoff: 0.95,
interpolation: SincInterpolationType::Linear,
oversampling_factor: 256,
window: WindowFunction::BlackmanHarris2,
};
let mut resampler = SincFixedIn::<f32>::new(
target_rate as f64 / self.sample_rate as f64,
2.0,
params,
self.samples(),
self.channels(),
)
.map_err(|e| CharonError::Resampling(e.to_string()))?;
let mut input_data: Vec<Vec<f32>> = Vec::new();
for ch in 0..self.channels() {
input_data.push(self.data.row(ch).to_vec());
}
let output_data = resampler
.process(&input_data, None)
.map_err(|e| CharonError::Resampling(e.to_string()))?;
let output_samples = output_data[0].len();
let mut data = Array2::zeros((self.channels(), output_samples));
for (ch, channel_data) in output_data.iter().enumerate() {
for (i, &sample) in channel_data.iter().enumerate() {
data[[ch, i]] = sample;
}
}
Ok(AudioBuffer::new(data, target_rate))
}
pub fn convert_channels(&self, target_channels: usize) -> Result<Self> {
if self.channels() == target_channels {
return Ok(self.clone());
}
let data = match (self.channels(), target_channels) {
(1, 2) => {
let mono = self.data.row(0);
ndarray::stack![ndarray::Axis(0), mono, mono]
}
(2, 1) => {
self.to_mono()
}
(n, 1) if n > 1 => {
self.to_mono()
}
(n, m) if n > m => {
self.data.slice(ndarray::s![0..m, ..]).to_owned()
}
_ => {
return Err(CharonError::Audio(format!(
"Unsupported channel conversion from {} to {}",
self.channels(),
target_channels
)))
}
};
Ok(AudioBuffer::new(data, self.sample_rate))
}
pub fn normalize(&mut self) {
let max_val = self.data.iter().map(|&x| x.abs()).fold(0.0f32, f32::max);
if max_val > 0.0 {
self.data /= max_val;
}
}
pub fn apply_gain(&mut self, gain_db: f32) {
let gain = 10.0f32.powf(gain_db / 20.0);
self.data *= gain;
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AudioFormat {
Wav,
Mp3,
Flac,
Ogg,
Auto,
}
impl AudioFormat {
pub fn from_path(path: &Path) -> Self {
match path.extension().and_then(|s| s.to_str()) {
Some("wav") => AudioFormat::Wav,
Some("mp3") => AudioFormat::Mp3,
Some("flac") => AudioFormat::Flac,
Some("ogg") => AudioFormat::Ogg,
_ => AudioFormat::Auto,
}
}
}
pub struct AudioFile;
impl AudioFile {
pub fn read<P: AsRef<Path>>(path: P) -> Result<AudioBuffer> {
let path = path.as_ref();
let file = std::fs::File::open(path)?;
let mss = MediaSourceStream::new(Box::new(file), Default::default());
let mut hint = Hint::new();
if let Some(ext) = path.extension().and_then(|e| e.to_str()) {
hint.with_extension(ext);
}
let meta_opts = MetadataOptions::default();
let fmt_opts = FormatOptions::default();
let probed = symphonia::default::get_probe()
.format(&hint, mss, &fmt_opts, &meta_opts)
.map_err(|e| CharonError::Audio(e.to_string()))?;
let mut format = probed.format;
let track = format
.tracks()
.iter()
.find(|t| t.codec_params.codec != CODEC_TYPE_NULL)
.ok_or_else(|| CharonError::Audio("No supported audio track found".to_string()))?;
let dec_opts = DecoderOptions::default();
let mut decoder = symphonia::default::get_codecs()
.make(&track.codec_params, &dec_opts)
.map_err(|e| CharonError::Audio(e.to_string()))?;
let sample_rate = track
.codec_params
.sample_rate
.ok_or_else(|| CharonError::Audio("Sample rate not found".to_string()))?;
let channels = track
.codec_params
.channels
.ok_or_else(|| CharonError::Audio("Channel info not found".to_string()))?
.count();
let mut samples: Vec<Vec<f32>> = vec![Vec::new(); channels];
while let Ok(packet) = format.next_packet() {
let decoded = match decoder.decode(&packet) {
Ok(decoded) => decoded,
Err(_) => continue,
};
Self::copy_samples(&decoded, &mut samples);
}
let num_samples = samples[0].len();
let mut data = Array2::zeros((channels, num_samples));
for (ch, channel_samples) in samples.iter().enumerate() {
for (i, &sample) in channel_samples.iter().enumerate() {
data[[ch, i]] = sample;
}
}
Ok(AudioBuffer::new(data, sample_rate))
}
fn copy_samples(decoded: &AudioBufferRef, output: &mut [Vec<f32>]) {
match decoded {
AudioBufferRef::F32(buf) => {
for (ch, out_ch) in output
.iter_mut()
.enumerate()
.take(buf.spec().channels.count())
{
out_ch.extend_from_slice(buf.chan(ch));
}
}
AudioBufferRef::S32(buf) => {
for (ch, out_ch) in output
.iter_mut()
.enumerate()
.take(buf.spec().channels.count())
{
out_ch.extend(
buf.chan(ch)
.iter()
.map(|&s| IntoSample::<f32>::into_sample(s)),
);
}
}
AudioBufferRef::S16(buf) => {
for (ch, out_ch) in output
.iter_mut()
.enumerate()
.take(buf.spec().channels.count())
{
out_ch.extend(
buf.chan(ch)
.iter()
.map(|&s| IntoSample::<f32>::into_sample(s)),
);
}
}
AudioBufferRef::U8(buf) => {
for (ch, out_ch) in output
.iter_mut()
.enumerate()
.take(buf.spec().channels.count())
{
out_ch.extend(
buf.chan(ch)
.iter()
.map(|&s| IntoSample::<f32>::into_sample(s)),
);
}
}
_ => {}
}
}
pub fn write_wav<P: AsRef<Path>>(path: P, buffer: &AudioBuffer) -> Result<()> {
let spec = WavSpec {
channels: buffer.channels() as u16,
sample_rate: buffer.sample_rate,
bits_per_sample: 32,
sample_format: hound::SampleFormat::Float,
};
let mut writer =
WavWriter::create(path, spec).map_err(|e| CharonError::Audio(e.to_string()))?;
for i in 0..buffer.samples() {
for ch in 0..buffer.channels() {
writer
.write_sample(buffer.data[[ch, i]])
.map_err(|e| CharonError::Audio(e.to_string()))?;
}
}
writer
.finalize()
.map_err(|e| CharonError::Audio(e.to_string()))?;
Ok(())
}
pub fn write<P: AsRef<Path>>(path: P, buffer: &AudioBuffer) -> Result<()> {
let format = AudioFormat::from_path(path.as_ref());
match format {
AudioFormat::Wav | AudioFormat::Auto => Self::write_wav(path, buffer),
_ => Err(CharonError::NotSupported(
"Only WAV output is currently supported".to_string(),
)),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_abs_diff_eq;
#[test]
fn test_audio_buffer_creation() {
let data = Array2::zeros((2, 1000));
let buffer = AudioBuffer::new(data, 44100);
assert_eq!(buffer.channels(), 2);
assert_eq!(buffer.samples(), 1000);
assert_eq!(buffer.sample_rate, 44100);
}
#[test]
fn test_duration_calculation() {
let data = Array2::zeros((2, 44100));
let buffer = AudioBuffer::new(data, 44100);
assert_abs_diff_eq!(buffer.duration(), 1.0, epsilon = 0.001);
}
#[test]
fn test_mono_conversion() {
let mut data = Array2::zeros((2, 100));
data.row_mut(0).fill(1.0);
data.row_mut(1).fill(3.0);
let buffer = AudioBuffer::new(data, 44100);
let mono = buffer.to_mono();
assert_eq!(mono.nrows(), 1);
assert_abs_diff_eq!(mono[[0, 0]], 2.0, epsilon = 0.001);
}
}