use crate::channel::{Channel, ChannelMap};
use std::fmt::Debug;
const CHANNELS: usize = Channel::count();
#[derive(Debug)]
enum Error {
DuplicateNonSilenceChannel,
AsymmetricChannels,
}
#[derive(Debug)]
struct ChannelLayout {
channels: Vec<Channel>,
channel_map: ChannelMap,
}
impl ChannelLayout {
fn new(channels: &[Channel]) -> Result<Self, Error> {
let channel_map = Self::get_channel_map(channels)?;
Ok(Self {
channels: channels.to_vec(),
channel_map,
})
}
fn get_channel_map(channels: &[Channel]) -> Result<ChannelMap, Error> {
let mut map = ChannelMap::empty();
for channel in channels {
let bitmask = ChannelMap::from(*channel);
if channel != &Channel::Silence && map.contains(bitmask) {
return Err(Error::DuplicateNonSilenceChannel);
}
map.insert(bitmask);
}
Ok(map)
}
}
#[derive(Debug)]
pub struct Coefficient<T>
where
T: MixingCoefficient,
T::Coef: Copy,
{
input_layout: ChannelLayout,
output_layout: ChannelLayout,
matrix: Vec<Vec<T::Coef>>,
would_overflow_from_coefficient_value: Option<bool>, }
impl<T> Coefficient<T>
where
T: MixingCoefficient,
T::Coef: Copy,
{
pub fn create(input_channels: &[Channel], output_channels: &[Channel]) -> Self {
let input_layout = ChannelLayout::new(input_channels).expect("Invalid input layout");
let output_layout = ChannelLayout::new(output_channels).expect("Invalid output layout");
let mixing_matrix =
Self::build_mixing_matrix(input_layout.channel_map, output_layout.channel_map)
.unwrap_or_else(|_| Self::get_basic_matrix());
let coefficient_matrix = Self::pick_coefficients(
&input_layout.channels,
&output_layout.channels,
&mixing_matrix,
);
let normalized_matrix = Self::normalize(T::max_coefficients_sum(), coefficient_matrix);
let would_overflow = T::would_overflow_from_coefficient_value(&normalized_matrix);
let matrix = normalized_matrix
.into_iter()
.map(|row| row.into_iter().map(T::coefficient_from_f64).collect())
.collect();
Self {
input_layout,
output_layout,
matrix,
would_overflow_from_coefficient_value: would_overflow,
}
}
pub fn get(&self, input: usize, output: usize) -> T::Coef {
assert!(output < self.matrix.len());
assert!(input < self.matrix[output].len());
self.matrix[output][input] }
pub fn would_overflow_from_coefficient_value(&self) -> Option<bool> {
self.would_overflow_from_coefficient_value
}
pub fn input_channels(&self) -> &[Channel] {
&self.input_layout.channels
}
pub fn output_channels(&self) -> &[Channel] {
&self.output_layout.channels
}
#[allow(clippy::cognitive_complexity)]
fn build_mixing_matrix(
input_map: ChannelMap,
output_map: ChannelMap,
) -> Result<[[f64; CHANNELS]; CHANNELS], Error> {
use std::f64::consts::FRAC_1_SQRT_2;
use std::f64::consts::SQRT_2;
const CENTER_MIX_LEVEL: f64 = FRAC_1_SQRT_2;
const SURROUND_MIX_LEVEL: f64 = FRAC_1_SQRT_2;
const LFE_MIX_LEVEL: f64 = 1.0;
const FRONT_LEFT: usize = Channel::FrontLeft.number();
const FRONT_RIGHT: usize = Channel::FrontRight.number();
const FRONT_CENTER: usize = Channel::FrontCenter.number();
const LOW_FREQUENCY: usize = Channel::LowFrequency.number();
const BACK_LEFT: usize = Channel::BackLeft.number();
const BACK_RIGHT: usize = Channel::BackRight.number();
const FRONT_LEFT_OF_CENTER: usize = Channel::FrontLeftOfCenter.number();
const FRONT_RIGHT_OF_CENTER: usize = Channel::FrontRightOfCenter.number();
const BACK_CENTER: usize = Channel::BackCenter.number();
const SIDE_LEFT: usize = Channel::SideLeft.number();
const SIDE_RIGHT: usize = Channel::SideRight.number();
fn is_symmetric(map: ChannelMap) -> bool {
fn even(map: ChannelMap) -> bool {
map.bits().count_ones() % 2 == 0
}
even(map & ChannelMap::FRONT_2)
&& even(map & ChannelMap::BACK_2)
&& even(map & ChannelMap::FRONT_2_OF_CENTER)
&& even(map & ChannelMap::SIDE_2)
}
if !is_symmetric(input_map) || !is_symmetric(output_map) {
return Err(Error::AsymmetricChannels);
}
let mut matrix = Self::get_basic_matrix();
let unaccounted_input_map = input_map & !output_map;
if unaccounted_input_map.contains(ChannelMap::FRONT_CENTER)
&& output_map.contains(ChannelMap::FRONT_2)
{
let coefficient = if input_map.contains(ChannelMap::FRONT_2) {
CENTER_MIX_LEVEL
} else {
FRAC_1_SQRT_2
};
matrix[FRONT_LEFT][FRONT_CENTER] += coefficient;
matrix[FRONT_RIGHT][FRONT_CENTER] += coefficient;
}
if unaccounted_input_map.contains(ChannelMap::FRONT_2)
&& output_map.contains(ChannelMap::FRONT_CENTER)
{
matrix[FRONT_CENTER][FRONT_LEFT] += FRAC_1_SQRT_2;
matrix[FRONT_CENTER][FRONT_RIGHT] += FRAC_1_SQRT_2;
if input_map.contains(ChannelMap::FRONT_CENTER) {
matrix[FRONT_CENTER][FRONT_CENTER] = CENTER_MIX_LEVEL * SQRT_2;
}
}
if unaccounted_input_map.contains(ChannelMap::BACK_CENTER) {
if output_map.contains(ChannelMap::BACK_2) {
matrix[BACK_LEFT][BACK_CENTER] += FRAC_1_SQRT_2;
matrix[BACK_RIGHT][BACK_CENTER] += FRAC_1_SQRT_2;
} else if output_map.contains(ChannelMap::SIDE_2) {
matrix[SIDE_LEFT][BACK_CENTER] += FRAC_1_SQRT_2;
matrix[SIDE_RIGHT][BACK_CENTER] += FRAC_1_SQRT_2;
} else if output_map.contains(ChannelMap::FRONT_2) {
matrix[FRONT_LEFT][BACK_CENTER] += SURROUND_MIX_LEVEL * FRAC_1_SQRT_2;
matrix[FRONT_RIGHT][BACK_CENTER] += SURROUND_MIX_LEVEL * FRAC_1_SQRT_2;
} else if output_map.contains(ChannelMap::FRONT_CENTER) {
matrix[FRONT_CENTER][BACK_CENTER] += SURROUND_MIX_LEVEL * FRAC_1_SQRT_2;
}
}
if unaccounted_input_map.contains(ChannelMap::BACK_2) {
if output_map.contains(ChannelMap::BACK_CENTER) {
matrix[BACK_CENTER][BACK_LEFT] += FRAC_1_SQRT_2;
matrix[BACK_CENTER][BACK_RIGHT] += FRAC_1_SQRT_2;
} else if output_map.contains(ChannelMap::SIDE_2) {
let coefficient = if input_map.contains(ChannelMap::SIDE_2) {
FRAC_1_SQRT_2
} else {
1.0
};
matrix[SIDE_LEFT][BACK_LEFT] += coefficient;
matrix[SIDE_RIGHT][BACK_RIGHT] += coefficient;
} else if output_map.contains(ChannelMap::FRONT_2) {
matrix[FRONT_LEFT][BACK_LEFT] += SURROUND_MIX_LEVEL;
matrix[FRONT_RIGHT][BACK_RIGHT] += SURROUND_MIX_LEVEL;
} else if output_map.contains(ChannelMap::FRONT_CENTER) {
matrix[FRONT_CENTER][BACK_LEFT] += SURROUND_MIX_LEVEL * FRAC_1_SQRT_2;
matrix[FRONT_CENTER][BACK_RIGHT] += SURROUND_MIX_LEVEL * FRAC_1_SQRT_2;
}
}
if unaccounted_input_map.contains(ChannelMap::SIDE_2) {
if output_map.contains(ChannelMap::BACK_2) {
let coefficient = if input_map.contains(ChannelMap::BACK_2) {
FRAC_1_SQRT_2
} else {
1.0
};
matrix[BACK_LEFT][SIDE_LEFT] += coefficient;
matrix[BACK_RIGHT][SIDE_RIGHT] += coefficient;
} else if output_map.contains(ChannelMap::BACK_CENTER) {
matrix[BACK_CENTER][SIDE_LEFT] += FRAC_1_SQRT_2;
matrix[BACK_CENTER][SIDE_RIGHT] += FRAC_1_SQRT_2;
} else if output_map.contains(ChannelMap::FRONT_2) {
matrix[FRONT_LEFT][SIDE_LEFT] += SURROUND_MIX_LEVEL;
matrix[FRONT_RIGHT][SIDE_RIGHT] += SURROUND_MIX_LEVEL;
} else if output_map.contains(ChannelMap::FRONT_CENTER) {
matrix[FRONT_CENTER][SIDE_LEFT] += SURROUND_MIX_LEVEL * FRAC_1_SQRT_2;
matrix[FRONT_CENTER][SIDE_RIGHT] += SURROUND_MIX_LEVEL * FRAC_1_SQRT_2;
}
}
if unaccounted_input_map.contains(ChannelMap::FRONT_2_OF_CENTER) {
if output_map.contains(ChannelMap::FRONT_2) {
matrix[FRONT_LEFT][FRONT_LEFT_OF_CENTER] += 1.0;
matrix[FRONT_RIGHT][FRONT_RIGHT_OF_CENTER] += 1.0;
} else if output_map.contains(ChannelMap::FRONT_CENTER) {
matrix[FRONT_CENTER][FRONT_LEFT_OF_CENTER] += FRAC_1_SQRT_2;
matrix[FRONT_CENTER][FRONT_RIGHT_OF_CENTER] += FRAC_1_SQRT_2;
}
}
if unaccounted_input_map.contains(ChannelMap::LOW_FREQUENCY) {
if output_map.contains(ChannelMap::FRONT_CENTER) {
matrix[FRONT_CENTER][LOW_FREQUENCY] += LFE_MIX_LEVEL;
} else if output_map.contains(ChannelMap::FRONT_2) {
matrix[FRONT_LEFT][LOW_FREQUENCY] += LFE_MIX_LEVEL * FRAC_1_SQRT_2;
matrix[FRONT_RIGHT][LOW_FREQUENCY] += LFE_MIX_LEVEL * FRAC_1_SQRT_2;
}
}
Ok(matrix)
}
fn get_basic_matrix() -> [[f64; CHANNELS]; CHANNELS] {
const SILENCE: usize = Channel::Silence.number();
let mut matrix = [[0.0; CHANNELS]; CHANNELS];
for (i, row) in matrix.iter_mut().enumerate() {
if i != SILENCE {
row[i] = 1.0;
}
}
matrix
}
fn pick_coefficients(
input_channels: &[Channel],
output_channels: &[Channel],
source: &[[f64; CHANNELS]; CHANNELS],
) -> Vec<Vec<f64>> {
let mut matrix = Vec::with_capacity(output_channels.len());
for output_channel in output_channels {
let output_channel_index = output_channel.clone().number();
let mut coefficients = Vec::with_capacity(input_channels.len());
for input_channel in input_channels {
let input_channel_index = input_channel.clone().number();
coefficients.push(source[output_channel_index][input_channel_index]);
}
matrix.push(coefficients);
}
matrix
}
fn normalize(max_coefficients_sum: f64, mut coefficients: Vec<Vec<f64>>) -> Vec<Vec<f64>> {
let mut max_sum: f64 = 0.0;
for coefs in &coefficients {
max_sum = max_sum.max(coefs.iter().sum());
}
if max_sum != 0.0 && max_sum > max_coefficients_sum {
max_sum /= max_coefficients_sum;
for coefs in &mut coefficients {
for coef in coefs {
*coef /= max_sum;
}
}
}
coefficients
}
}
pub trait MixingCoefficient {
type Coef;
fn max_coefficients_sum() -> f64; fn coefficient_from_f64(value: f64) -> Self::Coef;
fn would_overflow_from_coefficient_value(coefficient: &[Vec<f64>]) -> Option<bool>;
fn to_coefficient_value(value: Self) -> Self::Coef;
fn from_coefficient_value(value: Self::Coef, would_overflow: Option<bool>) -> Self;
}
impl MixingCoefficient for f32 {
type Coef = f32;
fn max_coefficients_sum() -> f64 {
f64::from(std::i32::MAX)
}
fn coefficient_from_f64(value: f64) -> Self::Coef {
value as Self::Coef
}
fn would_overflow_from_coefficient_value(_coefficient: &[Vec<f64>]) -> Option<bool> {
None
}
fn to_coefficient_value(value: Self) -> Self::Coef {
value
}
fn from_coefficient_value(value: Self::Coef, would_overflow: Option<bool>) -> Self {
assert!(would_overflow.is_none());
value
}
}
impl MixingCoefficient for i16 {
type Coef = i32;
fn max_coefficients_sum() -> f64 {
1.0
}
fn coefficient_from_f64(value: f64) -> Self::Coef {
(value * f64::from(1 << 15)).round() as Self::Coef
}
fn would_overflow_from_coefficient_value(coefficient: &[Vec<f64>]) -> Option<bool> {
let mut max_sum: Self::Coef = 0;
for row in coefficient {
let mut sum: Self::Coef = 0;
let mut rem: f64 = 0.0;
for coef in row {
let target = coef * f64::from(1 << 15) + rem;
let value = target.round() as Self::Coef;
rem += target - target.round();
sum += value.abs();
}
max_sum = max_sum.max(sum);
}
Some(max_sum > (1 << 15))
}
fn to_coefficient_value(value: Self) -> Self::Coef {
Self::Coef::from(value)
}
fn from_coefficient_value(value: Self::Coef, would_overflow: Option<bool>) -> Self {
use std::convert::TryFrom;
let would_overflow = would_overflow.expect("would_overflow must have value for i16 type");
let mut converted = (value + (1 << 14)) >> 15;
if would_overflow && ((converted + 0x8000) & !0xFFFF != 0) {
converted = (converted >> 31) ^ 0x7FFF;
}
Self::try_from(converted).expect("Cannot convert coefficient from i32 to i16")
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_create_f32() {
test_create::<f32>(MixDirection::Downmix);
test_create::<f32>(MixDirection::Upmix);
}
#[test]
fn test_create_i16() {
test_create::<i16>(MixDirection::Downmix);
test_create::<i16>(MixDirection::Upmix);
}
fn test_create<T>(direction: MixDirection)
where
T: MixingCoefficient,
T::Coef: Copy + Debug,
{
let (input_channels, output_channels) = get_test_channels(direction);
let coefficient = Coefficient::<T>::create(&input_channels, &output_channels);
println!(
"{:?} = {:?} * {:?}",
output_channels, coefficient.matrix, input_channels
);
}
enum MixDirection {
Downmix,
Upmix,
}
fn get_test_channels(direction: MixDirection) -> (Vec<Channel>, Vec<Channel>) {
let more = vec![
Channel::Silence,
Channel::FrontRight,
Channel::FrontLeft,
Channel::LowFrequency,
Channel::Silence,
Channel::BackCenter,
];
let less = vec![
Channel::FrontLeft,
Channel::Silence,
Channel::FrontRight,
Channel::FrontCenter,
];
match direction {
MixDirection::Downmix => (more, less),
MixDirection::Upmix => (less, more),
}
}
#[test]
fn test_create_with_duplicate_silience_channels_f32() {
test_create_with_duplicate_silience_channels::<f32>()
}
#[test]
fn test_create_with_duplicate_silience_channels_i16() {
test_create_with_duplicate_silience_channels::<i16>()
}
#[test]
#[should_panic]
fn test_create_with_duplicate_input_channels_f32() {
test_create_with_duplicate_input_channels::<f32>()
}
#[test]
#[should_panic]
fn test_create_with_duplicate_input_channels_i16() {
test_create_with_duplicate_input_channels::<i16>()
}
#[test]
#[should_panic]
fn test_create_with_duplicate_output_channels_f32() {
test_create_with_duplicate_output_channels::<f32>()
}
#[test]
#[should_panic]
fn test_create_with_duplicate_output_channels_i16() {
test_create_with_duplicate_output_channels::<i16>()
}
fn test_create_with_duplicate_silience_channels<T>()
where
T: MixingCoefficient,
T::Coef: Copy,
{
let input_channels = [
Channel::FrontLeft,
Channel::Silence,
Channel::FrontRight,
Channel::FrontCenter,
Channel::Silence,
];
let output_channels = [
Channel::Silence,
Channel::FrontRight,
Channel::FrontLeft,
Channel::BackCenter,
Channel::Silence,
];
let _ = Coefficient::<T>::create(&input_channels, &output_channels);
}
fn test_create_with_duplicate_input_channels<T>()
where
T: MixingCoefficient,
T::Coef: Copy,
{
let input_channels = [
Channel::FrontLeft,
Channel::Silence,
Channel::FrontLeft,
Channel::FrontCenter,
];
let output_channels = [
Channel::Silence,
Channel::FrontRight,
Channel::FrontLeft,
Channel::FrontCenter,
Channel::BackCenter,
];
let _ = Coefficient::<T>::create(&input_channels, &output_channels);
}
fn test_create_with_duplicate_output_channels<T>()
where
T: MixingCoefficient,
T::Coef: Copy,
{
let input_channels = [
Channel::FrontLeft,
Channel::Silence,
Channel::FrontRight,
Channel::FrontCenter,
];
let output_channels = [
Channel::Silence,
Channel::FrontRight,
Channel::FrontLeft,
Channel::FrontCenter,
Channel::FrontCenter,
Channel::BackCenter,
];
let _ = Coefficient::<T>::create(&input_channels, &output_channels);
}
#[test]
fn test_get_redirect_matrix_f32() {
test_get_redirect_matrix::<f32>();
}
#[test]
fn test_get_redirect_matrix_i16() {
test_get_redirect_matrix::<i16>();
}
fn test_get_redirect_matrix<T>()
where
T: MixingCoefficient,
T::Coef: Copy + Debug + PartialEq,
{
fn compute_redirect_matrix<T>(
input_channels: &[Channel],
output_channels: &[Channel],
) -> Vec<Vec<T::Coef>>
where
T: MixingCoefficient,
{
let mut matrix = Vec::with_capacity(output_channels.len());
for output_channel in output_channels {
let mut row = Vec::with_capacity(input_channels.len());
for input_channel in input_channels {
row.push(
if input_channel != output_channel
|| input_channel == &Channel::Silence
|| output_channel == &Channel::Silence
{
0.0
} else {
1.0
},
);
}
matrix.push(row);
}
matrix
.into_iter()
.map(|row| row.into_iter().map(T::coefficient_from_f64).collect())
.collect()
}
let input_channels = [
Channel::FrontLeft,
Channel::Silence,
Channel::FrontRight,
Channel::FrontCenter,
];
let output_channels = [
Channel::Silence,
Channel::FrontLeft,
Channel::Silence,
Channel::FrontCenter,
Channel::BackCenter,
];
let coefficient = Coefficient::<T>::create(&input_channels, &output_channels);
let expected = compute_redirect_matrix::<T>(&input_channels, &output_channels);
assert_eq!(coefficient.matrix, expected);
println!(
"{:?} = {:?} * {:?}",
output_channels, coefficient.matrix, input_channels
);
}
#[test]
fn test_normalize() {
use float_cmp::approx_eq;
let m = vec![
vec![1.0_f64, 2.0_f64, 3.0_f64],
vec![4.0_f64, 6.0_f64, 10.0_f64],
];
let mut max_row_sum: f64 = std::f64::MIN;
for row in &m {
max_row_sum = max_row_sum.max(row.iter().sum());
}
let n = Coefficient::<f32>::normalize(max_row_sum, m.clone());
assert_eq!(n, m);
let smaller_max = max_row_sum - 0.5_f64;
assert!(smaller_max > 0.0_f64);
let n = Coefficient::<f32>::normalize(smaller_max, m);
let mut max_row_sum: f64 = std::f64::MIN;
for row in &n {
max_row_sum = max_row_sum.max(row.iter().sum());
assert!(row.iter().sum::<f64>() <= smaller_max);
}
assert!(approx_eq!(f64, smaller_max, max_row_sum));
}
}