use crate::{AudioError, AudioResult};
use std::f32::consts::PI;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum AmbisonicOrder {
First,
Second,
Third,
}
impl AmbisonicOrder {
pub fn channel_count(&self) -> usize {
match self {
AmbisonicOrder::First => 4,
AmbisonicOrder::Second => 9,
AmbisonicOrder::Third => 16,
}
}
pub fn max_degree(&self) -> i32 {
match self {
AmbisonicOrder::First => 1,
AmbisonicOrder::Second => 2,
AmbisonicOrder::Third => 3,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct SphericalCoord {
pub azimuth: f32,
pub elevation: f32,
pub distance: f32,
}
impl SphericalCoord {
pub fn new(azimuth: f32, elevation: f32, distance: f32) -> Self {
Self {
azimuth,
elevation,
distance,
}
}
pub fn to_cartesian(&self) -> (f32, f32, f32) {
let x = self.distance * self.elevation.cos() * self.azimuth.sin();
let y = self.distance * self.elevation.cos() * self.azimuth.cos();
let z = self.distance * self.elevation.sin();
(x, y, z)
}
pub fn from_cartesian(x: f32, y: f32, z: f32) -> Self {
let distance = (x * x + y * y + z * z).sqrt();
let azimuth = x.atan2(y);
let elevation = if distance > 0.0 {
(z / distance).asin()
} else {
0.0
};
Self {
azimuth,
elevation,
distance,
}
}
}
fn factorial(n: u32) -> f64 {
(1..=n).map(|x| x as f64).product()
}
fn associated_legendre(l: i32, m: i32, x: f32) -> f32 {
let x = x.clamp(-1.0, 1.0);
let m_abs = m.abs();
if l < m_abs {
return 0.0;
}
if l == 0 && m == 0 {
return 1.0;
}
let mut pmm = 1.0;
if m_abs > 0 {
let somx2 = ((1.0 - x) * (1.0 + x)).sqrt();
let fact = 1.0;
for _i in 1..=m_abs {
pmm *= -fact * somx2;
}
}
if l == m_abs {
return pmm;
}
let mut pmmp1 = x * (2 * m_abs + 1) as f32 * pmm;
if l == m_abs + 1 {
return pmmp1;
}
let mut pll = 0.0;
for ll in (m_abs + 2)..=l {
pll =
((2 * ll - 1) as f32 * x * pmmp1 - (ll + m_abs - 1) as f32 * pmm) / (ll - m_abs) as f32;
pmm = pmmp1;
pmmp1 = pll;
}
pll
}
fn spherical_harmonic_n3d(l: i32, m: i32, azimuth: f32, elevation: f32) -> f32 {
let normalization = if m == 0 {
((2 * l + 1) as f64 / (4.0 * PI as f64)).sqrt() as f32
} else {
let m_abs = m.abs();
let factor = (2.0 * (2 * l + 1) as f64 * factorial((l - m_abs) as u32)
/ factorial((l + m_abs) as u32))
/ (4.0 * PI as f64);
factor.sqrt() as f32
};
let sin_elevation = elevation.sin();
let p_lm = associated_legendre(l, m.abs(), sin_elevation);
let azimuthal = if m > 0 {
(m as f32 * azimuth).cos()
} else if m < 0 {
(m.abs() as f32 * azimuth).sin()
} else {
1.0
};
normalization * p_lm * azimuthal
}
fn acn_index(l: i32, m: i32) -> usize {
(l * (l + 1) + m) as usize
}
pub struct AmbisonicEncoder {
order: AmbisonicOrder,
channel_count: usize,
}
impl AmbisonicEncoder {
pub fn new(order: AmbisonicOrder) -> Self {
let channel_count = order.channel_count();
Self {
order,
channel_count,
}
}
pub fn encode(
&self,
input: &[f32],
direction: SphericalCoord,
output: &mut [Vec<f32>],
) -> AudioResult<()> {
if output.len() != self.channel_count {
return Err(AudioError::InvalidParameter(format!(
"Expected {} output channels, got {}",
self.channel_count,
output.len()
)));
}
for channel in output.iter() {
if channel.len() != input.len() {
return Err(AudioError::InvalidParameter(
"Buffer size mismatch".to_string(),
));
}
}
let coefficients = self.compute_coefficients(direction);
for sample_idx in 0..input.len() {
let sample = input[sample_idx];
for (channel_idx, coefficient) in coefficients.iter().enumerate() {
output[channel_idx][sample_idx] = sample * coefficient;
}
}
Ok(())
}
pub fn compute_coefficients(&self, direction: SphericalCoord) -> Vec<f32> {
let mut coefficients = vec![0.0; self.channel_count];
let max_degree = self.order.max_degree();
for l in 0..=max_degree {
for m in -l..=l {
let idx = acn_index(l, m);
if idx < self.channel_count {
coefficients[idx] =
spherical_harmonic_n3d(l, m, direction.azimuth, direction.elevation);
}
}
}
coefficients
}
pub fn encode_with_distance(
&self,
input: &[f32],
direction: SphericalCoord,
output: &mut [Vec<f32>],
) -> AudioResult<()> {
let attenuation = 1.0 / (direction.distance * direction.distance).max(0.01);
let mut attenuated = vec![0.0; input.len()];
for (i, &sample) in input.iter().enumerate() {
attenuated[i] = sample * attenuation;
}
self.encode(&attenuated, direction, output)
}
pub fn channel_count(&self) -> usize {
self.channel_count
}
}
#[derive(Debug, Clone)]
pub struct SpeakerConfig {
pub positions: Vec<SphericalCoord>,
}
impl SpeakerConfig {
pub fn new(positions: Vec<SphericalCoord>) -> Self {
Self { positions }
}
pub fn stereo() -> Self {
Self {
positions: vec![
SphericalCoord::new(-30.0_f32.to_radians(), 0.0, 1.0),
SphericalCoord::new(30.0_f32.to_radians(), 0.0, 1.0),
],
}
}
pub fn quad() -> Self {
Self {
positions: vec![
SphericalCoord::new(-45.0_f32.to_radians(), 0.0, 1.0),
SphericalCoord::new(45.0_f32.to_radians(), 0.0, 1.0),
SphericalCoord::new(-135.0_f32.to_radians(), 0.0, 1.0),
SphericalCoord::new(135.0_f32.to_radians(), 0.0, 1.0),
],
}
}
pub fn surround_5_1() -> Self {
Self {
positions: vec![
SphericalCoord::new(-30.0_f32.to_radians(), 0.0, 1.0), SphericalCoord::new(30.0_f32.to_radians(), 0.0, 1.0), SphericalCoord::new(0.0, 0.0, 1.0), SphericalCoord::new(0.0, 0.0, 1.0), SphericalCoord::new(-110.0_f32.to_radians(), 0.0, 1.0), SphericalCoord::new(110.0_f32.to_radians(), 0.0, 1.0), ],
}
}
pub fn horizontal_ring(count: usize) -> Self {
let mut positions = Vec::new();
for i in 0..count {
let azimuth = 2.0 * PI * i as f32 / count as f32;
positions.push(SphericalCoord::new(azimuth, 0.0, 1.0));
}
Self { positions }
}
}
pub struct AmbisonicDecoder {
order: AmbisonicOrder,
input_channel_count: usize,
speaker_config: SpeakerConfig,
decode_matrix: Vec<Vec<f32>>,
}
impl AmbisonicDecoder {
pub fn new(order: AmbisonicOrder, speaker_config: SpeakerConfig) -> Self {
let input_channel_count = order.channel_count();
let decode_matrix = Self::compute_decode_matrix(order, &speaker_config);
Self {
order,
input_channel_count,
speaker_config,
decode_matrix,
}
}
fn compute_decode_matrix(
order: AmbisonicOrder,
speaker_config: &SpeakerConfig,
) -> Vec<Vec<f32>> {
let num_speakers = speaker_config.positions.len();
let num_channels = order.channel_count();
let mut encode_matrix = vec![vec![0.0; num_channels]; num_speakers];
let encoder = AmbisonicEncoder::new(order);
for (speaker_idx, position) in speaker_config.positions.iter().enumerate() {
let coefficients = encoder.compute_coefficients(*position);
encode_matrix[speaker_idx] = coefficients;
}
let mut decode_matrix = vec![vec![0.0; num_speakers]; num_channels];
for i in 0..num_channels {
for j in 0..num_speakers {
decode_matrix[i][j] = encode_matrix[j][i];
}
}
let scale = 1.0 / num_speakers as f32;
for row in &mut decode_matrix {
for val in row {
*val *= scale;
}
}
decode_matrix
}
pub fn decode(&self, input: &[Vec<f32>], output: &mut [Vec<f32>]) -> AudioResult<()> {
if input.len() != self.input_channel_count {
return Err(AudioError::InvalidParameter(format!(
"Expected {} input channels, got {}",
self.input_channel_count,
input.len()
)));
}
if output.len() != self.speaker_config.positions.len() {
return Err(AudioError::InvalidParameter(format!(
"Expected {} output channels, got {}",
self.speaker_config.positions.len(),
output.len()
)));
}
let buffer_size = input[0].len();
for channel in input.iter() {
if channel.len() != buffer_size {
return Err(AudioError::InvalidParameter(
"Input buffer size mismatch".to_string(),
));
}
}
for channel in output.iter() {
if channel.len() != buffer_size {
return Err(AudioError::InvalidParameter(
"Output buffer size mismatch".to_string(),
));
}
}
for sample_idx in 0..buffer_size {
for (speaker_idx, speaker_output) in output.iter_mut().enumerate() {
let mut sum = 0.0;
for (channel_idx, channel_input) in input.iter().enumerate() {
sum += self.decode_matrix[channel_idx][speaker_idx] * channel_input[sample_idx];
}
speaker_output[sample_idx] = sum;
}
}
Ok(())
}
pub fn speaker_count(&self) -> usize {
self.speaker_config.positions.len()
}
}
#[derive(Debug, Clone, Copy)]
pub struct RotationAngles {
pub yaw: f32,
pub pitch: f32,
pub roll: f32,
}
impl RotationAngles {
pub fn new(yaw: f32, pitch: f32, roll: f32) -> Self {
Self { yaw, pitch, roll }
}
pub fn identity() -> Self {
Self {
yaw: 0.0,
pitch: 0.0,
roll: 0.0,
}
}
}
pub struct AmbisonicRotator {
rotation: RotationAngles,
}
impl AmbisonicRotator {
pub fn new() -> Self {
Self {
rotation: RotationAngles::identity(),
}
}
pub fn set_rotation(&mut self, rotation: RotationAngles) {
self.rotation = rotation;
}
pub fn rotate_first_order(
&self,
input: &[Vec<f32>],
output: &mut [Vec<f32>],
) -> AudioResult<()> {
if input.len() != 4 || output.len() != 4 {
return Err(AudioError::InvalidParameter(
"First-order Ambisonics requires 4 channels".to_string(),
));
}
let buffer_size = input[0].len();
let (sin_yaw, cos_yaw) = (self.rotation.yaw.sin(), self.rotation.yaw.cos());
let (sin_pitch, cos_pitch) = (self.rotation.pitch.sin(), self.rotation.pitch.cos());
let (sin_roll, cos_roll) = (self.rotation.roll.sin(), self.rotation.roll.cos());
let m11 = cos_yaw * cos_roll - sin_yaw * sin_pitch * sin_roll;
let m12 = -cos_yaw * sin_roll - sin_yaw * sin_pitch * cos_roll;
let m13 = -sin_yaw * cos_pitch;
let m21 = cos_pitch * sin_roll;
let m22 = cos_pitch * cos_roll;
let m23 = -sin_pitch;
let m31 = sin_yaw * cos_roll + cos_yaw * sin_pitch * sin_roll;
let m32 = -sin_yaw * sin_roll + cos_yaw * sin_pitch * cos_roll;
let m33 = cos_yaw * cos_pitch;
for i in 0..buffer_size {
output[0][i] = input[0][i];
let x = input[1][i];
let y = input[2][i];
let z = input[3][i];
output[1][i] = m11 * x + m12 * y + m13 * z; output[2][i] = m21 * x + m22 * y + m23 * z; output[3][i] = m31 * x + m32 * y + m33 * z; }
Ok(())
}
}
impl Default for AmbisonicRotator {
fn default() -> Self {
Self::new()
}
}
pub struct AmbisonicProcessor {
encoder: AmbisonicEncoder,
decoder: AmbisonicDecoder,
rotator: AmbisonicRotator,
order: AmbisonicOrder,
}
impl AmbisonicProcessor {
pub fn new(order: AmbisonicOrder, speaker_config: SpeakerConfig) -> Self {
Self {
encoder: AmbisonicEncoder::new(order),
decoder: AmbisonicDecoder::new(order, speaker_config),
rotator: AmbisonicRotator::new(),
order,
}
}
pub fn encoder(&self) -> &AmbisonicEncoder {
&self.encoder
}
pub fn encoder_mut(&mut self) -> &mut AmbisonicEncoder {
&mut self.encoder
}
pub fn decoder(&self) -> &AmbisonicDecoder {
&self.decoder
}
pub fn decoder_mut(&mut self) -> &mut AmbisonicDecoder {
&mut self.decoder
}
pub fn rotator(&self) -> &AmbisonicRotator {
&self.rotator
}
pub fn rotator_mut(&mut self) -> &mut AmbisonicRotator {
&mut self.rotator
}
pub fn process_source(
&mut self,
input: &[f32],
direction: SphericalCoord,
output: &mut [Vec<f32>],
) -> AudioResult<()> {
let mut ambisonic_buffer: Vec<Vec<f32>> =
vec![vec![0.0; input.len()]; self.order.channel_count()];
self.encoder
.encode(input, direction, &mut ambisonic_buffer)?;
self.decoder.decode(&ambisonic_buffer, output)?;
Ok(())
}
pub fn process_source_with_rotation(
&mut self,
input: &[f32],
direction: SphericalCoord,
rotation: RotationAngles,
output: &mut [Vec<f32>],
) -> AudioResult<()> {
let mut ambisonic_buffer: Vec<Vec<f32>> =
vec![vec![0.0; input.len()]; self.order.channel_count()];
self.encoder
.encode(input, direction, &mut ambisonic_buffer)?;
if self.order == AmbisonicOrder::First {
let mut rotated_buffer = vec![vec![0.0; input.len()]; 4];
self.rotator.set_rotation(rotation);
self.rotator
.rotate_first_order(&ambisonic_buffer, &mut rotated_buffer)?;
ambisonic_buffer = rotated_buffer;
}
self.decoder.decode(&ambisonic_buffer, output)?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ambisonic_order() {
assert_eq!(AmbisonicOrder::First.channel_count(), 4);
assert_eq!(AmbisonicOrder::Second.channel_count(), 9);
assert_eq!(AmbisonicOrder::Third.channel_count(), 16);
}
#[test]
fn test_spherical_coord() {
let coord = SphericalCoord::new(0.0, 0.0, 1.0);
let (x, y, z) = coord.to_cartesian();
assert!(x.abs() < 0.01);
assert!((y - 1.0).abs() < 0.01);
assert!(z.abs() < 0.01);
}
#[test]
fn test_acn_index() {
assert_eq!(acn_index(0, 0), 0); assert_eq!(acn_index(1, -1), 1); assert_eq!(acn_index(1, 0), 2); assert_eq!(acn_index(1, 1), 3); }
#[test]
fn test_spherical_harmonic() {
let y00 = spherical_harmonic_n3d(0, 0, 0.0, 0.0);
assert!(y00 > 0.0);
let y11 = spherical_harmonic_n3d(1, 1, 0.0, 0.0);
assert!(y11 != 0.0);
}
#[test]
fn test_ambisonic_encoder() {
let encoder = AmbisonicEncoder::new(AmbisonicOrder::First);
let direction = SphericalCoord::new(0.0, 0.0, 1.0);
let coefficients = encoder.compute_coefficients(direction);
assert_eq!(coefficients.len(), 4);
assert!(coefficients[0] > 0.0); }
#[test]
fn test_encode_decode() {
let order = AmbisonicOrder::First;
let speaker_config = SpeakerConfig::stereo();
let encoder = AmbisonicEncoder::new(order);
let decoder = AmbisonicDecoder::new(order, speaker_config);
let input = vec![1.0; 100];
let direction = SphericalCoord::new(0.0, 0.0, 1.0);
let mut ambisonic = vec![vec![0.0; 100]; 4];
let result = encoder.encode(&input, direction, &mut ambisonic);
assert!(result.is_ok());
let mut output = vec![vec![0.0; 100]; 2];
let result = decoder.decode(&ambisonic, &mut output);
assert!(result.is_ok());
assert!(output[0].iter().any(|&x| x != 0.0));
assert!(output[1].iter().any(|&x| x != 0.0));
}
#[test]
fn test_rotation() {
let mut rotator = AmbisonicRotator::new();
rotator.set_rotation(RotationAngles::new(PI / 2.0, 0.0, 0.0));
let input = vec![vec![1.0; 10], vec![1.0; 10], vec![0.0; 10], vec![0.0; 10]];
let mut output = vec![vec![0.0; 10], vec![0.0; 10], vec![0.0; 10], vec![0.0; 10]];
let result = rotator.rotate_first_order(&input, &mut output);
assert!(result.is_ok());
assert!((output[0][0] - 1.0).abs() < 0.01);
}
#[test]
fn test_ambisonic_processor() {
let order = AmbisonicOrder::First;
let speaker_config = SpeakerConfig::quad();
let mut processor = AmbisonicProcessor::new(order, speaker_config);
let input = vec![1.0; 100];
let direction = SphericalCoord::new(0.0, 0.0, 1.0);
let mut output = vec![vec![0.0; 100]; 4];
let result = processor.process_source(&input, direction, &mut output);
assert!(result.is_ok());
assert!(output.iter().any(|ch| ch.iter().any(|&x| x != 0.0)));
}
}