Skip to main content

rlx_aec/
delay.rs

1// RLX — versatile ML compiler + runtime.
2// Copyright (C) 2026 Eugene Hauptmann, Nataliya Kosmyna.
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, version 3.
7//
8// This program is distributed in the hope that it will be useful,
9// but WITHOUT ANY WARRANTY; without even the implied warranty of
10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11// GNU General Public License for more details.
12//
13// You should have received a copy of the GNU General Public License
14// along with this program. If not, see <https://www.gnu.org/licenses/>.
15
16//! Delay estimation and far-end ring buffer.
17
18/// Ring buffer for asynchronous far-end reference playback.
19#[derive(Debug, Clone)]
20pub struct ReferenceRing {
21    buf: Vec<f32>,
22    cap: usize,
23    write_pos: usize,
24    len: usize,
25}
26
27impl ReferenceRing {
28    pub fn new(max_delay_samples: usize) -> Self {
29        let cap = max_delay_samples.max(160) + 4096;
30        Self {
31            buf: vec![0.0; cap],
32            cap,
33            write_pos: 0,
34            len: 0,
35        }
36    }
37
38    pub fn push(&mut self, samples: &[f32]) {
39        for &s in samples {
40            self.buf[self.write_pos] = s;
41            self.write_pos = (self.write_pos + 1) % self.cap;
42            self.len = self.len.saturating_add(1).min(self.cap);
43        }
44    }
45
46    pub fn clear(&mut self) {
47        self.len = 0;
48        self.write_pos = 0;
49    }
50
51    /// Read `out_len` samples ending `delay_samples` before the write head.
52    pub fn read_delayed(&self, delay_samples: usize, out_len: usize, out: &mut [f32]) {
53        let n = out.len().min(out_len);
54        for i in 0..n {
55            let age = delay_samples + (out_len - n) + i;
56            out[i] = self.sample_at_age(age);
57        }
58    }
59
60    fn sample_at_age(&self, age: usize) -> f32 {
61        if self.len == 0 || age >= self.len {
62            return 0.0;
63        }
64        let idx = (self.write_pos + self.cap - 1 - age) % self.cap;
65        self.buf[idx]
66    }
67}
68
69/// Estimate acoustic delay (samples): far leads mic by `lag` samples.
70pub fn estimate_delay_samples(far: &[f32], mic: &[f32], _n_fft: usize, max_delay: usize) -> usize {
71    let n = far.len().min(mic.len());
72    if n < 64 {
73        return 0;
74    }
75    let search = max_delay.min(n - 1);
76    let mut best_lag = 0usize;
77    let mut best = -1e30f32;
78    for lag in 0..=search {
79        let mut sum = 0.0f32;
80        for i in lag..n {
81            sum += far[i - lag] * mic[i];
82        }
83        if sum > best {
84            best = sum;
85            best_lag = lag;
86        }
87    }
88    best_lag
89}