Skip to main content

libflo_audio/lossless/
encoder.rs

1use crate::core::audio_constants::f32_to_i32;
2use crate::core::{ChannelData, Frame, FrameType, ResidualEncoding};
3use crate::{core::rice, FloResult, Writer};
4use alloc::vec;
5use alloc::vec::Vec;
6
7use super::lpc::{
8    autocorr_int, calc_residuals_int, fixed_predictor_residuals, levinson_durbin_int,
9};
10
11pub struct Encoder {
12    sample_rate: u32,
13    channels: u8,
14    bit_depth: u8,
15    compression_level: u8,
16}
17
18impl Encoder {
19    pub fn new(sample_rate: u32, channels: u8, bit_depth: u8) -> Self {
20        Encoder {
21            sample_rate,
22            channels,
23            bit_depth,
24            compression_level: 5,
25        }
26    }
27
28    pub fn with_compression(mut self, level: u8) -> Self {
29        self.compression_level = level.min(9);
30        self
31    }
32
33    /// encode samples to flo format
34    pub fn encode(&self, samples: &[f32], metadata: &[u8]) -> FloResult<Vec<u8>> {
35        if self.sample_rate == 0 {
36            return Err("Sample rate must be greater than zero".into());
37        }
38        if self.channels == 0 {
39            return Err("Channel count must be greater than zero".into());
40        }
41        if !samples.len().is_multiple_of(self.channels as usize) {
42            return Err("Interleaved samples must contain a complete final frame".into());
43        }
44
45        let samples_per_frame = self.sample_rate as usize;
46        let frames = self.encode_frames(samples, samples_per_frame);
47
48        let writer = Writer::new();
49        writer.write(
50            self.sample_rate,
51            self.channels,
52            self.bit_depth,
53            self.compression_level,
54            &frames,
55            metadata,
56        )
57    }
58
59    fn encode_frames(&self, samples: &[f32], samples_per_frame: usize) -> Vec<Frame> {
60        let total_samples = samples.len() / (self.channels as usize);
61        let num_frames = total_samples.div_ceil(samples_per_frame);
62
63        let mut frames = Vec::with_capacity(num_frames);
64
65        for frame_idx in 0..num_frames {
66            let start = frame_idx * samples_per_frame * (self.channels as usize);
67            let end =
68                ((frame_idx + 1) * samples_per_frame * (self.channels as usize)).min(samples.len());
69
70            let frame_samples = &samples[start..end];
71            let frame = self.encode_frame(frame_samples);
72            frames.push(frame);
73        }
74
75        frames
76    }
77
78    fn encode_frame(&self, samples: &[f32]) -> Frame {
79        let num_samples = samples.len() / (self.channels as usize);
80
81        // Check for silence
82        if samples.iter().all(|&s| s.abs() < 1e-7) {
83            let mut frame = Frame::new(FrameType::Silence as u8, num_samples as u32);
84            for _ in 0..self.channels {
85                frame.channels.push(ChannelData::new_silence());
86            }
87            return frame;
88        }
89
90        // Convert to integer domain
91        let samples_i32: Vec<i32> = samples.iter().map(|&s| f32_to_i32(s)).collect();
92
93        // Deinterleave channels
94        let mut channel_data: Vec<Vec<i32>> = (0..self.channels as usize)
95            .map(|ch| {
96                samples_i32
97                    .iter()
98                    .skip(ch)
99                    .step_by(self.channels as usize)
100                    .copied()
101                    .collect()
102            })
103            .collect();
104
105        // Apply mid-side coding for stereo (if it helps)
106        let use_mid_side = self.channels == 2 && self.should_use_mid_side(&channel_data);
107        if use_mid_side {
108            let (mid, side) = self.to_mid_side(&channel_data[0], &channel_data[1]);
109            channel_data[0] = mid;
110            channel_data[1] = side;
111        }
112
113        // Encode each channel
114        let lpc_order = self.lpc_order_from_level();
115        let mut encoded_channels = Vec::with_capacity(self.channels as usize);
116        let mut all_raw = true;
117
118        for ch_samples in &channel_data {
119            let (ch_data, order_used, used_raw) = self.encode_channel_int(ch_samples, lpc_order);
120            if !used_raw || order_used > 0 {
121                all_raw = false;
122            }
123            encoded_channels.push(ch_data);
124        }
125
126        // Determine frame type
127        let frame_type = if all_raw {
128            FrameType::Raw
129        } else {
130            FrameType::from_order(lpc_order)
131        };
132
133        let mut frame = Frame::new(frame_type as u8, num_samples as u32);
134        // Set mid-side flag if used
135        if use_mid_side {
136            frame.flags |= 0x01; // Bit 0 = mid-side coding
137        }
138        frame.channels = encoded_channels;
139        frame
140    }
141
142    /// Check if mid-side coding would help
143    fn should_use_mid_side(&self, channels: &[Vec<i32>]) -> bool {
144        if channels.len() != 2 {
145            return false;
146        }
147
148        let left = &channels[0];
149        let right = &channels[1];
150
151        // Calculate variance of L-R vs L and R separately
152        let mut var_l: i64 = 0;
153        let mut var_r: i64 = 0;
154        let mut var_side: i64 = 0;
155
156        for (&l, &r) in left.iter().zip(right.iter()) {
157            var_l += (l as i64) * (l as i64);
158            var_r += (r as i64) * (r as i64);
159            let side = l - r;
160            var_side += (side as i64) * (side as i64);
161        }
162
163        // If side channel has less energy, mid-side helps
164        var_side < (var_l + var_r) / 2
165    }
166
167    /// Convert stereo to mid-side
168    fn to_mid_side(&self, left: &[i32], right: &[i32]) -> (Vec<i32>, Vec<i32>) {
169        let mid: Vec<i32> = left
170            .iter()
171            .zip(right.iter())
172            .map(|(&l, &r)| (l + r) >> 1)
173            .collect();
174        let side: Vec<i32> = left
175            .iter()
176            .zip(right.iter())
177            .map(|(&l, &r)| l - r)
178            .collect();
179        (mid, side)
180    }
181
182    /// Encode a single channel using integer LPC.
183    #[allow(clippy::type_complexity)]
184    fn encode_channel_int(&self, samples: &[i32], max_order: usize) -> (ChannelData, usize, bool) {
185        if samples.is_empty() {
186            return (ChannelData::new_silence(), 0, false);
187        }
188
189        // Try different encoding strategies and pick the smallest
190        let mut best_data: Option<ChannelData> = None;
191        let mut best_size = usize::MAX;
192        let mut best_order = 0;
193        let mut best_used_raw = false;
194
195        // Strategy 1: Raw PCM (baseline)
196        // Raw stores 16-bit samples, so skip it when any sample exceeds i16.
197        // The halved mid always fits i16, but the side channel (L - R) reaches
198        // +/-65535 on full-scale stereo; casting that to i16 would wrap and
199        // silently corrupt data.
200        let raw_fits_i16 = samples
201            .iter()
202            .all(|&s| (i32::from(i16::MIN)..=i32::from(i16::MAX)).contains(&s));
203        if raw_fits_i16 {
204            let raw = self.encode_raw(samples);
205            let raw_size = raw.residuals.len();
206            if raw_size < best_size {
207                best_size = raw_size;
208                best_data = Some(raw);
209                best_order = 0;
210                best_used_raw = true;
211            }
212        }
213
214        // Strategy 2: Fixed predictors (order 0-4, very fast)
215        for order in 0..=(4).min(max_order) {
216            if let Some((data, size)) = self.try_fixed_predictor(samples, order) {
217                if size < best_size {
218                    best_size = size;
219                    best_data = Some(data);
220                    best_order = order;
221                    best_used_raw = false;
222                }
223            }
224        }
225
226        // Strategy 3: LPC predictors (if compression level allows)
227        if self.compression_level >= 3 && max_order > 4 {
228            for order in 5..=max_order {
229                if let Some((data, size)) = self.try_lpc_predictor(samples, order) {
230                    if size < best_size {
231                        best_size = size;
232                        best_data = Some(data);
233                        best_order = order;
234                        best_used_raw = false;
235                    }
236                }
237            }
238        }
239
240        (best_data.unwrap(), best_order, best_used_raw)
241    }
242
243    /// Encode as raw PCM
244    fn encode_raw(&self, samples: &[i32]) -> ChannelData {
245        let raw_bytes: Vec<u8> = samples
246            .iter()
247            .flat_map(|&s| (s as i16).to_le_bytes().to_vec())
248            .collect();
249        ChannelData::new_raw(raw_bytes)
250    }
251
252    /// Try fixed predictor
253    fn try_fixed_predictor(&self, samples: &[i32], order: usize) -> Option<(ChannelData, usize)> {
254        if order > 4 {
255            return None;
256        }
257
258        let residuals = fixed_predictor_residuals(samples, order);
259
260        // Find optimal Rice parameter
261        let k = rice::estimate_rice_parameter_i32(&residuals);
262        let encoded = rice::encode_i32(&residuals, k);
263
264        // For fixed predictors: store negative order to distinguish from LPC
265        // predictor_coeffs is empty, shift_bits stores (128 + order) as marker
266        let ch_data = ChannelData {
267            predictor_coeffs: vec![],        // Empty = fixed predictor
268            shift_bits: (128 + order) as u8, // Marker: 128-132 = fixed order 0-4
269            residual_encoding: ResidualEncoding::Rice,
270            rice_parameter: k,
271            residuals: encoded.clone(),
272        };
273
274        Some((ch_data, encoded.len()))
275    }
276
277    /// Try LPC predictor with given order
278    fn try_lpc_predictor(&self, samples: &[i32], order: usize) -> Option<(ChannelData, usize)> {
279        if samples.len() <= order {
280            return None;
281        }
282
283        // Calculate autocorrelation in integer domain
284        let autocorr = autocorr_int(samples, order);
285
286        // Levinson-Durbin for LPC coefficients (in fixed-point)
287        let (coeffs_fp, shift) = levinson_durbin_int(&autocorr, order)?;
288
289        // Calculate residuals using integer arithmetic
290        let residuals = calc_residuals_int(samples, &coeffs_fp, shift, order);
291
292        // Check if residuals are reasonable (not exploding)
293        let max_res = residuals.iter().map(|&r| r.abs()).max().unwrap_or(0);
294        if max_res > 1_000_000 {
295            return None; // Unstable, skip this order
296        }
297
298        // Encode residuals
299        let k = rice::estimate_rice_parameter_i32(&residuals);
300        let encoded = rice::encode_i32(&residuals, k);
301
302        let ch_data = ChannelData {
303            predictor_coeffs: coeffs_fp,
304            shift_bits: shift,
305            residual_encoding: ResidualEncoding::Rice,
306            rice_parameter: k,
307            residuals: encoded.clone(),
308        };
309
310        Some((ch_data, encoded.len()))
311    }
312
313    fn lpc_order_from_level(&self) -> usize {
314        match self.compression_level {
315            0 => 0, // Only fixed predictors
316            1 => 2,
317            2 => 4,
318            3 => 4,
319            4 => 6,
320            5 => 8,
321            6 => 8,
322            7 => 10,
323            8 => 12,
324            _ => 12,
325        }
326    }
327}
328
329impl Default for Encoder {
330    fn default() -> Self {
331        Encoder::new(44100, 1, 16)
332    }
333}