use std::path::Path;
use ffcine_core::{Error, Result};
#[derive(Debug, Clone, PartialEq, Default)]
pub struct Audio {
pub rate: u32,
pub channels: Vec<Vec<f32>>,
}
impl Audio {
pub fn silence(rate: u32, channels: usize, frames: usize) -> Self {
Self {
rate,
channels: vec![vec![0.0; frames]; channels],
}
}
pub fn mono(rate: u32, samples: Vec<f32>) -> Self {
Self {
rate,
channels: vec![samples],
}
}
pub fn frames(&self) -> usize {
self.channels.first().map_or(0, Vec::len)
}
pub fn seconds(&self) -> f64 {
self.frames() as f64 / f64::from(self.rate.max(1))
}
pub fn to_mono(&self) -> Vec<f32> {
match self.channels.as_slice() {
[] => Vec::new(),
[one] => one.clone(),
chans => {
let k = 1.0 / chans.len() as f32;
(0..self.frames())
.map(|i| chans.iter().map(|c| c[i]).sum::<f32>() * k)
.collect()
}
}
}
#[must_use]
pub fn to_stereo(&self) -> Self {
let channels = match self.channels.as_slice() {
[] => vec![Vec::new(), Vec::new()],
[one] => vec![one.clone(), one.clone()],
[l, r, ..] => vec![l.clone(), r.clone()],
};
Self {
rate: self.rate,
channels,
}
}
pub fn gain_db(&mut self, db: f64) {
let g = db_to_lin(db) as f32;
for c in &mut self.channels {
for s in c {
*s *= g;
}
}
}
pub fn mix_in(&mut self, other: &Audio, at: usize) {
let need = at + other.frames();
for (dst, src) in self.channels.iter_mut().zip(&other.channels) {
if dst.len() < need {
dst.resize(need, 0.0);
}
for (d, s) in dst[at..].iter_mut().zip(src) {
*d += *s;
}
}
}
pub fn set_frames(&mut self, frames: usize) {
for c in &mut self.channels {
c.resize(frames, 0.0);
}
}
pub fn read_wav(path: &Path) -> Result<Self> {
let err = |e: hound::Error| io_err(path, e);
let mut r = hound::WavReader::open(path).map_err(err)?;
let spec = r.spec();
let n = usize::from(spec.channels.max(1));
let interleaved: Vec<f32> = match spec.sample_format {
hound::SampleFormat::Float => {
r.samples::<f32>().collect::<Result<_, _>>().map_err(err)?
}
hound::SampleFormat::Int => {
let k = 1.0 / f64::from(1u32 << (spec.bits_per_sample - 1)) as f32;
r.samples::<i32>()
.map(|s| s.map(|s| s as f32 * k))
.collect::<Result<_, _>>()
.map_err(err)?
}
};
let mut channels = vec![Vec::with_capacity(interleaved.len() / n); n];
for frame in interleaved.chunks_exact(n) {
for (c, s) in channels.iter_mut().zip(frame) {
c.push(*s);
}
}
Ok(Self {
rate: spec.sample_rate,
channels,
})
}
pub fn write_wav(&self, path: &Path) -> Result<()> {
let err = |e: hound::Error| io_err(path, e);
let spec = hound::WavSpec {
channels: u16::try_from(self.channels.len()).map_err(|_| Error::Invalid {
what: "channel count",
value: self.channels.len().to_string(),
})?,
sample_rate: self.rate,
bits_per_sample: 32,
sample_format: hound::SampleFormat::Float,
};
let mut w = hound::WavWriter::create(path, spec).map_err(err)?;
for i in 0..self.frames() {
for c in &self.channels {
w.write_sample(c[i]).map_err(err)?;
}
}
w.finalize().map_err(err)
}
}
fn io_err(path: &Path, e: hound::Error) -> Error {
let source = match e {
hound::Error::IoError(e) => e,
e => std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()),
};
Error::Io {
path: path.to_owned(),
source,
}
}
pub fn db_to_lin(db: f64) -> f64 {
10f64.powf(db / 20.0)
}
pub fn lin_to_db(lin: f64) -> f64 {
20.0 * lin.max(1e-12).log10()
}