Skip to main content

libflo_audio/
writer.rs

1use crate::core::{crc32, FloResult, Frame, FrameType};
2use crate::{ResidualEncoding, HEADER_SIZE, MAGIC, VERSION_MAJOR, VERSION_MINOR};
3use alloc::vec::Vec;
4
5/// binary writer for flo format
6pub struct Writer {
7    buffer: Vec<u8>,
8}
9
10impl Writer {
11    /// new writer
12    pub fn new() -> Self {
13        Writer { buffer: Vec::new() }
14    }
15
16    /// write a complete flo file
17    pub fn write(
18        self,
19        sample_rate: u32,
20        channels: u8,
21        bit_depth: u8,
22        compression_level: u8,
23        frames: &[Frame],
24        metadata: &[u8],
25    ) -> FloResult<Vec<u8>> {
26        self.write_ex(
27            sample_rate,
28            channels,
29            bit_depth,
30            compression_level,
31            false,
32            0,
33            frames,
34            metadata,
35        )
36    }
37
38    /// write flo file with extended options
39    #[allow(clippy::too_many_arguments)]
40    pub fn write_ex(
41        mut self,
42        sample_rate: u32,
43        channels: u8,
44        bit_depth: u8,
45        compression_level: u8,
46        lossy: bool,
47        lossy_quality: u8,
48        frames: &[Frame],
49        metadata: &[u8],
50    ) -> FloResult<Vec<u8>> {
51        // sizes
52        let toc_size = 4 + (frames.len() * 20) as u64;
53        let data_chunk = self.build_data_chunk(frames);
54        let data_size = data_chunk.len() as u64;
55        let extra_size = 0u64;
56        let meta_size = metadata.len() as u64;
57
58        // crc32
59        let data_crc32 = crc32::compute(&data_chunk);
60
61        // toc
62        let toc_chunk = self.build_toc_chunk(frames, sample_rate);
63
64        // flags
65        let mut flags: u16 = 0;
66        if lossy {
67            flags |= 0x01; // lossy mode
68            flags |= (lossy_quality as u16) << 8; // quality level
69        }
70
71        // Calculate total samples across all frames
72        let total_samples: u64 = frames.iter().map(|frame| frame.frame_samples as u64).sum();
73
74        // header
75        self.write_header_ex(
76            sample_rate,
77            channels,
78            bit_depth,
79            compression_level,
80            total_samples,
81            data_crc32,
82            flags,
83            toc_size,
84            data_size,
85            extra_size,
86            meta_size,
87        );
88
89        // toc
90        self.buffer.extend_from_slice(&toc_chunk);
91
92        // data
93        self.buffer.extend_from_slice(&data_chunk);
94
95        // extra (empty for now)
96
97        // metadata
98        self.buffer.extend_from_slice(metadata);
99
100        Ok(self.buffer)
101    }
102
103    #[allow(dead_code, clippy::too_many_arguments)]
104    fn write_header(
105        &mut self,
106        sample_rate: u32,
107        channels: u8,
108        bit_depth: u8,
109        compression_level: u8,
110        total_samples: u64,
111        data_crc32: u32,
112        toc_size: u64,
113        data_size: u64,
114        extra_size: u64,
115        meta_size: u64,
116    ) {
117        self.write_header_ex(
118            sample_rate,
119            channels,
120            bit_depth,
121            compression_level,
122            total_samples,
123            data_crc32,
124            0,
125            toc_size,
126            data_size,
127            extra_size,
128            meta_size,
129        );
130    }
131
132    #[allow(clippy::too_many_arguments)]
133    fn write_header_ex(
134        &mut self,
135        sample_rate: u32,
136        channels: u8,
137        bit_depth: u8,
138        compression_level: u8,
139        total_samples: u64,
140        data_crc32: u32,
141        flags: u16,
142        toc_size: u64,
143        data_size: u64,
144        extra_size: u64,
145        meta_size: u64,
146    ) {
147        // Magic "FLO!"
148        self.buffer.extend_from_slice(&MAGIC);
149
150        // Version (u8, u8)
151        self.buffer.push(VERSION_MAJOR);
152        self.buffer.push(VERSION_MINOR);
153
154        // Flags (u16 LE)
155        self.buffer.extend_from_slice(&flags.to_le_bytes());
156
157        // Sample Rate (u32 LE)
158        self.buffer.extend_from_slice(&sample_rate.to_le_bytes());
159
160        // Channels (u8)
161        self.buffer.push(channels);
162
163        // Bit Depth (u8)
164        self.buffer.push(bit_depth);
165
166        // Total Samples (u64 LE)
167        self.buffer.extend_from_slice(&total_samples.to_le_bytes());
168
169        // Compression Level (u8)
170        self.buffer.push(compression_level);
171
172        // Reserved (3 bytes)
173        self.buffer.extend_from_slice(&[0, 0, 0]);
174
175        // Data CRC32 (u32 LE)
176        self.buffer.extend_from_slice(&data_crc32.to_le_bytes());
177
178        // Header Size (u64 LE) - size after magic
179        self.buffer.extend_from_slice(&HEADER_SIZE.to_le_bytes());
180
181        // TOC Size (u64 LE)
182        self.buffer.extend_from_slice(&toc_size.to_le_bytes());
183
184        // Data Size (u64 LE)
185        self.buffer.extend_from_slice(&data_size.to_le_bytes());
186
187        // Extra Size (u64 LE)
188        self.buffer.extend_from_slice(&extra_size.to_le_bytes());
189
190        // Meta Size (u64 LE)
191        self.buffer.extend_from_slice(&meta_size.to_le_bytes());
192    }
193
194    fn build_toc_chunk(&self, frames: &[Frame], sample_rate: u32) -> Vec<u8> {
195        let mut toc = Vec::new();
196
197        // Number of entries (u32 LE)
198        toc.extend_from_slice(&(frames.len() as u32).to_le_bytes());
199
200        let mut byte_offset = 0u64;
201        let mut cumulative_samples = 0u64;
202
203        for (i, frame) in frames.iter().enumerate() {
204            let frame_size = frame.byte_size() as u32;
205
206            // Frame index (u32 LE)
207            toc.extend_from_slice(&(i as u32).to_le_bytes());
208
209            // Byte offset (u64 LE)
210            toc.extend_from_slice(&byte_offset.to_le_bytes());
211
212            // Frame size (u32 LE)
213            toc.extend_from_slice(&frame_size.to_le_bytes());
214
215            // Timestamp in milliseconds (u32 LE)
216            // Calculate based on cumulative samples and sample rate
217            let timestamp_ms = (cumulative_samples * 1000 / sample_rate as u64) as u32;
218            toc.extend_from_slice(&timestamp_ms.to_le_bytes());
219
220            byte_offset += frame_size as u64;
221            cumulative_samples += frame.frame_samples as u64;
222        }
223
224        toc
225    }
226
227    fn build_data_chunk(&self, frames: &[Frame]) -> Vec<u8> {
228        let mut data = Vec::new();
229
230        for frame in frames {
231            self.write_frame(&mut data, frame);
232        }
233
234        data
235    }
236
237    fn write_frame(&self, buffer: &mut Vec<u8>, frame: &Frame) {
238        let frame_type = FrameType::from(frame.frame_type);
239
240        // frame header
241        buffer.push(frame.frame_type);
242        buffer.extend_from_slice(&frame.frame_samples.to_le_bytes());
243        buffer.push(frame.flags);
244
245        // channel data with size prefix
246        for ch_data in &frame.channels {
247            // build channel first to get size
248            let mut ch_buffer = Vec::new();
249            self.write_channel_data(&mut ch_buffer, ch_data, frame_type);
250
251            // size then data
252            buffer.extend_from_slice(&(ch_buffer.len() as u32).to_le_bytes());
253            buffer.extend_from_slice(&ch_buffer);
254        }
255    }
256
257    fn write_channel_data(
258        &self,
259        buffer: &mut Vec<u8>,
260        ch_data: &crate::ChannelData,
261        frame_type: FrameType,
262    ) {
263        match frame_type {
264            FrameType::Silence => {
265                // nothing for silence
266            }
267            FrameType::Raw => {
268                // raw residuals
269                buffer.extend_from_slice(&ch_data.residuals);
270            }
271            FrameType::Transform => {
272                // already serialized
273                buffer.extend_from_slice(&ch_data.residuals);
274            }
275            _ if frame_type.is_alpc() => {
276                // predictor count
277                buffer.push(ch_data.predictor_coeffs.len() as u8);
278
279                // predictor coeffs
280                for &coeff in &ch_data.predictor_coeffs {
281                    buffer.extend_from_slice(&coeff.to_le_bytes());
282                }
283
284                // shift bits
285                buffer.push(ch_data.shift_bits);
286
287                // residual encoding
288                buffer.push(ch_data.residual_encoding as u8);
289
290                // rice param
291                if ch_data.residual_encoding == ResidualEncoding::Rice {
292                    buffer.push(ch_data.rice_parameter);
293                }
294
295                // residuals
296                buffer.extend_from_slice(&ch_data.residuals);
297            }
298            _ => {
299                // reserved
300            }
301        }
302    }
303}
304
305impl Default for Writer {
306    fn default() -> Self {
307        Self::new()
308    }
309}