use super::{AudioIoError, AudioIoResult};
use voirs_sdk::AudioBuffer;
pub fn convert_sample_rate(
audio: AudioBuffer,
target_sample_rate: u32,
quality: u8,
) -> AudioIoResult<AudioBuffer> {
if audio.sample_rate() == target_sample_rate {
return Ok(audio);
}
let ratio = audio.sample_rate() as f64 / target_sample_rate as f64;
let channels = audio.channels();
let input_samples = audio.samples();
let frames_in = input_samples.len() / channels as usize;
let frames_out = (frames_in as f64 / ratio).ceil() as usize;
let mut output_samples = Vec::with_capacity(frames_out * channels as usize);
if quality >= 7 {
for frame_out in 0..frames_out {
let pos = frame_out as f64 * ratio;
let input_frame = pos.floor() as usize;
let frac = pos - input_frame as f64;
for ch in 0..channels as usize {
let sample = cubic_interpolate(
input_samples,
input_frame,
ch,
channels as usize,
frac,
frames_in,
);
output_samples.push(sample);
}
}
} else {
for frame_out in 0..frames_out {
let pos = frame_out as f64 * ratio;
let input_frame = pos.floor() as usize;
let frac = pos - input_frame as f64;
for ch in 0..channels as usize {
let sample = linear_interpolate(
input_samples,
input_frame,
ch,
channels as usize,
frac,
frames_in,
);
output_samples.push(sample);
}
}
}
Ok(AudioBuffer::new(
output_samples,
target_sample_rate,
channels,
))
}
pub fn convert_channels(audio: AudioBuffer, target_channels: u32) -> AudioIoResult<AudioBuffer> {
if audio.channels() == target_channels {
return Ok(audio);
}
let input_samples = audio.samples();
let input_channels = audio.channels();
let frames = input_samples.len() / input_channels as usize;
let mut output_samples = Vec::with_capacity(frames * target_channels as usize);
for frame in 0..frames {
match (input_channels, target_channels) {
(1, 2) => {
let sample = input_samples[frame];
output_samples.push(sample);
output_samples.push(sample);
}
(2, 1) => {
let left = input_samples[frame * 2];
let right = input_samples[frame * 2 + 1];
output_samples.push((left + right) * 0.5);
}
(1, n) if n > 2 => {
let sample = input_samples[frame];
for _ in 0..n {
output_samples.push(sample);
}
}
(n, 1) if n > 2 => {
let mut sum = 0.0;
for ch in 0..n as usize {
sum += input_samples[frame * n as usize + ch];
}
output_samples.push(sum / n as f32);
}
(n, 2) if n > 2 => {
let mut left = 0.0;
let mut right = 0.0;
for ch in 0..n as usize {
let sample = input_samples[frame * n as usize + ch];
if ch % 2 == 0 {
left += sample;
} else {
right += sample;
}
}
let left_count = n.div_ceil(2);
let right_count = n / 2;
output_samples.push(left / left_count as f32);
output_samples.push(if right_count > 0 {
right / right_count as f32
} else {
left / left_count as f32
});
}
(2, n) if n > 2 => {
let left = input_samples[frame * 2];
let right = input_samples[frame * 2 + 1];
for ch in 0..n as usize {
if ch % 2 == 0 {
output_samples.push(left);
} else {
output_samples.push(right);
}
}
}
(from, to) => {
for ch in 0..to as usize {
if ch < from as usize {
output_samples.push(input_samples[frame * from as usize + ch]);
} else {
output_samples.push(0.0);
}
}
}
}
}
Ok(AudioBuffer::new(
output_samples,
audio.sample_rate(),
target_channels,
))
}
pub fn normalize_audio(audio: AudioBuffer) -> AudioIoResult<AudioBuffer> {
let samples = audio.samples();
let peak = samples.iter().map(|&s| s.abs()).fold(0.0f32, f32::max);
if peak == 0.0 || peak == 1.0 {
return Ok(audio);
}
let scale = 1.0 / peak;
let normalized_samples: Vec<f32> = samples.iter().map(|&s| s * scale).collect();
Ok(AudioBuffer::new(
normalized_samples,
audio.sample_rate(),
audio.channels(),
))
}
pub fn remove_dc_offset(audio: AudioBuffer) -> AudioIoResult<AudioBuffer> {
let samples = audio.samples();
let channels = audio.channels() as usize;
let frames = samples.len() / channels;
let mut dc_offsets = vec![0.0f32; channels];
for ch in 0..channels {
let mut sum = 0.0;
for frame in 0..frames {
sum += samples[frame * channels + ch];
}
dc_offsets[ch] = sum / frames as f32;
}
let corrected_samples: Vec<f32> = samples
.iter()
.enumerate()
.map(|(i, &s)| {
let ch = i % channels;
s - dc_offsets[ch]
})
.collect();
Ok(AudioBuffer::new(
corrected_samples,
audio.sample_rate(),
audio.channels(),
))
}
pub fn apply_gain(audio: AudioBuffer, gain_db: f32) -> AudioIoResult<AudioBuffer> {
let gain_linear = 10.0f32.powf(gain_db / 20.0);
let samples = audio.samples();
let amplified_samples: Vec<f32> = samples
.iter()
.map(|&s| (s * gain_linear).clamp(-1.0, 1.0))
.collect();
Ok(AudioBuffer::new(
amplified_samples,
audio.sample_rate(),
audio.channels(),
))
}
fn cubic_interpolate(
samples: &[f32],
frame: usize,
channel: usize,
channels: usize,
frac: f64,
total_frames: usize,
) -> f32 {
let get_sample = |f: isize| -> f32 {
if f < 0 || f as usize >= total_frames {
0.0
} else {
samples[f as usize * channels + channel]
}
};
let y0 = get_sample(frame as isize - 1);
let y1 = get_sample(frame as isize);
let y2 = get_sample(frame as isize + 1);
let y3 = get_sample(frame as isize + 2);
let frac = frac as f32;
let a0 = y3 - y2 - y0 + y1;
let a1 = y0 - y1 - a0;
let a2 = y2 - y0;
let a3 = y1;
a0 * frac * frac * frac + a1 * frac * frac + a2 * frac + a3
}
fn linear_interpolate(
samples: &[f32],
frame: usize,
channel: usize,
channels: usize,
frac: f64,
total_frames: usize,
) -> f32 {
let s0 = if frame < total_frames {
samples[frame * channels + channel]
} else {
0.0
};
let s1 = if frame + 1 < total_frames {
samples[(frame + 1) * channels + channel]
} else {
s0
};
s0 + frac as f32 * (s1 - s0)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sample_rate_conversion() {
let samples = vec![0.5f32; 8000]; let audio = AudioBuffer::new(samples, 16000, 1);
let result = convert_sample_rate(audio, 32000, 7);
assert!(result.is_ok());
let converted = result.unwrap();
assert_eq!(converted.sample_rate(), 32000);
assert_eq!(converted.channels(), 1);
}
#[test]
fn test_sample_rate_no_conversion() {
let samples = vec![0.5f32; 8000];
let audio = AudioBuffer::new(samples, 16000, 1);
let result = convert_sample_rate(audio, 16000, 7);
assert!(result.is_ok());
let converted = result.unwrap();
assert_eq!(converted.sample_rate(), 16000);
}
#[test]
fn test_mono_to_stereo() {
let samples = vec![0.5f32; 1000];
let audio = AudioBuffer::new(samples, 16000, 1);
let result = convert_channels(audio, 2);
assert!(result.is_ok());
let converted = result.unwrap();
assert_eq!(converted.channels(), 2);
assert_eq!(converted.samples().len(), 2000);
}
#[test]
fn test_stereo_to_mono() {
let samples = vec![0.5f32; 2000];
let audio = AudioBuffer::new(samples, 16000, 2);
let result = convert_channels(audio, 1);
assert!(result.is_ok());
let converted = result.unwrap();
assert_eq!(converted.channels(), 1);
assert_eq!(converted.samples().len(), 1000);
}
#[test]
fn test_normalization() {
let samples = vec![0.1f32, 0.2f32, -0.3f32, 0.4f32];
let audio = AudioBuffer::new(samples, 16000, 1);
let result = normalize_audio(audio);
assert!(result.is_ok());
let normalized = result.unwrap();
let peak = normalized
.samples()
.iter()
.map(|&s| s.abs())
.fold(0.0f32, f32::max);
assert!((peak - 1.0).abs() < 0.001);
}
#[test]
fn test_dc_offset_removal() {
let samples = vec![0.6f32, 0.7f32, 0.5f32, 0.8f32]; let audio = AudioBuffer::new(samples, 16000, 1);
let result = remove_dc_offset(audio);
assert!(result.is_ok());
let corrected = result.unwrap();
let mean = corrected.samples().iter().sum::<f32>() / corrected.samples().len() as f32;
assert!(mean.abs() < 0.001); }
#[test]
fn test_gain_application() {
let samples = vec![0.1f32; 1000];
let audio = AudioBuffer::new(samples, 16000, 1);
let result = apply_gain(audio, 6.0); assert!(result.is_ok());
let amplified = result.unwrap();
let expected_amplitude = 0.1 * 10.0f32.powf(6.0 / 20.0);
assert!((amplified.samples()[0] - expected_amplitude).abs() < 0.001);
}
#[test]
fn test_cubic_interpolation() {
let samples = vec![0.0, 1.0, 0.0, -1.0, 0.0];
let channels = 1;
let total_frames = 5;
let result = cubic_interpolate(&samples, 1, 0, channels, 0.5, total_frames);
assert!(result > 0.0 && result < 1.0);
}
#[test]
fn test_linear_interpolation() {
let samples = vec![0.0, 1.0, 0.0];
let channels = 1;
let total_frames = 3;
let result = linear_interpolate(&samples, 0, 0, channels, 0.5, total_frames);
assert_eq!(result, 0.5); }
}