#[cfg(test)]
mod sample_source_tests {
use std::sync::Arc;
use crate::audio::sample_source::audio::AudioSampleSource;
use crate::audio::sample_source::create_sample_source_from_file;
use crate::audio::sample_source::memory::MemorySampleSource;
use crate::audio::sample_source::traits::{
ChannelMappedSampleSource, SampleSource, SampleSourceTestExt,
};
use crate::audio::sample_source::transcoder::AudioTranscoder;
use crate::audio::sample_source::{BufferFillPool, BufferedSampleSource, ChannelMappedSource};
use crate::audio::TargetFormat;
use crate::config::ResamplerType;
#[test]
fn test_integer_scaling_signed_ranges() {
assert!((AudioSampleSource::scale_s8(0) - 0.0).abs() < 1e-7);
assert!(AudioSampleSource::scale_s8(i8::MAX) <= 1.0 + 1e-7);
assert!(AudioSampleSource::scale_s8(i8::MIN) >= -1.0 - 1e-7);
assert!((AudioSampleSource::scale_s16(0) - 0.0).abs() < 1e-7);
assert!(AudioSampleSource::scale_s16(i16::MAX) <= 1.0 + 1e-7);
assert!(AudioSampleSource::scale_s16(i16::MIN) >= -1.0 - 1e-7);
assert!((AudioSampleSource::scale_s24(0) - 0.0).abs() < 1e-7);
assert!(AudioSampleSource::scale_s24((1 << 23) - 1) <= 1.0 + 1e-7);
assert!(AudioSampleSource::scale_s24(-(1 << 23)) >= -1.0 - 1e-7);
assert!((AudioSampleSource::scale_s32(0) - 0.0).abs() < 1e-7);
assert!(AudioSampleSource::scale_s32(i32::MAX) <= 1.0 + 1e-7);
assert!(AudioSampleSource::scale_s32(i32::MIN) >= -1.0 - 1e-7);
}
#[test]
fn test_integer_scaling_unsigned_ranges() {
assert!((AudioSampleSource::scale_u8(0) + 1.0).abs() < 1e-7);
assert!((AudioSampleSource::scale_u8(u8::MAX) - 1.0).abs() < 1e-7);
let mid_u8 = AudioSampleSource::scale_u8(128);
assert!(mid_u8 > -0.01 && mid_u8 < 0.01);
assert!((AudioSampleSource::scale_u16(0) + 1.0).abs() < 1e-7);
assert!((AudioSampleSource::scale_u16(u16::MAX) - 1.0).abs() < 1e-7);
let mid_u16 = AudioSampleSource::scale_u16(u16::MAX / 2);
assert!(mid_u16 > -0.01 && mid_u16 < 0.01);
let max_u24 = (1u32 << 24) - 1;
assert!((AudioSampleSource::scale_u24(0) + 1.0).abs() < 1e-7);
assert!((AudioSampleSource::scale_u24(max_u24) - 1.0).abs() < 1e-7);
let mid_u24 = AudioSampleSource::scale_u24(max_u24 / 2);
assert!(mid_u24 > -0.01 && mid_u24 < 0.01);
assert!((AudioSampleSource::scale_u32(0) + 1.0).abs() < 1e-7);
assert!((AudioSampleSource::scale_u32(u32::MAX) - 1.0).abs() < 1e-7);
let mid_u32 = AudioSampleSource::scale_u32(u32::MAX / 2);
assert!(mid_u32 > -0.01 && mid_u32 < 0.01);
}
use crate::testutil::audio_test_utils::calculate_snr;
use rand;
fn calculate_high_frequency_energy(samples: &[f32], _sample_rate: f32) -> f32 {
if samples.len() < 2 {
return 0.0;
}
let mut high_freq_energy = 0.0;
for i in 1..samples.len() {
let diff = samples[i] - samples[i - 1];
high_freq_energy += diff * diff;
}
high_freq_energy / (samples.len() - 1) as f32
}
#[test]
fn test_memory_sample_source() {
let samples = vec![1.0, 2.0, 3.0, 4.0, 5.0];
let _target_format = TargetFormat::default();
let mut source = MemorySampleSource::new(samples.clone(), 1, 44100);
for (i, expected) in samples.iter().enumerate() {
let sample = source.next_sample().unwrap().unwrap();
assert_eq!(sample, *expected);
if i == samples.len() - 1 {
assert!(SampleSourceTestExt::is_finished(&source));
} else {
assert!(!SampleSourceTestExt::is_finished(&source));
}
}
assert!(source.next_sample().unwrap().is_none());
assert!(SampleSourceTestExt::is_finished(&source));
}
#[test]
fn test_memory_sample_source_duration_mono() {
let samples = vec![1.0, 2.0, 3.0, 4.0, 5.0]; let source = MemorySampleSource::new(samples.clone(), 1, 44100);
let total_samples = samples.len() as f64;
let samples_per_channel = total_samples / 1.0; let duration_secs = samples_per_channel / 44100.0;
let expected_duration = std::time::Duration::from_secs_f64(duration_secs);
let actual_duration = source.duration().unwrap();
let diff = actual_duration.abs_diff(expected_duration);
assert!(
diff < std::time::Duration::from_micros(1),
"Duration mismatch: expected {:?}, got {:?}",
expected_duration,
actual_duration
);
}
#[test]
fn test_memory_sample_source_duration_stereo() {
let samples = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]; let source = MemorySampleSource::new(samples.clone(), 2, 44100);
let total_samples = samples.len() as f64;
let samples_per_channel = total_samples / 2.0; let duration_secs = samples_per_channel / 44100.0;
let expected_duration = std::time::Duration::from_secs_f64(duration_secs);
let actual_duration = source.duration().unwrap();
let diff = actual_duration.abs_diff(expected_duration);
assert!(
diff < std::time::Duration::from_micros(1),
"Duration mismatch: expected {:?}, got {:?}",
expected_duration,
actual_duration
);
}
#[test]
fn test_resampling_quality() {
let source_format =
TargetFormat::new(48000, crate::audio::SampleFormat::Float, 32).unwrap();
let target_format =
TargetFormat::new(44100, crate::audio::SampleFormat::Float, 32).unwrap();
let mock_source = MemorySampleSource::new(vec![0.1, 0.2, 0.3, 0.4, 0.5], 1, 44100);
match AudioTranscoder::new(
mock_source,
&source_format,
&target_format,
1,
ResamplerType::Sinc,
) {
Ok(mut converter) => {
let mut output_samples = Vec::with_capacity(100);
let mut sample_count = 0;
const MAX_SAMPLES: usize = 100;
while sample_count < MAX_SAMPLES {
match converter.next_sample() {
Ok(Some(sample)) => {
output_samples.push(sample);
sample_count += 1;
}
Ok(None) => break, Err(_e) => break, }
}
assert!(!output_samples.is_empty(), "Output should not be empty");
assert!(
!output_samples.is_empty(),
"Should have some output samples"
);
let max_amplitude = output_samples.iter().map(|&x| x.abs()).fold(0.0, f32::max);
assert!(
max_amplitude > 0.0,
"Resampled signal should have some amplitude"
);
assert!(
max_amplitude <= 1.1,
"Resampled signal too loud, max amplitude: {}",
max_amplitude
);
}
Err(_e) => {
}
}
}
#[test]
fn test_rubato_resampler_creation() {
let test_cases = vec![
(44100, 48000), (48000, 44100), (44100, 44100), ];
for (source_rate, target_rate) in test_cases {
let source_format =
TargetFormat::new(source_rate, crate::audio::SampleFormat::Float, 32).unwrap();
let target_format =
TargetFormat::new(target_rate, crate::audio::SampleFormat::Float, 32).unwrap();
let mock_source = MemorySampleSource::new(vec![0.1, 0.2, 0.3], 1, 44100);
let converter = AudioTranscoder::new(
mock_source,
&source_format,
&target_format,
1,
ResamplerType::Sinc,
);
if source_rate == target_rate {
assert!(converter.is_ok());
let converter = converter.unwrap();
assert!(converter.resampler.is_none());
} else {
match converter {
Ok(_converter) => {
if source_rate != target_rate {
}
}
Err(_e) => {
}
}
}
}
}
#[test]
fn test_rubato_configuration_debug() {
let source_format =
TargetFormat::new(48000, crate::audio::SampleFormat::Float, 32).unwrap();
let target_format =
TargetFormat::new(44100, crate::audio::SampleFormat::Float, 32).unwrap();
let mock_source = MemorySampleSource::new(vec![1.0, 2.0, 3.0, 4.0, 5.0], 1, 44100);
match AudioTranscoder::new(
mock_source,
&source_format,
&target_format,
1,
ResamplerType::Sinc,
) {
Ok(mut converter) => {
let mut sample_count = 0;
const MAX_SAMPLES: usize = 50;
while sample_count < MAX_SAMPLES {
match converter.next_sample() {
Ok(Some(_sample)) => sample_count += 1,
Ok(None) => break,
Err(_e) => break,
}
}
}
Err(_e) => {}
}
}
#[test]
fn test_resampling_edge_cases() {
let test_cases = vec![
(44100, 48000, true), (48000, 44100, true), (44100, 44100, false), ];
for (source_rate, target_rate, should_need_resampling) in test_cases {
let source_format =
TargetFormat::new(source_rate, crate::audio::SampleFormat::Float, 32).unwrap();
let target_format =
TargetFormat::new(target_rate, crate::audio::SampleFormat::Float, 32).unwrap();
let mock_source = MemorySampleSource::new(vec![0.1, 0.2, 0.3], 1, 44100);
let converter = AudioTranscoder::new(
mock_source,
&source_format,
&target_format,
1,
ResamplerType::Sinc,
);
match converter {
Ok(converter) => {
let _needs_resampling = should_need_resampling;
assert!(converter.source_rate == source_rate);
assert!(converter.target_rate == target_rate);
}
Err(_) => {
}
}
}
}
#[test]
fn test_no_resampling_needed() {
let source_format =
TargetFormat::new(44100, crate::audio::SampleFormat::Float, 32).unwrap();
let target_format =
TargetFormat::new(44100, crate::audio::SampleFormat::Float, 32).unwrap();
let mock_source = MemorySampleSource::new(vec![0.1, 0.2, 0.3, 0.4, 0.5], 1, 44100);
let mut converter = AudioTranscoder::new(
mock_source,
&source_format,
&target_format,
1,
ResamplerType::Sinc,
)
.unwrap();
let mut output_samples = Vec::with_capacity(10);
let mut sample_count = 0;
const MAX_SAMPLES: usize = 10;
while sample_count < MAX_SAMPLES {
match converter.next_sample() {
Ok(Some(sample)) => {
output_samples.push(sample);
sample_count += 1;
}
Ok(None) => break,
Err(_e) => break,
}
}
assert!(!output_samples.is_empty());
}
#[test]
fn test_resampling_quality_sine_wave() {
let source_format =
TargetFormat::new(48000, crate::audio::SampleFormat::Float, 32).unwrap();
let target_format =
TargetFormat::new(44100, crate::audio::SampleFormat::Float, 32).unwrap();
let frequency = 1000.0; let duration = 0.1; let num_samples = (48000.0 * duration) as usize;
let mut input_samples = Vec::with_capacity(num_samples);
for i in 0..num_samples {
let t = i as f32 / 48000.0;
let sample = (2.0 * std::f32::consts::PI * frequency * t).sin();
input_samples.push(sample);
}
let mock_source = MemorySampleSource::new(input_samples, 1, 44100);
match AudioTranscoder::new(
mock_source,
&source_format,
&target_format,
1,
ResamplerType::Sinc,
) {
Ok(mut converter) => {
let mut output_samples = Vec::with_capacity(200);
let mut sample_count = 0;
const MAX_SAMPLES: usize = 200;
while sample_count < MAX_SAMPLES {
match converter.next_sample() {
Ok(Some(sample)) => {
output_samples.push(sample);
sample_count += 1;
}
Ok(None) => break,
Err(_e) => break,
}
}
assert!(
!output_samples.is_empty(),
"Should have some output samples"
);
assert!(
!output_samples.is_empty(),
"Should have some output samples"
);
let max_amplitude = output_samples.iter().map(|&x| x.abs()).fold(0.0, f32::max);
assert!(
max_amplitude > 0.5,
"Resampled sine wave too quiet, max amplitude: {}",
max_amplitude
);
assert!(
max_amplitude <= 1.1,
"Resampled sine wave too loud, max amplitude: {}",
max_amplitude
);
let high_freq_energy = calculate_high_frequency_energy(&output_samples, 44100.0);
assert!(
high_freq_energy < 0.1,
"Too much high-frequency content (aliasing): {}",
high_freq_energy
);
}
Err(_e) => {}
}
}
#[test]
fn test_roundtrip_resampling_quality() {
let original_rate = 44100;
let intermediate_rate = 48000;
let final_rate = 44100;
let source_format_1 =
TargetFormat::new(original_rate, crate::audio::SampleFormat::Float, 32).unwrap();
let target_format_1 =
TargetFormat::new(intermediate_rate, crate::audio::SampleFormat::Float, 32).unwrap();
let source_format_2 =
TargetFormat::new(intermediate_rate, crate::audio::SampleFormat::Float, 32).unwrap();
let target_format_2 =
TargetFormat::new(final_rate, crate::audio::SampleFormat::Float, 32).unwrap();
let duration = 0.1; let num_samples = (original_rate as f32 * duration) as usize;
let mut original_samples = Vec::with_capacity(num_samples);
for i in 0..num_samples {
let t = i as f32 / original_rate as f32;
let sine_wave = 0.5 * (2.0 * std::f32::consts::PI * 1000.0 * t).sin();
let noise = 0.05 * (rand::random::<f32>() - 0.5);
original_samples.push(sine_wave + noise);
}
let source_1 = MemorySampleSource::new(original_samples.clone(), 1, 44100);
let mut converter_1 = AudioTranscoder::new(
source_1,
&source_format_1,
&target_format_1,
1,
ResamplerType::Sinc,
)
.unwrap();
let mut intermediate_samples = Vec::with_capacity(num_samples);
loop {
match converter_1.next_sample() {
Ok(Some(sample)) => intermediate_samples.push(sample),
Ok(None) => break,
Err(_) => break,
}
}
let intermediate_len = intermediate_samples.len();
let source_2 = MemorySampleSource::new(intermediate_samples, 1, intermediate_rate);
let mut converter_2 = AudioTranscoder::new(
source_2,
&source_format_2,
&target_format_2,
1,
ResamplerType::Sinc,
)
.unwrap();
let mut final_samples = Vec::with_capacity(intermediate_len);
loop {
match converter_2.next_sample() {
Ok(Some(sample)) => final_samples.push(sample),
Ok(None) => break,
Err(_) => break,
}
}
assert!(
!final_samples.is_empty(),
"Final samples should not be empty"
);
let expected_length = original_samples.len();
let length_tolerance = (expected_length as f32 * 0.35) as usize;
assert!(
final_samples.len() >= expected_length - length_tolerance
&& final_samples.len() <= expected_length + length_tolerance,
"Final length {} should be close to original length {}",
final_samples.len(),
expected_length
);
let max_amplitude = final_samples.iter().map(|&x| x.abs()).fold(0.0, f32::max);
assert!(
max_amplitude > 0.1,
"Final signal should have reasonable amplitude, got {}",
max_amplitude
);
assert!(
max_amplitude <= 1.0,
"Final signal should not be too loud, got {}",
max_amplitude
);
}
#[test]
fn test_resampling_quality_impulse() {
let source_format =
TargetFormat::new(48000, crate::audio::SampleFormat::Float, 32).unwrap();
let target_format =
TargetFormat::new(44100, crate::audio::SampleFormat::Float, 32).unwrap();
let mut input_samples = vec![0.0; 100];
input_samples[50] = 1.0;
let source = MemorySampleSource::new(input_samples, 1, 44100);
let mut converter = AudioTranscoder::new(
source,
&source_format,
&target_format,
1,
ResamplerType::Sinc,
)
.unwrap();
let mut output_samples = Vec::new();
loop {
match converter.next_sample() {
Ok(Some(sample)) => output_samples.push(sample),
Ok(None) => break,
Err(_) => break,
}
}
assert!(!output_samples.is_empty(), "Output should not be empty");
let max_amplitude = output_samples.iter().map(|&x| x.abs()).fold(0.0, f32::max);
assert!(
max_amplitude > 1e-8,
"Impulse signal should have reasonable amplitude after resampling, got {}",
max_amplitude
);
}
#[test]
fn test_resampling_quality_noise() {
let source_format =
TargetFormat::new(44100, crate::audio::SampleFormat::Float, 32).unwrap();
let target_format =
TargetFormat::new(48000, crate::audio::SampleFormat::Float, 32).unwrap();
let num_samples = 1000;
let mut input_samples = Vec::new();
for _ in 0..num_samples {
let noise = (rand::random::<f32>() - 0.5) * 2.0;
input_samples.push(noise);
}
let source = MemorySampleSource::new(input_samples.clone(), 1, 44100);
let mut converter = AudioTranscoder::new(
source,
&source_format,
&target_format,
1,
ResamplerType::Sinc,
)
.unwrap();
let mut output_samples = Vec::new();
loop {
match converter.next_sample() {
Ok(Some(sample)) => output_samples.push(sample),
Ok(None) => break,
Err(_) => break,
}
}
assert!(!output_samples.is_empty(), "Output should not be empty");
let expected_ratio = 48000.0 / 44100.0;
let expected_length = (num_samples as f32 * expected_ratio) as usize;
let length_tolerance = (expected_length as f32 * 0.30) as usize;
assert!(
output_samples.len() >= expected_length - length_tolerance
&& output_samples.len() <= expected_length + length_tolerance,
"Expected ~{} samples, got {}",
expected_length,
output_samples.len()
);
let input_rms = calculate_rms(&input_samples);
let output_rms = calculate_rms(&output_samples);
let rms_ratio = output_rms / input_rms;
assert!(
rms_ratio > 0.5 && rms_ratio < 1.5,
"RMS ratio out of range: {} (input: {}, output: {})",
rms_ratio,
input_rms,
output_rms
);
}
#[test]
fn test_resampling_multichannel_quality() {
let source_format =
TargetFormat::new(48000, crate::audio::SampleFormat::Float, 32).unwrap();
let target_format =
TargetFormat::new(44100, crate::audio::SampleFormat::Float, 32).unwrap();
let channels = 2;
let duration = 0.1; let num_frames = (48000.0 * duration) as usize;
let mut input_samples = Vec::new();
for i in 0..num_frames {
let t = i as f32 / 48000.0;
let left = 0.3 * (2.0 * std::f32::consts::PI * 440.0 * t).sin();
let right = 0.3 * (2.0 * std::f32::consts::PI * 880.0 * t).sin();
input_samples.push(left);
input_samples.push(right);
}
let source = MemorySampleSource::new(input_samples, 1, 44100);
let mut converter = AudioTranscoder::new(
source,
&source_format,
&target_format,
channels,
ResamplerType::Sinc,
)
.unwrap();
let mut output_samples = Vec::new();
loop {
match converter.next_sample() {
Ok(Some(sample)) => {
output_samples.push(sample);
}
Ok(None) => break,
Err(_) => break,
}
}
assert!(!output_samples.is_empty(), "Output should not be empty");
let expected_length = (num_frames as f32 * (44100.0 / 48000.0) * channels as f32) as usize;
let length_tolerance = (expected_length as f32 * 0.1) as usize;
assert!(
output_samples.len() >= expected_length - length_tolerance
&& output_samples.len() <= expected_length + length_tolerance,
"Expected ~{} samples, got {}",
expected_length,
output_samples.len()
);
let remainder = output_samples.len() % 2;
assert!(
remainder == 0
|| output_samples.len() % 2 == 1
&& output_samples.len() <= expected_length + length_tolerance + 1,
"Stereo output should have even number of samples (or at most one extra), got {}",
output_samples.len()
);
}
fn calculate_rms(samples: &[f32]) -> f32 {
if samples.is_empty() {
return 0.0;
}
let sum_squares: f32 = samples.iter().map(|&x| x * x).sum();
(sum_squares / samples.len() as f32).sqrt()
}
#[test]
fn test_resampling_empty_input() {
let source_format =
TargetFormat::new(48000, crate::audio::SampleFormat::Float, 32).unwrap();
let target_format =
TargetFormat::new(44100, crate::audio::SampleFormat::Float, 32).unwrap();
let source = MemorySampleSource::new(vec![], 1, 44100);
let mut converter = AudioTranscoder::new(
source,
&source_format,
&target_format,
1,
ResamplerType::Sinc,
)
.unwrap();
assert!(matches!(converter.next_sample(), Ok(None)));
}
#[test]
fn test_resampling_single_sample() {
let source_format =
TargetFormat::new(48000, crate::audio::SampleFormat::Float, 32).unwrap();
let target_format =
TargetFormat::new(44100, crate::audio::SampleFormat::Float, 32).unwrap();
let source = MemorySampleSource::new(vec![0.5], 1, 44100);
let mut converter = AudioTranscoder::new(
source,
&source_format,
&target_format,
1,
ResamplerType::Sinc,
)
.unwrap();
let mut output_samples = Vec::new();
loop {
match converter.next_sample() {
Ok(Some(sample)) => output_samples.push(sample),
Ok(None) => break,
Err(_) => break,
}
}
assert!(
!output_samples.is_empty(),
"Single sample should produce some output"
);
}
#[test]
fn test_resampling_extreme_ratios() {
let test_cases = vec![
(8000, 192000), (192000, 8000), (44100, 88200), (88200, 44100), ];
for (source_rate, target_rate) in test_cases {
let source_format =
TargetFormat::new(source_rate, crate::audio::SampleFormat::Float, 32).unwrap();
let target_format =
TargetFormat::new(target_rate, crate::audio::SampleFormat::Float, 32).unwrap();
let duration = 0.01; let num_samples = (source_rate as f32 * duration) as usize;
let mut input_samples = Vec::new();
for i in 0..num_samples {
let t = i as f32 / source_rate as f32;
input_samples.push((2.0 * std::f32::consts::PI * 1000.0 * t).sin() * 0.5);
}
let source = MemorySampleSource::new(input_samples, 1, 44100);
let mut converter = AudioTranscoder::new(
source,
&source_format,
&target_format,
1,
ResamplerType::Sinc,
)
.unwrap();
let mut output_samples = Vec::new();
let mut sample_count = 0;
const MAX_SAMPLES: usize = 1000;
while sample_count < MAX_SAMPLES {
match converter.next_sample() {
Ok(Some(sample)) => {
output_samples.push(sample);
sample_count += 1;
}
Ok(None) => break,
Err(_) => break,
}
}
assert!(
!output_samples.is_empty(),
"Extreme ratio {}/{} should produce output",
source_rate,
target_rate
);
let max_amplitude = output_samples.iter().map(|&x| x.abs()).fold(0.0, f32::max);
assert!(
max_amplitude > 1e-8,
"Extreme ratio {}/{} should have reasonable amplitude, got {}",
source_rate,
target_rate,
max_amplitude
);
}
}
#[test]
fn test_resampling_long_duration() {
let source_format =
TargetFormat::new(48000, crate::audio::SampleFormat::Float, 32).unwrap();
let target_format =
TargetFormat::new(44100, crate::audio::SampleFormat::Float, 32).unwrap();
let duration = 1.0;
let num_samples = (48000.0 * duration) as usize;
let mut input_samples = Vec::new();
for i in 0..num_samples {
let t = i as f32 / 48000.0;
let signal = (2.0 * std::f32::consts::PI * 440.0 * t).sin() * 0.3
+ (2.0 * std::f32::consts::PI * 880.0 * t).sin() * 0.2
+ (2.0 * std::f32::consts::PI * 1760.0 * t).sin() * 0.1;
input_samples.push(signal);
}
let source = MemorySampleSource::new(input_samples, 1, 44100);
let mut converter = AudioTranscoder::new(
source,
&source_format,
&target_format,
1,
ResamplerType::Sinc,
)
.unwrap();
let mut output_samples = Vec::new();
let mut sample_count = 0;
const MAX_SAMPLES: usize = 50000;
while sample_count < MAX_SAMPLES {
match converter.next_sample() {
Ok(Some(sample)) => {
output_samples.push(sample);
sample_count += 1;
}
Ok(None) => break,
Err(_) => break,
}
}
assert!(
!output_samples.is_empty(),
"Long duration should produce output"
);
let expected_length = (num_samples as f32 * (44100.0 / 48000.0)) as usize;
let length_tolerance = (expected_length as f32 * 0.05) as usize;
assert!(
output_samples.len() >= expected_length - length_tolerance
&& output_samples.len() <= expected_length + length_tolerance,
"Long duration: expected ~{}, got {}",
expected_length,
output_samples.len()
);
}
#[test]
fn test_resampling_high_frequency_content() {
let source_format =
TargetFormat::new(48000, crate::audio::SampleFormat::Float, 32).unwrap();
let target_format =
TargetFormat::new(44100, crate::audio::SampleFormat::Float, 32).unwrap();
let num_samples = 1000;
let mut input_samples = Vec::new();
for i in 0..num_samples {
let t = i as f32 / 48000.0;
let signal = (2.0 * std::f32::consts::PI * 20000.0 * t).sin() * 0.5;
input_samples.push(signal);
}
let source = MemorySampleSource::new(input_samples, 1, 44100);
let mut converter = AudioTranscoder::new(
source,
&source_format,
&target_format,
1,
ResamplerType::Sinc,
)
.unwrap();
let mut output_samples = Vec::new();
loop {
match converter.next_sample() {
Ok(Some(sample)) => output_samples.push(sample),
Ok(None) => break,
Err(_) => break,
}
}
assert!(
!output_samples.is_empty(),
"High frequency content should produce output"
);
let high_freq_energy = calculate_high_frequency_energy(&output_samples, 44100.0);
assert!(
high_freq_energy < 0.5, "High frequency content should be attenuated, got {}",
high_freq_energy
);
}
#[test]
fn test_resampling_overflow_protection() {
let source_format =
TargetFormat::new(48000, crate::audio::SampleFormat::Float, 32).unwrap();
let target_format =
TargetFormat::new(44100, crate::audio::SampleFormat::Float, 32).unwrap();
let test_values = vec![
vec![1.0, -1.0, 0.999, -0.999], vec![0.0, 0.0, 0.0, 0.0], vec![0.5, -0.5, 0.5, -0.5], ];
for values in test_values {
let source = MemorySampleSource::new(values, 1, 44100);
let mut converter = AudioTranscoder::new(
source,
&source_format,
&target_format,
1,
ResamplerType::Sinc,
)
.unwrap();
let mut output_samples = Vec::new();
let mut sample_count = 0;
const MAX_SAMPLES: usize = 100;
while sample_count < MAX_SAMPLES {
match converter.next_sample() {
Ok(Some(sample)) => {
assert!(
sample.is_finite(),
"Output should be finite, got {}",
sample
);
output_samples.push(sample);
sample_count += 1;
}
Ok(None) => break,
Err(_) => break,
}
}
assert!(
!output_samples.is_empty(),
"Should produce output for edge values"
);
}
}
#[test]
fn test_resampling_dc_offset() {
let source_format =
TargetFormat::new(48000, crate::audio::SampleFormat::Float, 32).unwrap();
let target_format =
TargetFormat::new(44100, crate::audio::SampleFormat::Float, 32).unwrap();
let dc_offset = 0.1;
let num_samples = 100;
let mut input_samples = Vec::new();
for i in 0..num_samples {
let t = i as f32 / 48000.0;
let signal = (2.0 * std::f32::consts::PI * 1000.0 * t).sin() * 0.3 + dc_offset;
input_samples.push(signal);
}
let source = MemorySampleSource::new(input_samples, 1, 44100);
let mut converter = AudioTranscoder::new(
source,
&source_format,
&target_format,
1,
ResamplerType::Sinc,
)
.unwrap();
let mut output_samples = Vec::new();
loop {
match converter.next_sample() {
Ok(Some(sample)) => output_samples.push(sample),
Ok(None) => break,
Err(_) => break,
}
}
assert!(
!output_samples.is_empty(),
"DC offset test should produce output"
);
let mean_value = output_samples.iter().sum::<f32>() / output_samples.len() as f32;
assert!(
(mean_value - dc_offset).abs() < 0.2,
"DC offset should be reasonably preserved, expected ~{}, got {}",
dc_offset,
mean_value
);
}
#[test]
fn test_resampling_simple_snr() {
let source_format =
TargetFormat::new(48000, crate::audio::SampleFormat::Float, 32).unwrap();
let target_format =
TargetFormat::new(44100, crate::audio::SampleFormat::Float, 32).unwrap();
let frequency = 1000.0; let duration = 0.01; let num_samples = (48000.0 * duration) as usize;
let mut original_samples = Vec::with_capacity(num_samples);
for i in 0..num_samples {
let t = i as f32 / 48000.0;
let sample = (2.0 * std::f32::consts::PI * frequency * t).sin() * 0.5;
original_samples.push(sample);
}
let source = MemorySampleSource::new(original_samples.clone(), 1, 44100);
let mut converter = AudioTranscoder::new(
source,
&source_format,
&target_format,
1,
ResamplerType::Sinc,
)
.unwrap();
let mut output_samples = Vec::with_capacity(num_samples);
loop {
match converter.next_sample() {
Ok(Some(sample)) => output_samples.push(sample),
Ok(None) => break,
Err(_) => break,
}
}
assert!(!output_samples.is_empty(), "No output samples generated");
assert!(
output_samples.len() > 100,
"Too few output samples: {}",
output_samples.len()
);
let output_rms = calculate_rms(&output_samples);
assert!(output_rms > 0.1, "Output RMS too low: {}", output_rms);
assert!(output_rms < 1.0, "Output RMS too high: {}", output_rms);
}
#[test]
fn test_resampling_snr_quality() {
let source_format =
TargetFormat::new(48000, crate::audio::SampleFormat::Float, 32).unwrap();
let target_format =
TargetFormat::new(44100, crate::audio::SampleFormat::Float, 32).unwrap();
let back_format = TargetFormat::new(48000, crate::audio::SampleFormat::Float, 32).unwrap();
let frequency = 1000.0; let duration = 0.1; let num_samples = (48000.0 * duration) as usize;
let mut original_samples = Vec::with_capacity(num_samples);
for i in 0..num_samples {
let t = i as f32 / 48000.0;
let sample = (2.0 * std::f32::consts::PI * frequency * t).sin() * 0.5;
original_samples.push(sample);
}
let source_1 = MemorySampleSource::new(original_samples.clone(), 1, 44100);
let mut converter_1 = AudioTranscoder::new(
source_1,
&source_format,
&target_format,
1,
ResamplerType::Sinc,
)
.unwrap();
let mut intermediate_samples = Vec::with_capacity(num_samples);
loop {
match converter_1.next_sample() {
Ok(Some(sample)) => intermediate_samples.push(sample),
Ok(None) => break,
Err(_) => break,
}
}
let source_2 = MemorySampleSource::new(intermediate_samples, 1, 44100);
let mut converter_2 = AudioTranscoder::new(
source_2,
&target_format,
&back_format,
1,
ResamplerType::Sinc,
)
.unwrap();
let mut final_samples = Vec::with_capacity(original_samples.len());
loop {
match converter_2.next_sample() {
Ok(Some(sample)) => final_samples.push(sample),
Ok(None) => break,
Err(_) => break,
}
}
let min_len = original_samples.len().min(final_samples.len());
let original_truncated = &original_samples[..min_len];
let final_truncated = &final_samples[..min_len];
let snr = calculate_snr(original_truncated, final_truncated);
assert!(
snr > -10.0,
"SNR too low: {} dB (expected > -10 dB). Original: {} samples, Final: {} samples",
snr,
original_truncated.len(),
final_truncated.len()
);
}
#[test]
fn test_resampling_rms_preservation() {
let test_cases = vec![
(48000, 44100, 1000.0), (48000, 96000, 2000.0), (44100, 48000, 1500.0), ];
for (source_rate, target_rate, frequency) in test_cases {
let source_format =
TargetFormat::new(source_rate, crate::audio::SampleFormat::Float, 32).unwrap();
let target_format =
TargetFormat::new(target_rate, crate::audio::SampleFormat::Float, 32).unwrap();
let duration = 0.05; let num_samples = (source_rate as f32 * duration) as usize;
let mut input_samples = Vec::with_capacity(num_samples);
for i in 0..num_samples {
let t = i as f32 / source_rate as f32;
let sample = (2.0 * std::f32::consts::PI * frequency * t).sin() * 0.3;
input_samples.push(sample);
}
let source = MemorySampleSource::new(input_samples.clone(), 1, source_rate);
let mut converter = AudioTranscoder::new(
source,
&source_format,
&target_format,
1,
ResamplerType::Sinc,
)
.unwrap();
let mut output_samples = Vec::with_capacity(num_samples);
loop {
match converter.next_sample() {
Ok(Some(sample)) => output_samples.push(sample),
Ok(None) => break,
Err(_) => break,
}
}
let input_rms = calculate_rms(&input_samples);
let output_rms = calculate_rms(&output_samples);
let rms_ratio = output_rms / input_rms;
assert!(
(0.8..=1.2).contains(&rms_ratio),
"RMS ratio out of range for {}Hz->{}Hz: {} (input: {}, output: {})",
source_rate,
target_rate,
rms_ratio,
input_rms,
output_rms
);
}
}
#[test]
fn test_resampling_snr_multichannel() {
let source_format =
TargetFormat::new(48000, crate::audio::SampleFormat::Float, 32).unwrap();
let target_format =
TargetFormat::new(44100, crate::audio::SampleFormat::Float, 32).unwrap();
let duration = 0.1; let num_frames = (48000.0 * duration) as usize;
let mut input_samples = Vec::with_capacity(num_frames * 2);
for i in 0..num_frames {
let t = i as f32 / 48000.0;
let left = 0.3 * (2.0 * std::f32::consts::PI * 440.0 * t).sin();
let right = 0.3 * (2.0 * std::f32::consts::PI * 880.0 * t).sin();
input_samples.push(left);
input_samples.push(right);
}
let source_1 = MemorySampleSource::new(input_samples.clone(), 1, 44100);
let mut converter_1 = AudioTranscoder::new(
source_1,
&source_format,
&target_format,
2,
ResamplerType::Sinc,
)
.unwrap();
let mut intermediate_samples = Vec::with_capacity(input_samples.len());
loop {
match converter_1.next_sample() {
Ok(Some(sample)) => intermediate_samples.push(sample),
Ok(None) => break,
Err(_) => break,
}
}
let back_format = TargetFormat::new(48000, crate::audio::SampleFormat::Float, 32).unwrap();
let source_2 = MemorySampleSource::new(intermediate_samples, 1, 44100);
let mut converter_2 = AudioTranscoder::new(
source_2,
&target_format,
&back_format,
2,
ResamplerType::Sinc,
)
.unwrap();
let mut output_samples = Vec::with_capacity(input_samples.len());
loop {
match converter_2.next_sample() {
Ok(Some(sample)) => output_samples.push(sample),
Ok(None) => break,
Err(_) => break,
}
}
let mut left_original = Vec::with_capacity(num_frames);
let mut right_original = Vec::with_capacity(num_frames);
let mut left_output = Vec::with_capacity(output_samples.len() / 2);
let mut right_output = Vec::with_capacity(output_samples.len() / 2);
for i in (0..input_samples.len()).step_by(2) {
if i + 1 < input_samples.len() {
left_original.push(input_samples[i]);
right_original.push(input_samples[i + 1]);
}
}
for i in (0..output_samples.len()).step_by(2) {
if i + 1 < output_samples.len() {
left_output.push(output_samples[i]);
right_output.push(output_samples[i + 1]);
}
}
let left_min_len = left_original.len().min(left_output.len());
let right_min_len = right_original.len().min(right_output.len());
let left_original_truncated = &left_original[..left_min_len];
let left_output_truncated = &left_output[..left_min_len];
let right_original_truncated = &right_original[..right_min_len];
let right_output_truncated = &right_output[..right_min_len];
let left_snr = calculate_snr(left_original_truncated, left_output_truncated);
let right_snr = calculate_snr(right_original_truncated, right_output_truncated);
assert!(
left_snr > -10.0,
"Left channel SNR too low: {} dB (expected > -10 dB)",
left_snr
);
assert!(
right_snr > -10.0,
"Right channel SNR too low: {} dB (expected > -10 dB)",
right_snr
);
}
#[test]
fn test_resampling_rms_complex_signal() {
let source_format =
TargetFormat::new(48000, crate::audio::SampleFormat::Float, 32).unwrap();
let target_format =
TargetFormat::new(44100, crate::audio::SampleFormat::Float, 32).unwrap();
let duration = 0.1; let num_samples = (48000.0 * duration) as usize;
let mut input_samples = Vec::with_capacity(num_samples);
for i in 0..num_samples {
let t = i as f32 / 48000.0;
let fundamental = 0.4 * (2.0 * std::f32::consts::PI * 220.0 * t).sin();
let harmonic1 = 0.2 * (2.0 * std::f32::consts::PI * 440.0 * t).sin();
let harmonic2 = 0.1 * (2.0 * std::f32::consts::PI * 880.0 * t).sin();
let harmonic3 = 0.05 * (2.0 * std::f32::consts::PI * 1320.0 * t).sin();
let complex_signal = fundamental + harmonic1 + harmonic2 + harmonic3;
input_samples.push(complex_signal);
}
let source = MemorySampleSource::new(input_samples.clone(), 1, 44100);
let mut converter = AudioTranscoder::new(
source,
&source_format,
&target_format,
1,
ResamplerType::Sinc,
)
.unwrap();
let mut output_samples = Vec::with_capacity(num_samples);
loop {
match converter.next_sample() {
Ok(Some(sample)) => output_samples.push(sample),
Ok(None) => break,
Err(_) => break,
}
}
let input_rms = calculate_rms(&input_samples);
let output_rms = calculate_rms(&output_samples);
let rms_ratio = output_rms / input_rms;
assert!(
(0.85..=1.15).contains(&rms_ratio),
"RMS ratio out of range for complex signal: {} (input: {}, output: {})",
rms_ratio,
input_rms,
output_rms
);
let input_energy = input_rms * input_rms;
let output_energy = output_rms * output_rms;
let energy_ratio = output_energy / input_energy;
assert!(
(0.7..=1.3).contains(&energy_ratio),
"Energy ratio out of range for complex signal: {} (input: {}, output: {})",
energy_ratio,
input_energy,
output_energy
);
}
#[test]
fn test_fft_resampler_basic_quality() {
let source_rate = 48000;
let target_rate = 44100;
let num_samples = 4800; let frequency = 440.0;
let input_samples: Vec<f32> = (0..num_samples)
.map(|i| {
(i as f32 * frequency * 2.0 * std::f32::consts::PI / source_rate as f32).sin() * 0.5
})
.collect();
let source_format =
TargetFormat::new(source_rate, crate::audio::SampleFormat::Float, 32).unwrap();
let target_format =
TargetFormat::new(target_rate, crate::audio::SampleFormat::Float, 32).unwrap();
let source = MemorySampleSource::new(input_samples.clone(), 1, source_rate);
let mut converter = AudioTranscoder::new(
source,
&source_format,
&target_format,
1,
ResamplerType::Fft,
)
.unwrap();
let mut output_samples = Vec::new();
loop {
match converter.next_sample() {
Ok(Some(sample)) => output_samples.push(sample),
Ok(None) => break,
Err(_) => break,
}
}
let expected_len = (num_samples as f64 * target_rate as f64 / source_rate as f64) as usize;
assert!(
output_samples.len() > expected_len / 2,
"FFT resampler produced too few samples: {} (expected ~{})",
output_samples.len(),
expected_len
);
let rms: f32 = (output_samples.iter().map(|&x| x * x).sum::<f32>()
/ output_samples.len() as f32)
.sqrt();
assert!(
rms > 0.1 && rms < 1.0,
"FFT resampler output RMS out of range: {}",
rms
);
}
#[test]
fn test_fft_resampler_roundtrip() {
let num_samples = 8820; let frequency = 440.0;
let source_rate = 44100u32;
let intermediate_rate = 48000u32;
let original_samples: Vec<f32> = (0..num_samples)
.map(|i| {
(i as f32 * frequency * 2.0 * std::f32::consts::PI / source_rate as f32).sin() * 0.5
})
.collect();
let source_format =
TargetFormat::new(source_rate, crate::audio::SampleFormat::Float, 32).unwrap();
let intermediate_format =
TargetFormat::new(intermediate_rate, crate::audio::SampleFormat::Float, 32).unwrap();
let source_1 = MemorySampleSource::new(original_samples.clone(), 1, source_rate);
let mut converter_1 = AudioTranscoder::new(
source_1,
&source_format,
&intermediate_format,
1,
ResamplerType::Fft,
)
.unwrap();
let mut intermediate_samples = Vec::new();
loop {
match converter_1.next_sample() {
Ok(Some(sample)) => intermediate_samples.push(sample),
Ok(None) => break,
Err(_) => break,
}
}
let source_2 = MemorySampleSource::new(intermediate_samples, 1, intermediate_rate);
let mut converter_2 = AudioTranscoder::new(
source_2,
&intermediate_format,
&source_format,
1,
ResamplerType::Fft,
)
.unwrap();
let mut final_samples = Vec::new();
loop {
match converter_2.next_sample() {
Ok(Some(sample)) => final_samples.push(sample),
Ok(None) => break,
Err(_) => break,
}
}
let len_ratio = final_samples.len() as f64 / original_samples.len() as f64;
assert!(
(0.9..=1.1).contains(&len_ratio),
"FFT roundtrip length ratio out of range: {} ({} vs {})",
len_ratio,
final_samples.len(),
original_samples.len()
);
let skip = 2048; let compare_len = original_samples
.len()
.min(final_samples.len())
.saturating_sub(skip * 2);
if compare_len > 100 {
let orig_slice = &original_samples[skip..skip + compare_len];
let final_slice = &final_samples[skip..skip + compare_len];
let orig_rms: f32 =
(orig_slice.iter().map(|&x| x * x).sum::<f32>() / orig_slice.len() as f32).sqrt();
let final_rms: f32 =
(final_slice.iter().map(|&x| x * x).sum::<f32>() / final_slice.len() as f32).sqrt();
let rms_ratio = final_rms / orig_rms;
assert!(
(0.7..=1.3).contains(&rms_ratio),
"FFT roundtrip RMS ratio out of range: {:.3} (orig: {:.4}, final: {:.4})",
rms_ratio,
orig_rms,
final_rms
);
}
}
#[test]
fn test_fft_resampler_no_resampling_passthrough() {
let source_format =
TargetFormat::new(44100, crate::audio::SampleFormat::Float, 32).unwrap();
let target_format =
TargetFormat::new(44100, crate::audio::SampleFormat::Float, 32).unwrap();
let samples = vec![0.1, 0.2, 0.3, 0.4, 0.5];
let source = MemorySampleSource::new(samples.clone(), 1, 44100);
let mut converter = AudioTranscoder::new(
source,
&source_format,
&target_format,
1,
ResamplerType::Fft,
)
.unwrap();
assert!(
converter.resampler.is_none(),
"Resampler should be None when rates match"
);
let mut output = Vec::new();
loop {
match converter.next_sample() {
Ok(Some(s)) => output.push(s),
Ok(None) => break,
Err(_) => break,
}
}
assert_eq!(output, samples);
}
#[test]
fn test_wav_sample_source_16bit() {
use crate::testutil::write_wav_with_bits;
use tempfile::tempdir;
let tempdir = tempdir().unwrap();
let wav_path = tempdir.path().join("test_16bit.wav");
let samples: Vec<i16> = vec![1000, -2000, 3000, -4000, 5000];
write_wav_with_bits(wav_path.clone(), vec![samples], 44100, 16).unwrap();
let mut wav_source = create_sample_source_from_file(&wav_path, None, 1024).unwrap();
let mut read_samples = Vec::new();
loop {
match wav_source.next_sample() {
Ok(Some(sample)) => read_samples.push(sample),
Ok(None) => break,
Err(e) => panic!("Error reading sample: {}", e),
}
}
assert_eq!(read_samples.len(), 5);
let expected_samples = [
1000.0 / (1 << 15) as f32, -2000.0 / (1 << 15) as f32, 3000.0 / (1 << 15) as f32, -4000.0 / (1 << 15) as f32, 5000.0 / (1 << 15) as f32, ];
for (i, (actual, expected)) in read_samples.iter().zip(expected_samples.iter()).enumerate()
{
assert!(
(actual - expected).abs() < 0.0001,
"Sample {} mismatch: expected {}, got {}",
i,
expected,
actual
);
}
}
#[test]
fn test_wav_sample_source_24bit() {
use crate::testutil::write_wav_with_bits;
use tempfile::tempdir;
let tempdir = tempdir().unwrap();
let wav_path = tempdir.path().join("test_24bit.wav");
let samples: Vec<i32> = vec![100000, -200000, 300000, -400000, 500000];
write_wav_with_bits(wav_path.clone(), vec![samples], 44100, 24).unwrap();
let mut wav_source = create_sample_source_from_file(&wav_path, None, 1024).unwrap();
let mut read_samples = Vec::new();
loop {
match wav_source.next_sample() {
Ok(Some(sample)) => read_samples.push(sample),
Ok(None) => break,
Err(e) => panic!("Error reading sample: {}", e),
}
}
assert_eq!(read_samples.len(), 5);
let expected_samples = [
100000.0 / (1 << 23) as f32, -200000.0 / (1 << 23) as f32, 300000.0 / (1 << 23) as f32, -400000.0 / (1 << 23) as f32, 500000.0 / (1 << 23) as f32, ];
for (i, (actual, expected)) in read_samples.iter().zip(expected_samples.iter()).enumerate()
{
assert!(
(actual - expected).abs() < 0.0001,
"Sample {} mismatch: expected {}, got {}",
i,
expected,
actual
);
}
}
#[test]
fn test_wav_sample_source_32bit() {
use crate::testutil::write_wav;
use tempfile::tempdir;
let tempdir = tempdir().unwrap();
let wav_path = tempdir.path().join("test_32bit.wav");
let samples: Vec<i32> = vec![1000000, -2000000, 3000000, -4000000, 5000000];
write_wav(wav_path.clone(), vec![samples], 44100).unwrap();
let mut wav_source = create_sample_source_from_file(&wav_path, None, 1024).unwrap();
let mut read_samples = Vec::new();
loop {
match wav_source.next_sample() {
Ok(Some(sample)) => read_samples.push(sample),
Ok(None) => break,
Err(e) => panic!("Error reading sample: {}", e),
}
}
assert_eq!(read_samples.len(), 5);
let expected_samples = [
0.0004656613, -0.0009313226, 0.0013969839, -0.0018626451, 0.0023283064, ];
for (i, (actual, expected)) in read_samples.iter().zip(expected_samples.iter()).enumerate()
{
assert!(
(actual - expected).abs() < 0.0001,
"Sample {} mismatch: expected {}, got {}",
i,
expected,
actual
);
}
}
#[test]
fn test_wav_sample_source_stereo() {
use crate::testutil::write_wav;
use tempfile::tempdir;
let tempdir = tempdir().unwrap();
let wav_path = tempdir.path().join("test_stereo.wav");
let left_samples: Vec<i32> = vec![1000, 2000, 3000];
let right_samples: Vec<i32> = vec![-1000, -2000, -3000];
write_wav(wav_path.clone(), vec![left_samples, right_samples], 44100).unwrap();
let mut wav_source = AudioSampleSource::from_file(&wav_path, None, 1024).unwrap();
let mut read_samples = Vec::new();
loop {
match wav_source.next_sample() {
Ok(Some(sample)) => read_samples.push(sample),
Ok(None) => break,
Err(e) => panic!("Error reading sample: {}", e),
}
}
assert_eq!(read_samples.len(), 6);
let expected_samples = [
1000.0 / (1 << 31) as f32, -1000.0 / (1 << 31) as f32, 2000.0 / (1 << 31) as f32, -2000.0 / (1 << 31) as f32, 3000.0 / (1 << 31) as f32, -3000.0 / (1 << 31) as f32, ];
for (i, (actual, expected)) in read_samples.iter().zip(expected_samples.iter()).enumerate()
{
assert!(
(actual - expected).abs() < 0.0001,
"Sample {} mismatch: expected {}, got {}",
i,
expected,
actual
);
}
}
#[test]
fn test_wav_sample_source_empty_file() {
use crate::testutil::write_wav;
use tempfile::tempdir;
let tempdir = tempdir().unwrap();
let wav_path = tempdir.path().join("test_empty.wav");
write_wav(wav_path.clone(), vec![Vec::<i32>::new()], 44100).unwrap();
let mut wav_source = AudioSampleSource::from_file(&wav_path, None, 1024).unwrap();
match wav_source.next_sample() {
Ok(None) => {} Ok(Some(sample)) => panic!("Expected None for empty file, got: {}", sample),
Err(e) => panic!("Error reading empty file: {}", e),
}
assert!(wav_source.is_finished());
}
#[test]
fn test_wav_sample_source_nonexistent_file() {
let wav_path = std::path::Path::new("nonexistent_file.wav");
if AudioSampleSource::from_file(wav_path, None, 1024).is_ok() {
panic!("Expected error for nonexistent file")
}
}
#[test]
fn test_wav_sample_source_is_finished() {
use crate::testutil::write_wav;
use tempfile::tempdir;
let tempdir = tempdir().unwrap();
let wav_path = tempdir.path().join("test_finished.wav");
let samples: Vec<i32> = vec![1000, 2000, 3000];
write_wav(wav_path.clone(), vec![samples], 44100).unwrap();
let mut wav_source = AudioSampleSource::from_file(&wav_path, None, 1024).unwrap();
assert!(!wav_source.is_finished());
let mut sample_count = 0;
loop {
match wav_source.next_sample() {
Ok(Some(_)) => {
sample_count += 1;
assert!(!wav_source.is_finished()); }
Ok(None) => {
assert!(wav_source.is_finished()); break;
}
Err(e) => panic!("Error reading sample: {}", e),
}
}
assert_eq!(sample_count, 3);
assert!(wav_source.is_finished());
}
#[test]
fn test_wav_sample_source_amplitude_consistency() {
use crate::testutil::write_wav_with_bits;
use tempfile::tempdir;
let tempdir = tempdir().unwrap();
let sample_rate = 44100;
let duration_samples = 1000;
let frequency = 440.0;
let sine_wave: Vec<f32> = (0..duration_samples)
.map(|i| {
(i as f32 * frequency * 2.0 * std::f32::consts::PI / sample_rate as f32).sin() * 0.5
})
.collect();
let wav_16_path = tempdir.path().join("test_16bit_amplitude.wav");
let samples_16: Vec<i16> = sine_wave.iter().map(|&x| (x * 32767.0) as i16).collect();
write_wav_with_bits(wav_16_path.clone(), vec![samples_16], sample_rate, 16).unwrap();
let wav_24_path = tempdir.path().join("test_24bit_amplitude.wav");
let samples_24: Vec<i32> = sine_wave
.iter()
.map(|&x| (x * 8388607.0) as i32) .collect();
write_wav_with_bits(wav_24_path.clone(), vec![samples_24], sample_rate, 24).unwrap();
let wav_32_path = tempdir.path().join("test_32bit_amplitude.wav");
let samples_32: Vec<i32> = sine_wave
.iter()
.map(|&x| (x * 2147483647.0) as i32) .collect();
write_wav_with_bits(wav_32_path.clone(), vec![samples_32], sample_rate, 32).unwrap();
let mut wav_16_source = AudioSampleSource::from_file(&wav_16_path, None, 1024).unwrap();
let mut wav_24_source = AudioSampleSource::from_file(&wav_24_path, None, 1024).unwrap();
let mut wav_32_source = AudioSampleSource::from_file(&wav_32_path, None, 1024).unwrap();
let mut samples_16_read = Vec::new();
let mut samples_24_read = Vec::new();
let mut samples_32_read = Vec::new();
for _ in 0..duration_samples {
if let Ok(Some(sample)) = wav_16_source.next_sample() {
samples_16_read.push(sample);
}
if let Ok(Some(sample)) = wav_24_source.next_sample() {
samples_24_read.push(sample);
}
if let Ok(Some(sample)) = wav_32_source.next_sample() {
samples_32_read.push(sample);
}
}
let rms_16: f32 = (samples_16_read.iter().map(|&x| x * x).sum::<f32>()
/ samples_16_read.len() as f32)
.sqrt();
let rms_24: f32 = (samples_24_read.iter().map(|&x| x * x).sum::<f32>()
/ samples_24_read.len() as f32)
.sqrt();
let rms_32: f32 = (samples_32_read.iter().map(|&x| x * x).sum::<f32>()
/ samples_32_read.len() as f32)
.sqrt();
let expected_rms = 0.5 / (2.0_f32.sqrt());
assert!(
(rms_16 - expected_rms).abs() / expected_rms < 0.05,
"16-bit RMS too different: got {:.6}, expected {:.6}",
rms_16,
expected_rms
);
assert!(
(rms_24 - expected_rms).abs() / expected_rms < 0.05,
"24-bit RMS too different: got {:.6}, expected {:.6}",
rms_24,
expected_rms
);
assert!(
(rms_32 - expected_rms).abs() / expected_rms < 0.05,
"32-bit RMS too different: got {:.6}, expected {:.6}",
rms_32,
expected_rms
);
assert!(
(rms_16 - rms_24).abs() / rms_16 < 0.1,
"16-bit and 24-bit RMS too different: {:.6} vs {:.6}",
rms_16,
rms_24
);
assert!(
(rms_16 - rms_32).abs() / rms_16 < 0.1,
"16-bit and 32-bit RMS too different: {:.6} vs {:.6}",
rms_16,
rms_32
);
assert!(
(rms_24 - rms_32).abs() / rms_24 < 0.1,
"24-bit and 32-bit RMS too different: {:.6} vs {:.6}",
rms_24,
rms_32
);
}
#[test]
fn test_wav_sample_source_different_sample_rates() {
use crate::testutil::write_wav;
use tempfile::tempdir;
let tempdir = tempdir().unwrap();
let sample_rates = vec![22050, 44100, 48000, 96000];
for sample_rate in sample_rates {
let wav_path = tempdir.path().join(format!("test_{}.wav", sample_rate));
let duration = 0.01; let num_samples = (sample_rate as f32 * duration) as usize;
let samples: Vec<i32> = (0..num_samples)
.map(|i| {
((i as f32 * 1000.0 * 2.0 * std::f32::consts::PI / sample_rate as f32).sin()
* (1 << 23) as f32) as i32
})
.collect();
write_wav(wav_path.clone(), vec![samples], sample_rate).unwrap();
let mut wav_source = AudioSampleSource::from_file(&wav_path, None, 1024).unwrap();
let mut read_samples = Vec::new();
loop {
match wav_source.next_sample() {
Ok(Some(sample)) => read_samples.push(sample),
Ok(None) => break,
Err(e) => panic!("Error reading sample at {}Hz: {}", sample_rate, e),
}
}
assert_eq!(read_samples.len(), num_samples);
let rms: f32 = (read_samples.iter().map(|&x| x * x).sum::<f32>()
/ read_samples.len() as f32)
.sqrt();
assert!(rms > 0.001, "RMS too low for {}Hz: {}", sample_rate, rms);
}
}
#[test]
fn test_wav_sample_source_seek() {
use crate::testutil::write_wav;
use std::time::Duration;
use tempfile::tempdir;
let tempdir = tempdir().unwrap();
let wav_path = tempdir.path().join("test_seek.wav");
let sample_rate = 44100u32;
let duration_secs = 10;
let total_samples = sample_rate as usize * duration_secs;
let samples: Vec<i32> = (0..total_samples)
.map(|i| (i as i32 / 1000).min(i32::MAX / 2))
.collect();
write_wav(wav_path.clone(), vec![samples], sample_rate).unwrap();
let seek_time = Duration::from_secs(5);
let mut wav_source =
AudioSampleSource::from_file(&wav_path, Some(seek_time), 1024).unwrap();
let first_sample = wav_source.next_sample().unwrap();
assert!(first_sample.is_some(), "Should have samples after seeking");
let second_sample = wav_source.next_sample().unwrap();
assert!(
second_sample.is_some(),
"Should be able to read multiple samples after seeking"
);
let mut wav_source_start =
AudioSampleSource::from_file(&wav_path, Some(std::time::Duration::ZERO), 1024).unwrap();
let start_sample = wav_source_start.next_sample().unwrap();
assert!(start_sample.is_some(), "Should have samples from start");
}
#[test]
fn test_wav_sample_source_seek_clears_leftover_samples() {
use crate::testutil::write_wav;
use std::time::Duration;
use tempfile::tempdir;
let tempdir = tempdir().unwrap();
let wav_path = tempdir.path().join("test_seek_leftover.wav");
let sample_rate = 44100u32;
let duration_secs = 4;
let total_samples = sample_rate as usize * duration_secs;
let samples: Vec<i32> = (0..total_samples)
.map(|i| {
if i < (sample_rate as usize * 2) {
1000
} else {
-1000
}
})
.collect();
write_wav(wav_path.clone(), vec![samples], sample_rate).unwrap();
std::env::set_var("MTRACK_FORCE_DETECT_CHANNELS", "1");
let seek_time = Duration::from_secs(3);
let mut wav_source =
AudioSampleSource::from_file(&wav_path, Some(seek_time), 1024).unwrap();
let first_sample = wav_source
.next_sample()
.unwrap()
.expect("expected sample after seeking");
assert!(
first_sample < 0.0,
"expected first sample after seek to come from post‑seek (negative) region, got {}",
first_sample
);
std::env::remove_var("MTRACK_FORCE_DETECT_CHANNELS");
}
#[test]
fn test_buffered_sample_source_matches_inner_sequence() {
let samples = vec![0.1_f32, 0.2_f32, 0.3_f32, 0.4_f32];
let memory = MemorySampleSource::new(samples.clone(), 1, 44100);
let channel_mappings = vec![vec!["test".to_string()]];
let inner: Box<dyn crate::audio::sample_source::ChannelMappedSampleSource + Send + Sync> =
Box::new(ChannelMappedSource::new(
Box::new(memory),
channel_mappings,
1,
));
let pool = Arc::new(BufferFillPool::new(1).expect("failed to create BufferFillPool"));
let mut buffered = BufferedSampleSource::new(inner, pool, 2);
while buffered.is_exhausted() != Some(true) {
std::thread::sleep(std::time::Duration::from_millis(10));
}
let mut read = Vec::new();
while let Some(sample) = buffered.next_sample().unwrap() {
read.push(sample);
}
assert_eq!(read, samples);
}
#[test]
fn test_unbuffered_source_is_exhausted_returns_none() {
let memory = MemorySampleSource::new(vec![0.1, 0.2], 1, 44100);
let source = ChannelMappedSource::new(Box::new(memory), vec![vec!["test".to_string()]], 1);
assert_eq!(source.is_exhausted(), None);
}
#[test]
fn test_buffered_source_is_exhausted_lifecycle() {
let samples: Vec<f32> = (0..32).map(|i| i as f32 * 0.01).collect();
let memory = MemorySampleSource::new(samples, 1, 44100);
let inner: Box<dyn ChannelMappedSampleSource + Send + Sync> = Box::new(
ChannelMappedSource::new(Box::new(memory), vec![vec!["test".to_string()]], 1),
);
let pool = Arc::new(BufferFillPool::new(1).expect("pool"));
let mut buffered = BufferedSampleSource::new(inner, pool, 2);
assert_eq!(buffered.is_exhausted(), Some(false));
while buffered.next_sample().unwrap().is_some() {}
assert_eq!(buffered.is_exhausted(), Some(true));
}
#[test]
fn test_wav_sample_source_4channel() {
use crate::testutil::write_wav_with_bits;
use tempfile::tempdir;
let tempdir = tempdir().unwrap();
let wav_path = tempdir.path().join("test_4channel.wav");
let channel_0: Vec<i32> = vec![1000, -2000, 3000];
let channel_1: Vec<i32> = vec![4000, -5000, 6000];
let channel_2: Vec<i32> = vec![7000, -8000, 9000];
let channel_3: Vec<i32> = vec![10000, -11000, 12000];
write_wav_with_bits(
wav_path.clone(),
vec![channel_0, channel_1, channel_2, channel_3],
44100,
32,
)
.unwrap();
let mut wav_source = AudioSampleSource::from_file(&wav_path, None, 1024).unwrap();
assert_eq!(wav_source.channel_count(), 4);
let mut samples_read = Vec::new();
for _ in 0..12 {
if let Ok(Some(sample)) = wav_source.next_sample() {
samples_read.push(sample);
}
}
assert_eq!(samples_read.len(), 12);
let expected_samples = [
1000.0 / (1 << 31) as f32, 4000.0 / (1 << 31) as f32, 7000.0 / (1 << 31) as f32, 10000.0 / (1 << 31) as f32, -2000.0 / (1 << 31) as f32, -5000.0 / (1 << 31) as f32, -8000.0 / (1 << 31) as f32, -11000.0 / (1 << 31) as f32, 3000.0 / (1 << 31) as f32, 6000.0 / (1 << 31) as f32, 9000.0 / (1 << 31) as f32, 12000.0 / (1 << 31) as f32, ];
for (i, (actual, expected)) in samples_read.iter().zip(expected_samples.iter()).enumerate()
{
assert!(
(actual - expected).abs() < 0.0001,
"Sample {} mismatch: expected {}, got {}",
i,
expected,
actual
);
}
}
#[test]
fn test_wav_sample_source_6channel() {
use crate::testutil::write_wav_with_bits;
use tempfile::tempdir;
let tempdir = tempdir().unwrap();
let wav_path = tempdir.path().join("test_6channel.wav");
let channel_0: Vec<i32> = vec![1000, -2000]; let channel_1: Vec<i32> = vec![3000, -4000]; let channel_2: Vec<i32> = vec![5000, -6000]; let channel_3: Vec<i32> = vec![7000, -8000]; let channel_4: Vec<i32> = vec![9000, -10000]; let channel_5: Vec<i32> = vec![11000, -12000];
write_wav_with_bits(
wav_path.clone(),
vec![
channel_0, channel_1, channel_2, channel_3, channel_4, channel_5,
],
48000,
24,
)
.unwrap();
let mut wav_source = AudioSampleSource::from_file(&wav_path, None, 1024).unwrap();
assert_eq!(wav_source.channel_count(), 6);
assert_eq!(wav_source.sample_rate(), 48000);
let mut samples_read = Vec::new();
for _ in 0..12 {
if let Ok(Some(sample)) = wav_source.next_sample() {
samples_read.push(sample);
}
}
assert_eq!(samples_read.len(), 12);
let expected_samples = [
1000.0 / (1 << 23) as f32, -2000.0 / (1 << 23) as f32, 3000.0 / (1 << 23) as f32, -4000.0 / (1 << 23) as f32, 5000.0 / (1 << 23) as f32, -6000.0 / (1 << 23) as f32, 7000.0 / (1 << 23) as f32, -8000.0 / (1 << 23) as f32, 9000.0 / (1 << 23) as f32, -10000.0 / (1 << 23) as f32, 11000.0 / (1 << 23) as f32, -12000.0 / (1 << 23) as f32, ];
for (i, (actual, expected)) in samples_read.iter().zip(expected_samples.iter()).enumerate()
{
assert!(
(actual - expected).abs() < 0.0001,
"Sample {} mismatch: expected {}, got {}",
i,
expected,
actual
);
}
}
fn decode_some_samples_from<P: AsRef<std::path::Path>>(path: P) {
let path = path.as_ref();
assert!(
path.exists(),
"expected audio fixture to exist at {:?}",
path
);
let mut source = create_sample_source_from_file(path, None, 1024)
.expect("failed to create sample source");
assert!(source.sample_rate() > 0, "sample_rate should be > 0");
assert!(source.channel_count() > 0, "channel_count should be > 0");
let mut count = 0usize;
const MAX_SAMPLES: usize = 2048;
while count < MAX_SAMPLES {
match source.next_sample() {
Ok(Some(_)) => count += 1,
Ok(None) => break,
Err(e) => panic!("error while decoding samples: {}", e),
}
}
assert!(count > 0, "no samples decoded from test file: {:?}", path);
}
#[test]
fn test_symphonia_can_decode_wav() {
decode_some_samples_from(std::path::Path::new("assets/1Channel44.1k.wav"));
}
#[test]
fn test_symphonia_can_decode_flac() {
decode_some_samples_from(std::path::Path::new("assets/1Channel44.1k.flac"));
}
#[test]
fn test_symphonia_can_decode_ogg_vorbis() {
decode_some_samples_from(std::path::Path::new("assets/1Channel44.1k.ogg"));
}
#[test]
fn test_symphonia_can_decode_mp3() {
decode_some_samples_from(std::path::Path::new("assets/1Channel44.1k.mp3"));
}
#[test]
fn test_symphonia_can_decode_aac() {
decode_some_samples_from(std::path::Path::new("assets/1Channel44.1k.aac"));
}
#[test]
fn test_symphonia_can_decode_alac() {
decode_some_samples_from(std::path::Path::new("assets/1Channel44.1k_alac.m4a"));
}
#[test]
fn test_symphonia_can_decode_alac_stereo() {
decode_some_samples_from(std::path::Path::new("assets/2Channel44.1k_alac.m4a"));
}
#[test]
fn test_audio_sample_source_metadata_accessors() {
use crate::testutil::write_wav_with_bits;
use tempfile::tempdir;
let tempdir = tempdir().unwrap();
let wav_path = tempdir.path().join("meta.wav");
let samples: Vec<i16> = vec![1000, 2000, 3000, 4000]; write_wav_with_bits(wav_path.clone(), vec![samples.clone(), samples], 48000, 16).unwrap();
let source = AudioSampleSource::from_file(&wav_path, None, 1024).unwrap();
assert_eq!(source.channel_count(), 2);
assert_eq!(source.sample_rate(), 48000);
assert_eq!(source.bits_per_sample(), 16);
assert_eq!(source.sample_format(), crate::audio::SampleFormat::Int);
assert!(source.duration().is_some());
}
#[test]
fn test_boxed_sample_source_blanket_impl() {
let inner = MemorySampleSource::new(vec![0.1, 0.2, 0.3], 1, 44100);
let mut boxed: Box<dyn SampleSource> = Box::new(inner);
assert_eq!(boxed.channel_count(), 1);
assert_eq!(boxed.sample_rate(), 44100);
assert_eq!(boxed.bits_per_sample(), 32);
assert_eq!(boxed.sample_format(), crate::audio::SampleFormat::Float);
assert!(boxed.duration().is_some());
assert_eq!(boxed.next_sample().unwrap(), Some(0.1));
assert_eq!(boxed.next_sample().unwrap(), Some(0.2));
assert_eq!(boxed.next_sample().unwrap(), Some(0.3));
assert_eq!(boxed.next_sample().unwrap(), None);
}
#[test]
fn test_channel_mapped_source_next_frame_default() {
let memory = MemorySampleSource::new(vec![0.1, 0.2, 0.3, 0.4, 0.5, 0.6], 2, 44100);
let mut source = ChannelMappedSource::new(
Box::new(memory),
vec![vec!["L".into()], vec!["R".into()]],
2,
);
let mut frame = [0.0f32; 2];
let result = source.next_frame(&mut frame).unwrap();
assert_eq!(result, Some(2));
assert_eq!(frame, [0.1, 0.2]);
let result = source.next_frame(&mut frame).unwrap();
assert_eq!(result, Some(2));
assert_eq!(frame, [0.3, 0.4]);
let result = source.next_frame(&mut frame).unwrap();
assert_eq!(result, Some(2));
assert_eq!(frame, [0.5, 0.6]);
let result = source.next_frame(&mut frame).unwrap();
assert_eq!(result, None);
}
#[test]
fn test_channel_mapped_source_next_frame_buffer_too_small() {
let memory = MemorySampleSource::new(vec![0.1, 0.2], 2, 44100);
let mut source = ChannelMappedSource::new(
Box::new(memory),
vec![vec!["L".into()], vec!["R".into()]],
2,
);
let mut frame = [0.0f32; 1]; let result = source.next_frame(&mut frame);
assert!(result.is_err());
}
#[test]
fn test_channel_mapped_source_read_frames_default() {
let memory = MemorySampleSource::new(vec![0.1, 0.2, 0.3, 0.4], 2, 44100);
let mut source = ChannelMappedSource::new(
Box::new(memory),
vec![vec!["L".into()], vec!["R".into()]],
2,
);
let mut output = [0.0f32; 4];
let frames_read = source.read_frames(&mut output, 2).unwrap();
assert_eq!(frames_read, 2);
assert_eq!(output, [0.1, 0.2, 0.3, 0.4]);
}
#[test]
fn test_channel_mapped_source_read_frames_partial_eof() {
let memory = MemorySampleSource::new(vec![0.5, 0.6], 2, 44100);
let mut source = ChannelMappedSource::new(
Box::new(memory),
vec![vec!["L".into()], vec!["R".into()]],
2,
);
let mut output = [0.0f32; 6];
let frames_read = source.read_frames(&mut output, 3).unwrap();
assert_eq!(frames_read, 1);
assert_eq!(output[0], 0.5);
assert_eq!(output[1], 0.6);
}
#[test]
fn test_channel_mapped_source_is_exhausted_default() {
let memory = MemorySampleSource::new(vec![0.1], 1, 44100);
let source = ChannelMappedSource::new(Box::new(memory), vec![vec!["M".into()]], 1);
assert_eq!(source.is_exhausted(), None);
}
#[test]
fn test_channel_mapped_source_accessors() {
let memory = MemorySampleSource::new(vec![0.1], 1, 44100);
let mappings = vec![vec!["test".to_string()]];
let source = ChannelMappedSource::new(Box::new(memory), mappings.clone(), 1);
assert_eq!(source.source_channel_count(), 1);
assert_eq!(source.channel_mappings(), &mappings);
}
#[test]
fn test_create_channel_mapped_no_transcoding() {
use crate::audio::sample_source::create_channel_mapped_sample_source;
let source = MemorySampleSource::new(vec![0.1, 0.2, 0.3, 0.4], 2, 44100);
let target = TargetFormat::new(44100, crate::audio::SampleFormat::Float, 32).unwrap();
let mappings = vec![vec!["L".into()], vec!["R".into()]];
let mut mapped = create_channel_mapped_sample_source(
Box::new(source),
target,
mappings,
ResamplerType::Sinc,
)
.unwrap();
assert_eq!(mapped.source_channel_count(), 2);
assert_eq!(mapped.next_sample().unwrap(), Some(0.1));
assert_eq!(mapped.next_sample().unwrap(), Some(0.2));
}
#[test]
fn test_create_channel_mapped_with_transcoding() {
use crate::audio::sample_source::create_channel_mapped_sample_source;
let num_samples = 4410; let input: Vec<f32> = (0..num_samples)
.map(|i| (i as f32 * 440.0 * 2.0 * std::f32::consts::PI / 44100.0).sin() * 0.3)
.collect();
let source = MemorySampleSource::new(input, 1, 44100);
let target = TargetFormat::new(48000, crate::audio::SampleFormat::Float, 32).unwrap();
let mappings = vec![vec!["M".into()]];
let mut mapped = create_channel_mapped_sample_source(
Box::new(source),
target,
mappings,
ResamplerType::Sinc,
)
.unwrap();
assert_eq!(mapped.source_channel_count(), 1);
let mut count = 0;
while mapped.next_sample().unwrap().is_some() {
count += 1;
if count > 50000 {
break;
}
}
assert!(count > 3000, "expected transcoded output, got {count}");
}
#[test]
fn test_audio_transcoder_metadata_passthrough() {
let source = MemorySampleSource::new(vec![0.1, 0.2], 1, 44100);
let source_fmt = TargetFormat::new(44100, crate::audio::SampleFormat::Float, 32).unwrap();
let target_fmt = TargetFormat::new(44100, crate::audio::SampleFormat::Float, 32).unwrap();
let transcoder =
AudioTranscoder::new(source, &source_fmt, &target_fmt, 1, ResamplerType::Sinc).unwrap();
assert_eq!(transcoder.channel_count(), 1);
assert_eq!(transcoder.sample_rate(), 44100);
assert_eq!(transcoder.bits_per_sample(), 32);
assert_eq!(
transcoder.sample_format(),
crate::audio::SampleFormat::Float
);
assert!(transcoder.duration().is_some());
}
#[test]
fn test_audio_transcoder_metadata_with_resampling() {
let source = MemorySampleSource::new(vec![0.0; 4800], 2, 48000);
let source_fmt = TargetFormat::new(48000, crate::audio::SampleFormat::Float, 32).unwrap();
let target_fmt = TargetFormat::new(44100, crate::audio::SampleFormat::Float, 32).unwrap();
let transcoder =
AudioTranscoder::new(source, &source_fmt, &target_fmt, 2, ResamplerType::Sinc).unwrap();
assert_eq!(transcoder.channel_count(), 2);
assert_eq!(transcoder.sample_rate(), 44100); assert_eq!(
transcoder.sample_format(),
crate::audio::SampleFormat::Float
);
assert!(transcoder.resampler.is_some());
}
#[test]
fn test_buffered_source_stereo() {
let samples = vec![0.1, -0.1, 0.2, -0.2, 0.3, -0.3, 0.4, -0.4];
let memory = MemorySampleSource::new(samples.clone(), 2, 44100);
let mappings = vec![vec!["L".to_string()], vec!["R".to_string()]];
let inner: Box<dyn ChannelMappedSampleSource + Send + Sync> =
Box::new(ChannelMappedSource::new(Box::new(memory), mappings, 2));
let pool = Arc::new(BufferFillPool::new(1).unwrap());
let mut buffered = BufferedSampleSource::new(inner, pool, 8);
assert_eq!(buffered.source_channel_count(), 2);
assert_eq!(buffered.channel_mappings().len(), 2);
let mut read = Vec::new();
let mut frame = [0.0f32; 2];
while buffered.next_frame(&mut frame).unwrap().is_some() {
read.push(frame[0]);
read.push(frame[1]);
}
assert_eq!(read, samples);
}
#[test]
fn test_buffered_source_next_frame() {
let samples = vec![0.1, 0.2, 0.3, 0.4, 0.5, 0.6];
let memory = MemorySampleSource::new(samples, 2, 44100);
let inner: Box<dyn ChannelMappedSampleSource + Send + Sync> =
Box::new(ChannelMappedSource::new(
Box::new(memory),
vec![vec!["L".into()], vec!["R".into()]],
2,
));
let pool = Arc::new(BufferFillPool::new(1).unwrap());
let mut buffered = BufferedSampleSource::new(inner, pool, 2);
let mut frame = [0.0f32; 2];
let result = buffered.next_frame(&mut frame).unwrap();
assert_eq!(result, Some(2));
assert_eq!(frame, [0.1, 0.2]);
let result = buffered.next_frame(&mut frame).unwrap();
assert_eq!(result, Some(2));
assert_eq!(frame, [0.3, 0.4]);
}
#[test]
fn test_buffered_source_next_frame_output_too_small() {
let memory = MemorySampleSource::new(vec![0.1, 0.2], 2, 44100);
let inner: Box<dyn ChannelMappedSampleSource + Send + Sync> =
Box::new(ChannelMappedSource::new(
Box::new(memory),
vec![vec!["L".into()], vec!["R".into()]],
2,
));
let pool = Arc::new(BufferFillPool::new(1).unwrap());
let mut buffered = BufferedSampleSource::new(inner, pool, 2);
let mut frame = [0.0f32; 1]; let result = buffered.next_frame(&mut frame);
assert!(result.is_err());
}
#[test]
fn test_buffered_source_read_frames() {
let samples: Vec<f32> = (0..20).map(|i| i as f32 * 0.05).collect();
let memory = MemorySampleSource::new(samples.clone(), 2, 44100);
let inner: Box<dyn ChannelMappedSampleSource + Send + Sync> =
Box::new(ChannelMappedSource::new(
Box::new(memory),
vec![vec!["L".into()], vec!["R".into()]],
2,
));
let pool = Arc::new(BufferFillPool::new(1).unwrap());
let mut buffered = BufferedSampleSource::new(inner, pool, 20);
let mut all_output = Vec::new();
let mut buf = vec![0.0f32; 10]; loop {
let frames = buffered.read_frames(&mut buf, 5).unwrap();
if frames == 0 {
break;
}
all_output.extend_from_slice(&buf[..frames * 2]);
}
assert_eq!(all_output.len(), 20);
for (i, &s) in all_output.iter().enumerate() {
assert!(
(s - samples[i]).abs() < 1e-7,
"mismatch at {i}: {s} vs {}",
samples[i]
);
}
}
#[test]
fn test_buffer_fill_pool_creation() {
let pool = BufferFillPool::new(0).unwrap();
let (tx, rx) = std::sync::mpsc::channel();
pool.spawn(move || {
tx.send(42).unwrap();
});
assert_eq!(rx.recv().unwrap(), 42);
}
#[test]
fn test_buffer_fill_pool_multiple_threads() {
let pool = BufferFillPool::new(4).unwrap();
let (tx, rx) = std::sync::mpsc::channel();
for i in 0..8 {
let tx = tx.clone();
pool.spawn(move || {
tx.send(i).unwrap();
});
}
drop(tx);
let mut results: Vec<_> = rx.iter().collect();
results.sort();
assert_eq!(results, (0..8).collect::<Vec<_>>());
}
#[test]
fn test_create_sample_source_from_file_nonexistent() {
let result = create_sample_source_from_file("nonexistent_file.wav", None, 1024);
assert!(result.is_err());
}
#[test]
fn test_create_sample_source_from_file_with_start_time() {
use crate::testutil::write_wav;
use tempfile::tempdir;
let tempdir = tempdir().unwrap();
let wav_path = tempdir.path().join("factory_seek.wav");
let sample_rate = 44100u32;
let samples: Vec<i32> = (0..44100).map(|i| i * 100).collect();
write_wav(wav_path.clone(), vec![samples], sample_rate).unwrap();
let mut source = create_sample_source_from_file(
&wav_path,
Some(std::time::Duration::from_millis(500)),
1024,
)
.unwrap();
assert!(source.sample_rate() > 0);
assert!(source.next_sample().unwrap().is_some());
}
#[test]
fn test_buffered_source_ring_buffer_wraparound() {
let num_samples = 20;
let samples: Vec<f32> = (0..num_samples).map(|i| i as f32 * 0.05).collect();
let memory = MemorySampleSource::new(samples.clone(), 1, 44100);
let inner: Box<dyn ChannelMappedSampleSource + Send + Sync> = Box::new(
ChannelMappedSource::new(Box::new(memory), vec![vec!["M".into()]], 1),
);
let pool = Arc::new(BufferFillPool::new(1).unwrap());
let mut buffered = BufferedSampleSource::new(inner, pool, 8);
let mut read = Vec::new();
while let Some(s) = buffered.next_sample().unwrap() {
read.push(s);
}
assert_eq!(read.len(), num_samples);
for (i, &s) in read.iter().enumerate() {
assert!(
(s - samples[i]).abs() < 1e-7,
"mismatch at {i}: {s} vs {}",
samples[i]
);
}
}
#[test]
fn test_buffered_source_read_frames_wraparound() {
let num_samples = 60; let samples: Vec<f32> = (0..num_samples).map(|i| i as f32 * 0.01).collect();
let memory = MemorySampleSource::new(samples.clone(), 2, 44100);
let inner: Box<dyn ChannelMappedSampleSource + Send + Sync> =
Box::new(ChannelMappedSource::new(
Box::new(memory),
vec![vec!["L".into()], vec!["R".into()]],
2,
));
let pool = Arc::new(BufferFillPool::new(1).unwrap());
let mut buffered = BufferedSampleSource::new(inner, pool, 40);
let mut all_output = Vec::new();
let mut buf = vec![0.0f32; 10]; loop {
let frames = buffered.read_frames(&mut buf, 5).unwrap();
if frames == 0 {
break;
}
all_output.extend_from_slice(&buf[..frames * 2]);
}
assert_eq!(all_output.len(), num_samples);
for (i, &s) in all_output.iter().enumerate() {
assert!(
(s - samples[i]).abs() < 1e-7,
"mismatch at {i}: {s} vs {}",
samples[i]
);
}
}
#[test]
fn test_buffered_source_empty_inner() {
let memory = MemorySampleSource::new(vec![], 1, 44100);
let inner: Box<dyn ChannelMappedSampleSource + Send + Sync> = Box::new(
ChannelMappedSource::new(Box::new(memory), vec![vec!["M".into()]], 1),
);
let pool = Arc::new(BufferFillPool::new(1).unwrap());
let mut buffered = BufferedSampleSource::new(inner, pool, 2);
assert_eq!(buffered.next_sample().unwrap(), None);
assert_eq!(buffered.is_exhausted(), Some(true));
}
#[test]
fn test_audio_transcoder_fft_metadata() {
let source = MemorySampleSource::new(vec![0.0; 4800], 1, 48000);
let source_fmt = TargetFormat::new(48000, crate::audio::SampleFormat::Float, 32).unwrap();
let target_fmt = TargetFormat::new(44100, crate::audio::SampleFormat::Float, 32).unwrap();
let transcoder =
AudioTranscoder::new(source, &source_fmt, &target_fmt, 1, ResamplerType::Fft).unwrap();
assert_eq!(transcoder.channel_count(), 1);
assert_eq!(transcoder.sample_rate(), 44100);
assert!(transcoder.resampler.is_some());
}
#[test]
fn test_audio_transcoder_resampling_failed_error() {
let e =
crate::audio::sample_source::error::SampleSourceError::ResamplingFailed(44100, 48000);
let msg = format!("{e}");
assert!(msg.contains("44100"));
assert!(msg.contains("48000"));
}
#[test]
fn test_channel_mapped_source_next_sample_delegation() {
let memory = MemorySampleSource::new(vec![0.5, -0.5], 1, 44100);
let mut source = ChannelMappedSource::new(Box::new(memory), vec![vec!["mono".into()]], 1);
assert_eq!(source.next_sample().unwrap(), Some(0.5));
assert_eq!(source.next_sample().unwrap(), Some(-0.5));
assert_eq!(source.next_sample().unwrap(), None);
}
#[test]
fn test_audio_sample_source_repeated_eof() {
use crate::testutil::write_wav;
use tempfile::tempdir;
let tempdir = tempdir().unwrap();
let wav_path = tempdir.path().join("repeated_eof.wav");
write_wav(wav_path.clone(), vec![vec![1000i32, 2000]], 44100).unwrap();
let mut source = AudioSampleSource::from_file(&wav_path, None, 1024).unwrap();
while source.next_sample().unwrap().is_some() {}
assert!(source.is_finished());
assert_eq!(source.next_sample().unwrap(), None);
assert_eq!(source.next_sample().unwrap(), None);
}
#[test]
fn test_audio_sample_source_float_wav() {
use crate::testutil::write_wav_with_bits;
use tempfile::tempdir;
let tempdir = tempdir().unwrap();
let wav_path = tempdir.path().join("float32.wav");
let float_samples: Vec<f32> = vec![0.25, -0.5, 0.75, -1.0, 0.0];
write_wav_with_bits(wav_path.clone(), vec![float_samples.clone()], 44100, 32).unwrap();
let mut source = AudioSampleSource::from_file(&wav_path, None, 1024).unwrap();
assert_eq!(source.sample_format(), crate::audio::SampleFormat::Float);
assert_eq!(source.bits_per_sample(), 32);
assert_eq!(source.channel_count(), 1);
let mut read = Vec::new();
while let Some(s) = source.next_sample().unwrap() {
read.push(s);
}
assert_eq!(read.len(), 5);
for (i, (&actual, &expected)) in read.iter().zip(float_samples.iter()).enumerate() {
assert!(
(actual - expected).abs() < 1e-6,
"Float sample {i} mismatch: {actual} vs {expected}"
);
}
}
#[test]
fn test_audio_sample_source_small_buffer_leftover() {
use crate::testutil::write_wav;
use tempfile::tempdir;
let tempdir = tempdir().unwrap();
let wav_path = tempdir.path().join("leftover.wav");
let num_samples = 4096;
let samples: Vec<i32> = (0..num_samples)
.map(|i| {
((i as f32 * 440.0 * 2.0 * std::f32::consts::PI / 44100.0).sin() * 1_000_000.0)
as i32
})
.collect();
write_wav(wav_path.clone(), vec![samples], 44100).unwrap();
let mut source = AudioSampleSource::from_file(&wav_path, None, 1).unwrap();
let mut count = 0;
while source.next_sample().unwrap().is_some() {
count += 1;
}
assert_eq!(
count, num_samples,
"all samples should be read despite tiny buffer"
);
}
#[test]
fn test_audio_sample_source_stereo_small_buffer() {
use crate::testutil::write_wav;
use tempfile::tempdir;
let tempdir = tempdir().unwrap();
let wav_path = tempdir.path().join("stereo_small_buf.wav");
let left: Vec<i32> = (0..200).map(|i| i * 1000).collect();
let right: Vec<i32> = (0..200).map(|i| -(i * 1000)).collect();
write_wav(wav_path.clone(), vec![left, right], 44100).unwrap();
let mut source = AudioSampleSource::from_file(&wav_path, None, 4).unwrap();
assert_eq!(source.channel_count(), 2);
let mut count = 0;
while source.next_sample().unwrap().is_some() {
count += 1;
}
assert_eq!(count, 400); }
#[test]
fn test_fft_resampler_stereo_processing() {
let source_rate = 44100u32;
let target_rate = 48000u32;
let num_frames = 4410;
let mut input = Vec::with_capacity(num_frames * 2);
for i in 0..num_frames {
let t = i as f32 / source_rate as f32;
input.push((2.0 * std::f32::consts::PI * 440.0 * t).sin() * 0.3);
input.push((2.0 * std::f32::consts::PI * 880.0 * t).sin() * 0.3);
}
let source = MemorySampleSource::new(input, 2, source_rate);
let source_fmt =
TargetFormat::new(source_rate, crate::audio::SampleFormat::Float, 32).unwrap();
let target_fmt =
TargetFormat::new(target_rate, crate::audio::SampleFormat::Float, 32).unwrap();
let mut transcoder =
AudioTranscoder::new(source, &source_fmt, &target_fmt, 2, ResamplerType::Fft).unwrap();
assert_eq!(transcoder.channel_count(), 2);
assert_eq!(transcoder.sample_rate(), target_rate);
let mut output = Vec::new();
loop {
match transcoder.next_sample() {
Ok(Some(s)) => output.push(s),
Ok(None) => break,
Err(_) => break,
}
}
let expected_samples =
(num_frames as f64 * target_rate as f64 / source_rate as f64) as usize * 2;
assert!(
output.len() > expected_samples / 2,
"too few output samples: {}",
output.len()
);
let rms = (output.iter().map(|&x| x * x).sum::<f32>() / output.len() as f32).sqrt();
assert!(rms > 0.05, "RMS too low: {rms}");
}
#[test]
fn test_transcoder_passthrough_samples_unchanged() {
let input = vec![0.1, 0.2, 0.3, 0.4, 0.5];
let source = MemorySampleSource::new(input.clone(), 1, 44100);
let fmt = TargetFormat::new(44100, crate::audio::SampleFormat::Float, 32).unwrap();
let mut transcoder =
AudioTranscoder::new(source, &fmt, &fmt, 1, ResamplerType::Sinc).unwrap();
assert!(transcoder.resampler.is_none());
assert_eq!(
transcoder.sample_format(),
crate::audio::SampleFormat::Float
);
let mut output = Vec::new();
loop {
match transcoder.next_sample() {
Ok(Some(s)) => output.push(s),
Ok(None) => break,
Err(_) => break,
}
}
assert_eq!(output, input);
}
#[test]
fn test_transcoder_duration_delegates_to_source() {
let source = MemorySampleSource::new(vec![0.0; 44100], 1, 44100);
let fmt = TargetFormat::new(44100, crate::audio::SampleFormat::Float, 32).unwrap();
let transcoder = AudioTranscoder::new(source, &fmt, &fmt, 1, ResamplerType::Sinc).unwrap();
let dur = transcoder.duration().unwrap();
assert!((dur.as_secs_f64() - 1.0).abs() < 1e-6);
}
#[test]
fn test_buffered_source_channel_mappings_preserved() {
let memory = MemorySampleSource::new(vec![0.0; 4], 2, 44100);
let mappings = vec![
vec!["Left".to_string(), "FL".to_string()],
vec!["Right".to_string()],
];
let inner: Box<dyn ChannelMappedSampleSource + Send + Sync> = Box::new(
ChannelMappedSource::new(Box::new(memory), mappings.clone(), 2),
);
let pool = Arc::new(BufferFillPool::new(1).unwrap());
let buffered = BufferedSampleSource::new(inner, pool, 4);
assert_eq!(buffered.channel_mappings(), &mappings);
assert_eq!(buffered.source_channel_count(), 2);
}
#[test]
fn test_create_channel_mapped_bit_depth_mismatch() {
use crate::audio::sample_source::create_channel_mapped_sample_source;
let source = MemorySampleSource::new(vec![0.5, -0.5, 0.25, -0.25], 1, 44100);
let target = TargetFormat::new(44100, crate::audio::SampleFormat::Int, 16).unwrap();
let mut mapped = create_channel_mapped_sample_source(
Box::new(source),
target,
vec![vec!["M".into()]],
ResamplerType::Sinc,
)
.unwrap();
let mut count = 0;
while mapped.next_sample().unwrap().is_some() {
count += 1;
}
assert_eq!(count, 4);
}
#[test]
fn test_channel_mapped_source_read_frames_mono() {
let memory = MemorySampleSource::new(vec![0.1, 0.2, 0.3, 0.4, 0.5], 1, 44100);
let mut source = ChannelMappedSource::new(Box::new(memory), vec![vec!["M".into()]], 1);
let mut output = [0.0f32; 3];
let frames = source.read_frames(&mut output, 3).unwrap();
assert_eq!(frames, 3);
assert_eq!(output, [0.1, 0.2, 0.3]);
let frames = source.read_frames(&mut output, 3).unwrap();
assert_eq!(frames, 2); assert_eq!(output[0], 0.4);
assert_eq!(output[1], 0.5);
}
use symphonia::core::audio::Channels;
use symphonia::core::audio::{
AsAudioBufferRef, AudioBuffer as SymphAudioBuffer, Signal, SignalSpec,
};
fn mono_spec() -> SignalSpec {
SignalSpec::new(44100, Channels::FRONT_LEFT)
}
fn stereo_spec() -> SignalSpec {
SignalSpec::new(44100, Channels::FRONT_LEFT | Channels::FRONT_RIGHT)
}
#[test]
fn test_decode_buffer_f64() {
let mut buf = SymphAudioBuffer::<f64>::new(3, mono_spec());
buf.render(None, |planes, _| {
planes.planes()[0][0] = 0.25;
planes.planes()[0][1] = -0.5;
planes.planes()[0][2] = 1.0;
Ok(())
})
.unwrap();
let (samples, channels) =
AudioSampleSource::decode_buffer_to_f32(buf.as_audio_buffer_ref()).unwrap();
assert_eq!(channels, 1);
assert_eq!(samples.len(), 3);
assert!((samples[0] - 0.25).abs() < 1e-6);
assert!((samples[1] - (-0.5)).abs() < 1e-6);
assert!((samples[2] - 1.0).abs() < 1e-6);
}
#[test]
fn test_decode_buffer_s8() {
let mut buf = SymphAudioBuffer::<i8>::new(3, mono_spec());
buf.render(None, |planes, _| {
planes.planes()[0][0] = 0;
planes.planes()[0][1] = i8::MAX;
planes.planes()[0][2] = i8::MIN;
Ok(())
})
.unwrap();
let (samples, channels) =
AudioSampleSource::decode_buffer_to_f32(buf.as_audio_buffer_ref()).unwrap();
assert_eq!(channels, 1);
assert_eq!(samples.len(), 3);
assert!((samples[0] - AudioSampleSource::scale_s8(0)).abs() < 1e-6);
assert!((samples[1] - AudioSampleSource::scale_s8(i8::MAX)).abs() < 1e-6);
assert!((samples[2] - AudioSampleSource::scale_s8(i8::MIN)).abs() < 1e-6);
}
#[test]
fn test_decode_buffer_u8() {
let mut buf = SymphAudioBuffer::<u8>::new(3, mono_spec());
buf.render(None, |planes, _| {
planes.planes()[0][0] = 0;
planes.planes()[0][1] = 128;
planes.planes()[0][2] = 255;
Ok(())
})
.unwrap();
let (samples, channels) =
AudioSampleSource::decode_buffer_to_f32(buf.as_audio_buffer_ref()).unwrap();
assert_eq!(channels, 1);
assert_eq!(samples.len(), 3);
assert!((samples[0] - AudioSampleSource::scale_u8(0)).abs() < 1e-6);
assert!((samples[1] - AudioSampleSource::scale_u8(128)).abs() < 1e-6);
assert!((samples[2] - AudioSampleSource::scale_u8(255)).abs() < 1e-6);
}
#[test]
fn test_decode_buffer_u16() {
let mut buf = SymphAudioBuffer::<u16>::new(3, mono_spec());
buf.render(None, |planes, _| {
planes.planes()[0][0] = 0;
planes.planes()[0][1] = u16::MAX / 2;
planes.planes()[0][2] = u16::MAX;
Ok(())
})
.unwrap();
let (samples, channels) =
AudioSampleSource::decode_buffer_to_f32(buf.as_audio_buffer_ref()).unwrap();
assert_eq!(channels, 1);
assert_eq!(samples.len(), 3);
assert!((samples[0] - AudioSampleSource::scale_u16(0)).abs() < 1e-6);
assert!((samples[1] - AudioSampleSource::scale_u16(u16::MAX / 2)).abs() < 1e-6);
assert!((samples[2] - AudioSampleSource::scale_u16(u16::MAX)).abs() < 1e-6);
}
#[test]
fn test_decode_buffer_u24() {
use symphonia::core::sample::u24;
let mut buf = SymphAudioBuffer::<u24>::new(3, mono_spec());
buf.render(None, |planes, _| {
planes.planes()[0][0] = u24(0);
planes.planes()[0][1] = u24((1u32 << 24) / 2);
planes.planes()[0][2] = u24((1u32 << 24) - 1);
Ok(())
})
.unwrap();
let (samples, channels) =
AudioSampleSource::decode_buffer_to_f32(buf.as_audio_buffer_ref()).unwrap();
assert_eq!(channels, 1);
assert_eq!(samples.len(), 3);
assert!((samples[0] - AudioSampleSource::scale_u24(0)).abs() < 1e-6);
assert!((samples[1] - AudioSampleSource::scale_u24((1u32 << 24) / 2)).abs() < 1e-6);
assert!((samples[2] - AudioSampleSource::scale_u24((1u32 << 24) - 1)).abs() < 1e-6);
}
#[test]
fn test_decode_buffer_u32() {
let mut buf = SymphAudioBuffer::<u32>::new(3, mono_spec());
buf.render(None, |planes, _| {
planes.planes()[0][0] = 0;
planes.planes()[0][1] = u32::MAX / 2;
planes.planes()[0][2] = u32::MAX;
Ok(())
})
.unwrap();
let (samples, channels) =
AudioSampleSource::decode_buffer_to_f32(buf.as_audio_buffer_ref()).unwrap();
assert_eq!(channels, 1);
assert_eq!(samples.len(), 3);
assert!((samples[0] - AudioSampleSource::scale_u32(0)).abs() < 1e-6);
assert!((samples[1] - AudioSampleSource::scale_u32(u32::MAX / 2)).abs() < 1e-6);
assert!((samples[2] - AudioSampleSource::scale_u32(u32::MAX)).abs() < 1e-6);
}
#[test]
fn test_decode_buffer_stereo_interleaving() {
let mut buf = SymphAudioBuffer::<f32>::new(2, stereo_spec());
buf.render(None, |planes, _| {
planes.planes()[0][0] = 0.1; planes.planes()[0][1] = 0.3; planes.planes()[1][0] = 0.2; planes.planes()[1][1] = 0.4; Ok(())
})
.unwrap();
let (samples, channels) =
AudioSampleSource::decode_buffer_to_f32(buf.as_audio_buffer_ref()).unwrap();
assert_eq!(channels, 2);
assert_eq!(samples.len(), 4);
assert!((samples[0] - 0.1).abs() < 1e-6);
assert!((samples[1] - 0.2).abs() < 1e-6);
assert!((samples[2] - 0.3).abs() < 1e-6);
assert!((samples[3] - 0.4).abs() < 1e-6);
}
#[test]
fn test_audio_sample_source_mp3_duration() {
let path = std::path::Path::new("assets/1Channel44.1k.mp3");
if !path.exists() {
return; }
let source = AudioSampleSource::from_file(path, None, 1024).unwrap();
let _dur = source.duration();
assert!(source.sample_rate() > 0);
assert!(source.channel_count() > 0);
}
#[test]
fn test_audio_sample_source_aac_duration() {
let path = std::path::Path::new("assets/1Channel44.1k.aac");
if !path.exists() {
return;
}
let source = AudioSampleSource::from_file(path, None, 1024).unwrap();
let _dur = source.duration();
assert!(source.sample_rate() > 0);
assert!(source.channel_count() > 0);
}
#[test]
fn test_audio_sample_source_ogg_duration() {
let path = std::path::Path::new("assets/1Channel44.1k.ogg");
if !path.exists() {
return;
}
let source = AudioSampleSource::from_file(path, None, 1024).unwrap();
let _dur = source.duration();
assert!(source.sample_rate() > 0);
assert!(source.channel_count() > 0);
}
#[test]
fn test_audio_sample_source_channel_detection() {
use crate::testutil::write_wav;
use tempfile::tempdir;
let tempdir = tempdir().unwrap();
let wav_path = tempdir.path().join("detect_channels.wav");
let samples: Vec<i32> = (0..1000).map(|i| i * 100).collect();
write_wav(wav_path.clone(), vec![samples], 44100).unwrap();
std::env::set_var("MTRACK_FORCE_DETECT_CHANNELS", "1");
let mut source = AudioSampleSource::from_file(&wav_path, None, 1024).unwrap();
std::env::remove_var("MTRACK_FORCE_DETECT_CHANNELS");
assert_eq!(source.channel_count(), 1);
let mut count = 0;
while source.next_sample().unwrap().is_some() {
count += 1;
}
assert_eq!(count, 1000);
}
#[test]
fn test_audio_sample_source_invalid_format() {
use std::io::Write;
use tempfile::tempdir;
let tempdir = tempdir().unwrap();
let path = tempdir.path().join("garbage.wav");
let mut f = std::fs::File::create(&path).unwrap();
f.write_all(b"this is not a valid audio file at all")
.unwrap();
drop(f);
let result = AudioSampleSource::from_file(&path, None, 1024);
assert!(result.is_err(), "should fail on invalid audio file");
}
#[test]
fn test_audio_sample_source_duration() {
use crate::testutil::write_wav;
use tempfile::tempdir;
let tempdir = tempdir().unwrap();
let wav_path = tempdir.path().join("duration.wav");
let sample_rate = 44100u32;
let num_samples = 44100; let samples: Vec<i32> = vec![0; num_samples];
write_wav(wav_path.clone(), vec![samples], sample_rate).unwrap();
let source = AudioSampleSource::from_file(&wav_path, None, 1024).unwrap();
let dur = source.duration().unwrap();
assert!(
(dur.as_secs_f64() - 1.0).abs() < 0.01,
"expected ~1s, got {:?}",
dur
);
}
struct ErrorAfterN {
remaining: usize,
channel_count: u16,
}
impl ErrorAfterN {
fn new(n: usize, channels: u16) -> Self {
Self {
remaining: n,
channel_count: channels,
}
}
}
impl ChannelMappedSampleSource for ErrorAfterN {
fn next_sample(
&mut self,
) -> Result<Option<f32>, crate::audio::sample_source::error::SampleSourceError> {
if self.remaining == 0 {
return Err(
crate::audio::sample_source::error::SampleSourceError::SampleConversionFailed(
"test error".into(),
),
);
}
self.remaining -= 1;
Ok(Some(0.5))
}
fn channel_mappings(&self) -> &[Vec<String>] {
static EMPTY: std::sync::OnceLock<Vec<Vec<String>>> = std::sync::OnceLock::new();
EMPTY.get_or_init(|| vec![vec!["M".to_string()]])
}
fn source_channel_count(&self) -> u16 {
self.channel_count
}
}
#[test]
fn test_default_read_frames_error_mid_read() {
let mut source = ErrorAfterN::new(3, 1);
let mut output = [0.0f32; 5];
let frames = source.read_frames(&mut output, 5).unwrap();
assert_eq!(frames, 3);
assert_eq!(output[0], 0.5);
assert_eq!(output[1], 0.5);
assert_eq!(output[2], 0.5);
}
#[test]
fn test_default_read_frames_error_on_first() {
let mut source = ErrorAfterN::new(0, 1);
let mut output = [0.0f32; 3];
let frames = source.read_frames(&mut output, 3).unwrap();
assert_eq!(frames, 0);
}
#[test]
fn test_default_next_frame_with_erroring_source() {
let mut source = ErrorAfterN::new(1, 2);
let mut frame = [0.0f32; 2];
let result = source.next_frame(&mut frame);
assert!(result.is_err());
}
#[test]
fn test_buffered_source_inner_error_treated_as_eof() {
let source = ErrorAfterN::new(4, 1);
let inner: Box<dyn ChannelMappedSampleSource + Send + Sync> = Box::new(source);
let pool = Arc::new(BufferFillPool::new(1).unwrap());
let mut buffered = BufferedSampleSource::new(inner, pool, 8);
let mut count = 0;
while buffered.next_sample().unwrap().is_some() {
count += 1;
}
assert_eq!(count, 4);
assert_eq!(buffered.is_exhausted(), Some(true));
}
#[test]
fn test_sinc_resampler_stereo_44100_to_48000() {
let source_rate = 44100u32;
let target_rate = 48000u32;
let num_frames = 4410;
let mut input = Vec::with_capacity(num_frames * 2);
for i in 0..num_frames {
let t = i as f32 / source_rate as f32;
input.push((2.0 * std::f32::consts::PI * 440.0 * t).sin() * 0.3);
input.push((2.0 * std::f32::consts::PI * 880.0 * t).sin() * 0.3);
}
let source = MemorySampleSource::new(input, 2, source_rate);
let source_fmt =
TargetFormat::new(source_rate, crate::audio::SampleFormat::Float, 32).unwrap();
let target_fmt =
TargetFormat::new(target_rate, crate::audio::SampleFormat::Float, 32).unwrap();
let mut transcoder =
AudioTranscoder::new(source, &source_fmt, &target_fmt, 2, ResamplerType::Sinc).unwrap();
assert!(transcoder.resampler.is_some());
assert_eq!(transcoder.channel_count(), 2);
assert_eq!(transcoder.sample_rate(), target_rate);
assert_eq!(transcoder.bits_per_sample(), 32);
let mut output = Vec::new();
loop {
match transcoder.next_sample() {
Ok(Some(s)) => output.push(s),
Ok(None) => break,
Err(_) => break,
}
}
let expected = (num_frames as f64 * target_rate as f64 / source_rate as f64) as usize * 2;
assert!(output.len() > expected / 2, "too few: {}", output.len());
let rms = (output.iter().map(|&x| x * x).sum::<f32>() / output.len() as f32).sqrt();
assert!(rms > 0.05 && rms < 1.0, "RMS out of range: {rms}");
}
#[test]
fn test_audio_sample_source_large_buffer() {
use crate::testutil::write_wav;
use tempfile::tempdir;
let tempdir = tempdir().unwrap();
let wav_path = tempdir.path().join("large_buf.wav");
let samples: Vec<i32> = (0..100).map(|i| i * 1000).collect();
write_wav(wav_path.clone(), vec![samples], 44100).unwrap();
let mut source = AudioSampleSource::from_file(&wav_path, None, 100000).unwrap();
let mut count = 0;
while source.next_sample().unwrap().is_some() {
count += 1;
}
assert_eq!(count, 100);
}
#[test]
fn test_audio_sample_source_truncated_file() {
use tempfile::tempdir;
let tempdir = tempdir().unwrap();
let flac_data = std::fs::read("assets/1Channel44.1k.flac").unwrap();
let truncated_path = tempdir.path().join("truncated.flac");
let truncate_at = flac_data.len() / 3;
std::fs::write(&truncated_path, &flac_data[..truncate_at]).unwrap();
let result = AudioSampleSource::from_file(&truncated_path, None, 1024);
match result {
Ok(mut source) => {
let mut count = 0;
loop {
match source.next_sample() {
Ok(Some(_)) => count += 1,
Ok(None) => break,
Err(_) => break, }
}
assert!(count > 0, "expected some samples from truncated file");
}
Err(_) => {
}
}
}
#[test]
fn test_audio_sample_source_truncated_wav() {
use crate::testutil::write_wav;
use tempfile::tempdir;
let tempdir = tempdir().unwrap();
let wav_path = tempdir.path().join("full.wav");
let samples: Vec<i32> = (0..10000)
.map(|i| ((i as f64 * 0.1).sin() * 30000.0) as i32)
.collect();
write_wav(wav_path.clone(), vec![samples], 44100).unwrap();
let wav_data = std::fs::read(&wav_path).unwrap();
let truncated_path = tempdir.path().join("truncated.wav");
std::fs::write(&truncated_path, &wav_data[..wav_data.len() / 2]).unwrap();
let result = AudioSampleSource::from_file(&truncated_path, None, 256);
match result {
Ok(mut source) => {
let mut count = 0;
loop {
match source.next_sample() {
Ok(Some(_)) => count += 1,
Ok(None) => break,
Err(_) => break,
}
}
assert!(count > 0);
}
Err(_) => {
}
}
}
}