libflo_audio/lossless/
lpc.rs

1/// Calculate autocorrelation coefficients
2pub fn autocorrelation(samples: &[f32], max_lag: usize) -> Vec<f32> {
3    let n = samples.len();
4    let mut autocorr = vec![0.0; max_lag + 1];
5
6    for lag in 0..=max_lag {
7        let mut sum = 0.0;
8        for i in 0..(n.saturating_sub(lag)) {
9            sum += samples[i] * samples[i + lag];
10        }
11        autocorr[lag] = sum;
12    }
13
14    autocorr
15}
16
17/// Levinson-Durbin algorithm for LPC coefficient calculation
18pub fn levinson_durbin(autocorr: &[f32], order: usize) -> Vec<f32> {
19    if order == 0 || autocorr.is_empty() {
20        return vec![];
21    }
22
23    let mut coeffs = vec![0.0; order];
24    let mut prev = vec![0.0; order];
25    let mut error = autocorr[0];
26
27    if error.abs() < 1e-10 {
28        error = 1e-10;
29    }
30
31    for i in 0..order {
32        let mut lambda = autocorr.get(i + 1).copied().unwrap_or(0.0);
33        for j in 0..i {
34            lambda -= coeffs[j] * autocorr.get(i - j).copied().unwrap_or(0.0);
35        }
36        lambda /= error;
37        lambda = lambda.clamp(-0.999, 0.999);
38
39        prev.copy_from_slice(&coeffs);
40
41        coeffs[i] = lambda;
42        for j in 0..i {
43            coeffs[j] = prev[j] - lambda * prev[i - 1 - j];
44        }
45
46        error *= 1.0 - lambda * lambda;
47        if error.abs() < 1e-10 {
48            error = 1e-10;
49        }
50    }
51
52    coeffs
53}
54
55/// Calculate prediction residuals
56pub fn calculate_residuals(samples: &[f32], coeffs: &[f32]) -> Vec<f32> {
57    let order = coeffs.len();
58    let mut residuals = Vec::with_capacity(samples.len());
59
60    // First 'order' samples stored as-is (warm-up)
61    for i in 0..order.min(samples.len()) {
62        residuals.push(samples[i]);
63    }
64
65    // Remaining samples: residual = actual - predicted
66    for i in order..samples.len() {
67        let mut prediction = 0.0;
68        for (j, &coeff) in coeffs.iter().enumerate() {
69            prediction += coeff * samples[i - j - 1];
70        }
71        residuals.push(samples[i] - prediction);
72    }
73
74    residuals
75}
76
77/// Reconstruct samples from coefficients and residuals
78pub fn reconstruct_samples(coeffs: &[f32], residuals: &[f32], target_len: usize) -> Vec<f32> {
79    let order = coeffs.len();
80    let mut samples = Vec::with_capacity(target_len);
81
82    // First 'order' samples from residuals (warm-up)
83    for i in 0..order.min(residuals.len()) {
84        samples.push(residuals[i]);
85    }
86
87    // Reconstruct remaining: sample = prediction + residual
88    for i in order..target_len.min(residuals.len()) {
89        let mut prediction = 0.0;
90        for (j, &coeff) in coeffs.iter().enumerate() {
91            if i > j {
92                prediction += coeff * samples[i - j - 1];
93            }
94        }
95        samples.push(prediction + residuals[i]);
96    }
97
98    // Pad with zeros if needed
99    while samples.len() < target_len {
100        samples.push(0.0);
101    }
102
103    samples
104}
105
106/// Quantize floating-point coefficients to integers
107pub fn quantize_coefficients(coeffs: &[f32]) -> (Vec<i32>, u8) {
108    if coeffs.is_empty() {
109        return (vec![], 0);
110    }
111
112    let max_val = coeffs.iter().map(|&c| c.abs()).fold(0.0f32, f32::max);
113
114    let shift_bits = if max_val > 0.0 && max_val.is_finite() {
115        let ratio = 2147483647.0f32 / max_val;
116        if ratio > 1.0 {
117            (ratio.log2().floor() as i32).clamp(0, 28) as u8
118        } else {
119            0
120        }
121    } else {
122        15
123    };
124
125    let scale = if shift_bits < 31 {
126        (1u32 << shift_bits) as f32
127    } else {
128        2147483648.0
129    };
130    let quantized: Vec<i32> = coeffs.iter().map(|&c| (c * scale).round() as i32).collect();
131
132    (quantized, shift_bits)
133}
134
135/// Dequantize integer coefficients back to floats
136pub fn dequantize_coefficients(coeffs: &[i32], shift_bits: u8) -> Vec<f32> {
137    let scale = if shift_bits < 31 {
138        1.0 / (1u32 << shift_bits) as f32
139    } else {
140        1.0 / 2147483648.0
141    };
142    coeffs.iter().map(|&c| c as f32 * scale).collect()
143}
144
145/// Check if LPC coefficients represent a stable filter
146/// A filter is stable if all poles are inside the unit circle.
147/// This approximation checks if the sum of absolute coefficients is reasonable.
148pub fn is_stable(coeffs: &[f32]) -> bool {
149    if coeffs.is_empty() {
150        return true;
151    }
152
153    // Check 1: No coefficient should be too large (absolute value)
154    let max_coef = coeffs.iter().map(|c| c.abs()).fold(0.0f32, f32::max);
155    if max_coef > 1.5 {
156        return false;
157    }
158
159    // Check 2: Sum of absolute values shouldn't exceed order
160    // For a stable filter, sum should be less than 1 for IIR stability
161    let sum_abs: f32 = coeffs.iter().map(|c| c.abs()).sum();
162    if sum_abs > coeffs.len() as f32 {
163        return false;
164    }
165
166    // Check 3: Simulate a few steps of the filter to detect divergence
167    // Feed it an impulse and see if the output stays bounded
168    let test_len = 50.max(coeffs.len() * 5);
169    let mut output = vec![0.0f32; test_len];
170    output[0] = 1.0; // impulse
171
172    for i in 1..test_len {
173        let mut val = 0.0;
174        for (j, &coeff) in coeffs.iter().enumerate() {
175            if i > j {
176                val += coeff * output[i - j - 1];
177            }
178        }
179        output[i] = val;
180
181        // If output grows at all beyond initial impulse, filter is marginally unstable (haha)
182        if val.abs() > 2.0 || !val.is_finite() {
183            return false;
184        }
185    }
186
187    true
188}
189
190/// Check stability after quantization roundtrip (more strict)
191pub fn is_stable_after_quantization(coeffs: &[f32]) -> bool {
192    if coeffs.is_empty() {
193        return true;
194    }
195
196    // First check raw stability
197    if !is_stable(coeffs) {
198        return false;
199    }
200
201    // Quantize and dequantize to see if roundtrip is still stable
202    let (quantized, shift) = quantize_coefficients(coeffs);
203    let recovered = dequantize_coefficients(&quantized, shift);
204
205    is_stable(&recovered)
206}
207
208// ============================================================================
209// Integer LPC functions
210// ============================================================================
211
212/// Integer autocorrelation
213pub fn autocorr_int(samples: &[i32], order: usize) -> Vec<i64> {
214    let mut autocorr = vec![0i64; order + 1];
215    for lag in 0..=order {
216        for i in lag..samples.len() {
217            autocorr[lag] += (samples[i] as i64) * (samples[i - lag] as i64);
218        }
219    }
220    autocorr
221}
222
223/// Levinson-Durbin in fixed-point
224/// Returns coefficients scaled by 2^shift and the shift value
225pub fn levinson_durbin_int(autocorr: &[i64], order: usize) -> Option<(Vec<i32>, u8)> {
226    if autocorr.is_empty() || autocorr[0] == 0 {
227        return None;
228    }
229
230    // Work in f64 for precision, then convert to fixed-point
231    let mut coeffs = vec![0.0f64; order];
232    let mut error = autocorr[0] as f64;
233
234    for i in 0..order {
235        let mut lambda = autocorr.get(i + 1).copied().unwrap_or(0) as f64;
236        for j in 0..i {
237            lambda -= coeffs[j] * autocorr.get(i - j).copied().unwrap_or(0) as f64;
238        }
239
240        if error.abs() < 1e-10 {
241            return None;
242        }
243
244        let gamma = lambda / error;
245
246        // Check for instability
247        if gamma.abs() >= 1.0 {
248            return None;
249        }
250
251        let mut new_coeffs = vec![0.0f64; i + 1];
252        new_coeffs[i] = gamma;
253        for j in 0..i {
254            new_coeffs[j] = coeffs[j] - gamma * coeffs[i - 1 - j];
255        }
256        coeffs[..=i].copy_from_slice(&new_coeffs);
257
258        error *= 1.0 - gamma * gamma;
259    }
260
261    // Convert to fixed-point
262    // Find appropriate shift to maximize precision
263    let max_coeff = coeffs.iter().map(|&c| c.abs()).fold(0.0f64, f64::max);
264    if max_coeff == 0.0 || !max_coeff.is_finite() {
265        return None;
266    }
267
268    // Use shift that keeps coefficients in i32 range with good precision
269    let shift = ((1 << 30) as f64 / max_coeff).log2().floor() as u8;
270    let shift = shift.min(15); // Cap at 15 bits
271    let scale = (1i64 << shift) as f64;
272
273    let coeffs_fp: Vec<i32> = coeffs.iter().map(|&c| (c * scale).round() as i32).collect();
274
275    Some((coeffs_fp, shift))
276}
277
278/// Calculate residuals using integer predictor
279pub fn calc_residuals_int(samples: &[i32], coeffs: &[i32], shift: u8, order: usize) -> Vec<i32> {
280    let mut residuals = Vec::with_capacity(samples.len());
281
282    // Warmup samples
283    for i in 0..order.min(samples.len()) {
284        residuals.push(samples[i]);
285    }
286
287    // Predicted samples
288    for i in order..samples.len() {
289        let mut prediction: i64 = 0;
290        for (j, &coeff) in coeffs.iter().enumerate() {
291            prediction += (coeff as i64) * (samples[i - j - 1] as i64);
292        }
293        prediction >>= shift;
294        residuals.push(samples[i] - prediction as i32);
295    }
296
297    residuals
298}
299
300/// Fixed predictor residuals
301pub fn fixed_predictor_residuals(samples: &[i32], order: usize) -> Vec<i32> {
302    match order {
303        0 => samples.to_vec(),
304        1 => {
305            // First-order: r[i] = s[i] - s[i-1]
306            let mut r = vec![samples[0]];
307            for i in 1..samples.len() {
308                r.push(samples[i] - samples[i - 1]);
309            }
310            r
311        }
312        2 => {
313            // Second-order: r[i] = s[i] - 2*s[i-1] + s[i-2]
314            let mut r = vec![samples[0]];
315            if samples.len() > 1 {
316                r.push(samples[1] - samples[0]);
317            }
318            for i in 2..samples.len() {
319                r.push(samples[i] - 2 * samples[i - 1] + samples[i - 2]);
320            }
321            r
322        }
323        3 => {
324            // Third-order
325            let mut r = vec![samples[0]];
326            if samples.len() > 1 {
327                r.push(samples[1] - samples[0]);
328            }
329            if samples.len() > 2 {
330                r.push(samples[2] - 2 * samples[1] + samples[0]);
331            }
332            for i in 3..samples.len() {
333                r.push(samples[i] - 3 * samples[i - 1] + 3 * samples[i - 2] - samples[i - 3]);
334            }
335            r
336        }
337        4 => {
338            // Fourth-order
339            let mut r = vec![samples[0]];
340            if samples.len() > 1 {
341                r.push(samples[1] - samples[0]);
342            }
343            if samples.len() > 2 {
344                r.push(samples[2] - 2 * samples[1] + samples[0]);
345            }
346            if samples.len() > 3 {
347                r.push(samples[3] - 3 * samples[2] + 3 * samples[1] - samples[0]);
348            }
349            for i in 4..samples.len() {
350                r.push(
351                    samples[i] - 4 * samples[i - 1] + 6 * samples[i - 2] - 4 * samples[i - 3]
352                        + samples[i - 4],
353                );
354            }
355            r
356        }
357        _ => samples.to_vec(),
358    }
359}