Skip to content

Instantly share code, notes, and snippets.

@ChunMinChang
Created October 23, 2019 14:36
Show Gist options
  • Save ChunMinChang/14308c25e08e2e15e86a0e5acf8531ed to your computer and use it in GitHub Desktop.
Save ChunMinChang/14308c25e08e2e15e86a0e5acf8531ed to your computer and use it in GitHub Desktop.
A mixer prototype
#[derive(Clone, Debug, PartialEq)]
enum Channel {
FrontLeft = 0,
FrontRight = 1,
FrontCenter = 2,
LowFrequency = 3,
BackLeft = 4,
BackRight = 5,
FrontLeftOfCenter = 6,
FrontRightOfCenter = 7,
BackCenter = 8,
SideLeft = 9,
SideRight = 10,
TopCenter = 11,
TopFrontLeft = 12,
TopFrontCenter = 13,
TopFrontRight = 14,
TopBackLeft = 15,
TopBackCenter = 16,
TopBackRight = 17,
Silence = 18,
}
const CHANNELS: usize = Channel::Silence as usize + 1;
// A mixer mixing M-channel input data to N-channel output data within defined layouts.
struct Mixer {
input_layout: Vec<Channel>,
output_layout: Vec<Channel>,
matrix: Vec<Vec<f32>>,
}
impl Mixer {
// Create a mixer that can mix M-channel input data to N-channel output data, based on the
// given input and output layout (channel order). Except Silence channel, the channel in
// the layout cannot be duplicate.
// A Silence channel in input_layout means its corresponding input-channel data won't be
// mixed into the output-channel data. A Silence channel in output_layout means its
// corresponding output-channel data will be zero.
fn new(input_layout: Vec<Channel>, output_layout: Vec<Channel>) -> Self {
// Preprocess the mixing matrix.
let matrix = Mixer::generate_matrix(&input_layout, &output_layout);
Self {
input_layout,
output_layout,
matrix,
}
}
// Mix M-channel input data to N-channel output data. The order of the input-channel data must
// be the same of the given input_layout and the order of the calculated output-channel data
// will be the same of the given output_layout.
fn mix(&self, input_buffer: &[f32], output_buffer: &mut [f32]) {
// self.matrix is a NxM-matrix containing mixing coefficients such that
// output[i] = self.matrix[i][j] * input[j] for all j in [0, M), where i is in [0, N).
assert_eq!(self.matrix.len(), output_buffer.len());
for (i, output) in output_buffer.iter_mut().enumerate() {
*output = 0.0;
assert_eq!(self.matrix[i].len(), input_buffer.len());
for (j, input) in input_buffer.iter().enumerate() {
*output += self.matrix[i][j] * input;
}
}
}
// Given a Mx1-matrix input_layout, which stating the order of M input channels,
// and a Nx1 matrix output_layout, which stating the order of N input channels,
// generate a NxM-matrix m with mixing coefficients such that
// output_channel_data[i] = m[i][j] * inpu_channel_data[j] for all j in [0, M),
// where i is in [0, N).
// Instead of representing a vertical Nx1-matrix formally by a 2D-vector with N
// rows vector containing only 1 element, the Nx1-matrix, input_layout and output_layout,
// will be represented by a 1D-vector with N elements for convenience.
fn generate_matrix(input_layout: &[Channel], output_layout: &[Channel]) -> Vec<Vec<f32>> {
assert!(Mixer::validate_layout(&input_layout));
assert!(Mixer::validate_layout(&output_layout));
// Now, generate a CHANNELSxCHANNELS matrix m such that
// output_channel_data[i] = m[i][j] * input_channel_data[j] for all j in [0, CHANNELS),
// where i is in [0, CHANNELS). The channels of input and output is sorted by the order
// defined in enum Channel. That is, the output-channel and input-channel are enum
// Channel(i) and Channel(j) respectively.
let mut matrix = [[0.0; CHANNELS]; CHANNELS];
let silence_index = Channel::Silence as usize;
for (i, row) in matrix.iter_mut().enumerate() {
if i != silence_index {
row[i] = 1.0;
}
}
// TODO: Generate more mixing coefficients to matrix here ...
// Generate a NxM-matrix m such that
// output_channel_data[i] = m[i][j] * inpu_channel_data[j] for all j in [0, M), where i is in [0, N).
// The channels of input and output is defined by input_layout and output_layout.
// That is, output-channel i is output_layout[i] and input-channel j is input_layout[j].
let mut mixing_matrix = vec![vec![0.0; input_layout.len()]; output_layout.len()];
for (i, output_channel) in output_layout.iter().enumerate() {
let output_channel_index = output_channel.clone() as usize;
for (j, input_channel) in input_layout.iter().enumerate() {
let input_channel_index = input_channel.clone() as usize;
mixing_matrix[i][j] = matrix[output_channel_index][input_channel_index];
}
}
// TODO: Normalizing the mixing_matrix ...
mixing_matrix
}
// Except Silence channel, the duplicated channels are not allowed.
fn validate_layout(layout: &[Channel]) -> bool {
let mut map = 0; // Use bitmap as a hash table.
for channel in layout {
if channel == &Channel::Silence {
continue;
}
let bit = 1 << channel.clone() as usize;
if map & bit != 0 {
return false;
}
map |= bit;
}
true
}
}
fn main() {
let input = vec![
Channel::FrontLeft,
Channel::Silence,
Channel::FrontRight,
Channel::FrontCenter,
];
let output = vec![
Channel::Silence,
Channel::FrontRight,
Channel::FrontLeft,
Channel::Silence,
Channel::FrontCenter,
Channel::BackCenter,
];
let matrix = Mixer::generate_matrix(&input, &output);
println!("{:?} = {:?} * {:?}", output, matrix, input);
let input_buffer = [1.0, 2.0, 3.0, 4.0];
let mut output_buffer = vec![0.0; output.len()];
let mixer = Mixer::new(input, output);
mixer.mix(&input_buffer, &mut output_buffer.as_mut_slice());
println!("{:?} = {:?} * {:?}", output_buffer, matrix, input_buffer);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment