use crate::dsp::audio_buffer::{AudioBuffer, AudioEffectState};
use crate::dsp::speaker_layout::{SpeakerLayout, SpeakerLayoutType};
use glam::Vec3;
use std::f32::consts::PI;
#[expect(dead_code)]
#[derive(Default)]
struct PanningData {
speaker_indices: [i32; 2],
angle_between_speakers: f32,
delta_phi: f32,
}
#[derive(Default, Debug, Copy, Clone)]
pub struct PanningEffectParameters {
pub direction: Vec3,
}
#[derive(Debug)]
pub struct PanningEffect {
speaker_layout: SpeakerLayout,
prev_direction: Vec3,
}
impl PanningEffect {
pub fn new(layout: SpeakerLayoutType) -> Self {
Self {
speaker_layout: SpeakerLayout::new(layout),
prev_direction: Vec3::ZERO,
}
}
#[expect(dead_code)]
pub(crate) fn reset(&mut self) {
self.prev_direction = Vec3::ZERO;
}
pub fn apply(
&mut self,
parameters: PanningEffectParameters,
input: &AudioBuffer<1>,
output: &mut AudioBuffer<2>,
) -> AudioEffectState {
let panning_data = PanningData::default();
let prev_panning_data = PanningData::default();
for i in 0..output.num_channels() {
let weight =
Self::panning_weight(parameters.direction, &self.speaker_layout, i, &panning_data);
let weight_prev = Self::panning_weight(
self.prev_direction,
&self.speaker_layout,
i,
&prev_panning_data,
);
for j in 0..input.num_samples() {
let alpha = (i as f32) / (input[0].len() as f32);
let blended_weight = alpha * weight + (1.0 - alpha) * weight_prev;
output[i][j] = blended_weight * input[0][j];
}
}
self.prev_direction = parameters.direction;
AudioEffectState::TailComplete
}
fn panning_weight(
direction: Vec3,
speaker_layout: &SpeakerLayout,
index: usize,
_panning_data: &PanningData, ) -> f32 {
match speaker_layout.layout_type {
SpeakerLayoutType::Mono => 1.0,
SpeakerLayoutType::Stereo => Self::stereo_panning_weight(direction, index),
SpeakerLayoutType::Quadraphonic => {
todo!()
}
SpeakerLayoutType::FivePointOne => {
todo!()
}
SpeakerLayoutType::SevenPointOne => {
todo!()
}
SpeakerLayoutType::Custom => {
todo!()
}
}
}
fn stereo_panning_weight(direction: Vec3, channel: usize) -> f32 {
let horizontal = direction.normalize_or_zero();
let p = horizontal.x;
let q = (p + 1.0) * (PI / 4.0);
if channel == 0 {
q.cos()
} else {
q.sin()
}
}
}