use symphonia::core::{
audio::{AudioBuffer, Signal},
conv::{FromSample, IntoSample},
sample::Sample,
};
pub fn mix_volume<S>(input: &mut AudioBuffer<S>, volume: f64)
where
S: Sample + FromSample<f32> + IntoSample<f32>,
{
let channels = input.spec().channels.count();
let frames = input.frames();
for c in 0..channels {
let src = input.chan_mut(c);
for x in src {
#[allow(clippy::cast_possible_truncation)]
let s: f32 = (f64::from((*x).into_sample()) * volume) as f32;
*x = s.into_sample();
}
}
log::trace!(
"Volume mixer: applied volume {volume:.3} to {frames} frames with {channels} channels"
);
}
#[cfg(test)]
mod tests {
use super::*;
use symphonia::core::audio::{Channels, Layout, SignalSpec};
#[test_log::test]
fn test_mix_volume_reduces_amplitude() {
let spec = SignalSpec::new(48000, Channels::FRONT_LEFT | Channels::FRONT_RIGHT);
let mut buffer = AudioBuffer::<f32>::new(100, spec);
for channel in 0..buffer.spec().channels.count() {
for sample in buffer.chan_mut(channel) {
*sample = 0.5;
}
}
mix_volume(&mut buffer, 0.5);
for channel in 0..buffer.spec().channels.count() {
for sample in buffer.chan(channel) {
assert!(
(*sample - 0.25).abs() < 0.001,
"Expected ~0.25, got {sample}"
);
}
}
}
#[test_log::test]
fn test_mix_volume_zero_mutes_audio() {
let spec = SignalSpec::new(44100, Channels::FRONT_LEFT);
let mut buffer = AudioBuffer::<f32>::new(50, spec);
for sample in buffer.chan_mut(0) {
*sample = 0.8;
}
mix_volume(&mut buffer, 0.0);
for sample in buffer.chan(0) {
assert!(sample.abs() < 0.001, "Expected ~0.0 (muted), got {sample}");
}
}
#[test_log::test]
fn test_mix_volume_amplification() {
let spec = SignalSpec::new(48000, Channels::FRONT_LEFT | Channels::FRONT_RIGHT);
let mut buffer = AudioBuffer::<f32>::new(100, spec);
for channel in 0..buffer.spec().channels.count() {
for sample in buffer.chan_mut(channel) {
*sample = 0.1;
}
}
mix_volume(&mut buffer, 2.0);
for channel in 0..buffer.spec().channels.count() {
for sample in buffer.chan(channel) {
assert!((*sample - 0.2).abs() < 0.001, "Expected ~0.2, got {sample}");
}
}
}
#[test_log::test]
fn test_mix_volume_unity_gain_no_change() {
let spec = SignalSpec::new(44100, Channels::FRONT_LEFT);
let mut buffer = AudioBuffer::<f32>::new(50, spec);
for sample in buffer.chan_mut(0) {
*sample = 0.7;
}
mix_volume(&mut buffer, 1.0);
for sample in buffer.chan(0) {
assert!(
(*sample - 0.7).abs() < 0.001,
"Expected ~0.7 (unchanged), got {sample}"
);
}
}
#[test_log::test]
fn test_mix_volume_multichannel_independence() {
let spec = SignalSpec::new(48000, Layout::FivePointOne.into_channels());
let mut buffer = AudioBuffer::<f32>::new(100, spec);
let initial_values = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6];
for (channel_idx, &initial_value) in initial_values.iter().enumerate() {
if channel_idx < buffer.spec().channels.count() {
for sample in buffer.chan_mut(channel_idx) {
*sample = initial_value;
}
}
}
mix_volume(&mut buffer, 0.5);
for (channel_idx, &initial_value) in initial_values.iter().enumerate() {
if channel_idx < buffer.spec().channels.count() {
let expected = initial_value * 0.5;
for sample in buffer.chan(channel_idx) {
assert!(
(*sample - expected).abs() < 0.001,
"Channel {channel_idx}: Expected ~{expected}, got {sample}"
);
}
}
}
}
#[test_log::test]
fn test_mix_volume_handles_negative_samples() {
let spec = SignalSpec::new(44100, Channels::FRONT_LEFT);
let mut buffer = AudioBuffer::<f32>::new(50, spec);
for sample in buffer.chan_mut(0) {
*sample = -0.6;
}
mix_volume(&mut buffer, 0.5);
for sample in buffer.chan(0) {
assert!(
(*sample - (-0.3)).abs() < 0.001,
"Expected ~-0.3, got {sample}"
);
}
}
}