libflo_audio/
writer.rs

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