1use std::f32::consts::PI;
13
14use lv2::prelude::*;
15
16use nalgebra::{linalg::SVD, Matrix3, Vector3};
17
18use rustfft::{num_complex::Complex, num_traits::Zero, FftPlanner};
19
20use std::sync::Mutex;
21
22const C: f32 = 343.00; #[derive(Copy, Clone)]
27struct ElemDistance {
28 x: f32,
29 y: f32,
30}
31
32fn analytic_signal(planner: &mut FftPlanner<f32>, signal: &[f32], len: usize) -> Vec<Complex<f32>> {
36 let mut complex_signal: Vec<Complex<f32>> =
38 signal.iter().map(|&x| Complex::new(x, 0.0)).collect();
39
40 let fft = planner.plan_fft_forward(len);
42 let ifft = planner.plan_fft_inverse(len);
43
44 fft.process(&mut complex_signal);
46
47 for i in 0..len {
51 if i > 0 && i < len / 2 {
52 complex_signal[i] *= Complex::new(2.0, 0.0);
53 } else if i >= len / 2 {
54 complex_signal[i] = Complex::zero();
55 }
56 }
57
58 ifft.process(&mut complex_signal);
60 complex_signal.iter_mut().for_each(|x| *x /= len as f32);
61
62 complex_signal
63}
64
65fn steering_vec(theta: f32, phi: f32, f: f32, elems: [ElemDistance; 3]) -> Vector3<Complex<f32>> {
69 let mic_positions: Vec<Vector3<f32>> =
71 elems.iter().map(|e| Vector3::new(e.x, e.y, 0f32)).collect();
72
73 let repetency = (2f32 * PI) / (f / C);
75
76 let u_dir = Vector3::new(phi.sin() * theta.cos(), phi.sin() * theta.sin(), phi.cos());
78
79 let mut steering_vector = Vector3::from_element(Complex::new(0f32, 0f32));
82
83 for (i, mic_pos) in mic_positions.iter().enumerate() {
84 let delay = mic_pos.dot(&u_dir) / C;
85 let phase = -repetency * delay;
86 steering_vector[i] = Complex::new(phase.cos(), phase.sin());
87 }
88
89 steering_vector
90}
91
92fn covariance(signals: &Vec<Vec<Complex<f32>>>, n_samples: usize) -> Matrix3<Complex<f32>> {
95 let mut covar = Matrix3::zeros();
96
97 for t in 0..n_samples {
98 let discrete: Vector3<Complex<f32>> = Vector3::from_iterator(signals.iter().map(|s| s[t]));
99 covar += &discrete * discrete.adjoint();
100 }
101
102 let reg = Matrix3::identity().map(|x: f32| Complex::new(x * 1e-4f32, 0f32));
106
107 covar /= Complex::new(n_samples as f32, 0f32);
108 covar + reg
109}
110
111fn mvdr_weights(cov: &Matrix3<Complex<f32>>, sv: &Vector3<Complex<f32>>) -> Vector3<Complex<f32>> {
118 let svd = SVD::new(cov.to_owned(), true, true);
121 let r_inv = svd.pseudo_inverse(1e-4f32).unwrap();
122
123 let num = r_inv * sv;
124 let den = sv.dotc(&num); num / den
127}
128
129#[derive(PortCollection)]
144pub struct Ports {
145 in_1: InputPort<Audio>,
146 in_2: InputPort<Audio>,
147 in_3: InputPort<Audio>,
148 out: OutputPort<Audio>,
149 h_angle: InputPort<Control>,
150 v_angle: InputPort<Control>,
151 opt_freq: InputPort<Control>,
152 t_win: InputPort<Control>,
153 mic2_x: InputPort<Control>,
154 mic2_y: InputPort<Control>,
155 mic3_x: InputPort<Control>,
156 mic3_y: InputPort<Control>,
157}
158
159#[uri("https://chadmed.au/triforce")]
163pub struct Triforce {
164 hangle_curr: f32,
165 vangle_curr: f32,
166 freq_curr: f32,
167 sample_rate: f32,
168 samples_since_last_update: usize,
169 covar_window: Vec<Vec<Complex<f32>>>,
170 steering_vector: Vector3<Complex<f32>>,
171 covar: Matrix3<Complex<f32>>,
172 array_geom: [ElemDistance; 3],
173 fft_planner: Mutex<FftPlanner<f32>>,
174 weights: Vector3<Complex<f32>>,
175}
176
177trait Beamformer: Plugin {
178 fn update_params(&mut self, ports: &mut Ports);
179}
180
181impl Triforce {
182 pub fn with_sample_rate(sample_rate: f32) -> Self {
183 Self {
184 hangle_curr: 0f32,
185 vangle_curr: 0f32,
186 freq_curr: 1000f32,
187 samples_since_last_update: usize::max_value(),
188 sample_rate,
189 covar_window: vec![
190 vec![Complex::new(0f32, 0f32); 256],
191 vec![Complex::new(0f32, 0f32); 256],
192 vec![Complex::new(0f32, 0f32); 256],
193 ],
194 array_geom: [ElemDistance { x: 0f32, y: 0f32 }; 3],
195 steering_vector: steering_vec(
196 90f32.to_radians(),
197 45f32.to_radians(),
198 1000f32,
199 [ElemDistance { x: 0f32, y: 0f32 }; 3],
200 ),
201 covar: Matrix3::zeros(),
202 fft_planner: Mutex::new(FftPlanner::new()),
203 weights: Vector3::zeros(),
204 }
205 }
206
207 pub fn process_slice(
208 &mut self,
209 mic1: &[f32],
210 mic2: &[f32],
211 mic3: &[f32],
212 output: &mut [f32],
213 t_win: f32,
214 buf_len: usize
215 ) {
216 let inputs = {
218 let mut planner = self.fft_planner.lock().unwrap();
219 vec![
220 analytic_signal(&mut planner, mic1, buf_len),
221 analytic_signal(&mut planner, mic2, buf_len),
222 analytic_signal(&mut planner, mic3, buf_len),
223 ]
224 };
225
226 if self.samples_since_last_update as f32 >= (t_win / 1000f32) * self.sample_rate {
229 self.samples_since_last_update = 0;
230 let i = buf_len / 3;
232 self.covar_window[0].extend_from_slice(&inputs[0][0..i]);
233 self.covar_window[1].extend_from_slice(&inputs[1][0..i]);
234 self.covar_window[2].extend_from_slice(&inputs[2][0..i]);
235 self.covar = covariance(&self.covar_window, self.covar_window[0].len());
236 self.covar_window[0] = inputs[0][i + 1..buf_len].to_vec();
237 self.covar_window[1] = inputs[1][i + 1..buf_len].to_vec();
238 self.covar_window[2] = inputs[2][i + 1..buf_len].to_vec();
239 self.weights = mvdr_weights(&self.covar, &self.steering_vector);
240 } else {
241 self.samples_since_last_update += buf_len;
242 }
243
244 for t in 0..buf_len {
245 let discrete: Vector3<Complex<f32>> =
246 Vector3::from_iterator(inputs.iter().map(|s| s[t]));
247
248 let out =
249 self.weights.dotc(&discrete)
251 .re;
253
254 output[t] = if out.is_finite() && !out.is_nan() {
256 out.clamp(-10f32, 10f32)
257 } else {
258 0f32
259 };
260 }
261 }
262}
263
264impl Plugin for Triforce {
265 type Ports = Ports;
266
267 type InitFeatures = ();
268 type AudioFeatures = ();
269
270 fn new(info: &PluginInfo, _features: &mut ()) -> Option<Self> {
271 Some(Self::with_sample_rate(info.sample_rate() as f32))
272 }
273
274 fn run(&mut self, ports: &mut Ports, _features: &mut (), samples: u32) {
275 Beamformer::update_params(self, ports);
276 self.process_slice(
277 &ports.in_1,
278 &ports.in_2,
279 &ports.in_3,
280 &mut ports.out,
281 *ports.t_win,
282 samples as usize
283 );
284 }
285}
286
287impl Beamformer for Triforce {
288 fn update_params(&mut self, ports: &mut Ports) {
289 if self.hangle_curr != *ports.h_angle
290 || self.freq_curr != *ports.opt_freq
291 || self.vangle_curr != *ports.v_angle
292 || self.array_geom[1].x != *ports.mic2_x
293 || self.array_geom[1].y != *ports.mic2_y
294 || self.array_geom[2].x != *ports.mic3_x
295 || self.array_geom[2].y != *ports.mic3_y
296 {
297 self.hangle_curr = *ports.h_angle;
298 self.vangle_curr = *ports.v_angle;
299 self.freq_curr = *ports.opt_freq;
300 self.array_geom = [
301 ElemDistance { x: 0f32, y: 0f32 },
302 ElemDistance {
303 x: *ports.mic2_x,
304 y: *ports.mic2_y,
305 },
306 ElemDistance {
307 x: *ports.mic3_x,
308 y: *ports.mic3_y,
309 },
310 ];
311 self.steering_vector = steering_vec(
312 self.hangle_curr.to_radians(),
313 self.vangle_curr.to_radians(),
314 self.freq_curr,
315 self.array_geom,
316 );
317
318 self.weights = mvdr_weights(&self.covar, &self.steering_vector);
320 }
321 }
322}
323
324lv2_descriptors!(Triforce);