use crate::core::{crc32, FloResult, Frame, FrameType};
use crate::{ResidualEncoding, HEADER_SIZE, MAGIC, VERSION_MAJOR, VERSION_MINOR};
use alloc::vec::Vec;
pub struct Writer {
buffer: Vec<u8>,
}
impl Writer {
pub fn new() -> Self {
Writer { buffer: Vec::new() }
}
pub fn write(
self,
sample_rate: u32,
channels: u8,
bit_depth: u8,
compression_level: u8,
frames: &[Frame],
metadata: &[u8],
) -> FloResult<Vec<u8>> {
self.write_ex(
sample_rate,
channels,
bit_depth,
compression_level,
false,
0,
frames,
metadata,
)
}
#[allow(clippy::too_many_arguments)]
pub fn write_ex(
mut self,
sample_rate: u32,
channels: u8,
bit_depth: u8,
compression_level: u8,
lossy: bool,
lossy_quality: u8,
frames: &[Frame],
metadata: &[u8],
) -> FloResult<Vec<u8>> {
let toc_size = 4 + (frames.len() * 20) as u64;
let data_chunk = self.build_data_chunk(frames);
let data_size = data_chunk.len() as u64;
let extra_size = 0u64;
let meta_size = metadata.len() as u64;
let data_crc32 = crc32::compute(&data_chunk);
let toc_chunk = self.build_toc_chunk(frames, sample_rate);
let mut flags: u16 = 0;
if lossy {
flags |= 0x01; flags |= (lossy_quality as u16) << 8; }
let total_samples: u64 = frames.iter().map(|frame| frame.frame_samples as u64).sum();
self.write_header_ex(
sample_rate,
channels,
bit_depth,
compression_level,
total_samples,
data_crc32,
flags,
toc_size,
data_size,
extra_size,
meta_size,
);
self.buffer.extend_from_slice(&toc_chunk);
self.buffer.extend_from_slice(&data_chunk);
self.buffer.extend_from_slice(metadata);
Ok(self.buffer)
}
#[allow(dead_code, clippy::too_many_arguments)]
fn write_header(
&mut self,
sample_rate: u32,
channels: u8,
bit_depth: u8,
compression_level: u8,
total_samples: u64,
data_crc32: u32,
toc_size: u64,
data_size: u64,
extra_size: u64,
meta_size: u64,
) {
self.write_header_ex(
sample_rate,
channels,
bit_depth,
compression_level,
total_samples,
data_crc32,
0,
toc_size,
data_size,
extra_size,
meta_size,
);
}
#[allow(clippy::too_many_arguments)]
fn write_header_ex(
&mut self,
sample_rate: u32,
channels: u8,
bit_depth: u8,
compression_level: u8,
total_samples: u64,
data_crc32: u32,
flags: u16,
toc_size: u64,
data_size: u64,
extra_size: u64,
meta_size: u64,
) {
self.buffer.extend_from_slice(&MAGIC);
self.buffer.push(VERSION_MAJOR);
self.buffer.push(VERSION_MINOR);
self.buffer.extend_from_slice(&flags.to_le_bytes());
self.buffer.extend_from_slice(&sample_rate.to_le_bytes());
self.buffer.push(channels);
self.buffer.push(bit_depth);
self.buffer.extend_from_slice(&total_samples.to_le_bytes());
self.buffer.push(compression_level);
self.buffer.extend_from_slice(&[0, 0, 0]);
self.buffer.extend_from_slice(&data_crc32.to_le_bytes());
self.buffer.extend_from_slice(&HEADER_SIZE.to_le_bytes());
self.buffer.extend_from_slice(&toc_size.to_le_bytes());
self.buffer.extend_from_slice(&data_size.to_le_bytes());
self.buffer.extend_from_slice(&extra_size.to_le_bytes());
self.buffer.extend_from_slice(&meta_size.to_le_bytes());
}
fn build_toc_chunk(&self, frames: &[Frame], sample_rate: u32) -> Vec<u8> {
let mut toc = Vec::new();
toc.extend_from_slice(&(frames.len() as u32).to_le_bytes());
let mut byte_offset = 0u64;
let mut cumulative_samples = 0u64;
for (i, frame) in frames.iter().enumerate() {
let frame_size = frame.byte_size() as u32;
toc.extend_from_slice(&(i as u32).to_le_bytes());
toc.extend_from_slice(&byte_offset.to_le_bytes());
toc.extend_from_slice(&frame_size.to_le_bytes());
let timestamp_ms = (cumulative_samples * 1000 / sample_rate as u64) as u32;
toc.extend_from_slice(×tamp_ms.to_le_bytes());
byte_offset += frame_size as u64;
cumulative_samples += frame.frame_samples as u64;
}
toc
}
fn build_data_chunk(&self, frames: &[Frame]) -> Vec<u8> {
let mut data = Vec::new();
for frame in frames {
self.write_frame(&mut data, frame);
}
data
}
fn write_frame(&self, buffer: &mut Vec<u8>, frame: &Frame) {
let frame_type = FrameType::from(frame.frame_type);
buffer.push(frame.frame_type);
buffer.extend_from_slice(&frame.frame_samples.to_le_bytes());
buffer.push(frame.flags);
for ch_data in &frame.channels {
let mut ch_buffer = Vec::new();
self.write_channel_data(&mut ch_buffer, ch_data, frame_type);
buffer.extend_from_slice(&(ch_buffer.len() as u32).to_le_bytes());
buffer.extend_from_slice(&ch_buffer);
}
}
fn write_channel_data(
&self,
buffer: &mut Vec<u8>,
ch_data: &crate::ChannelData,
frame_type: FrameType,
) {
match frame_type {
FrameType::Silence => {
}
FrameType::Raw => {
buffer.extend_from_slice(&ch_data.residuals);
}
FrameType::Transform => {
buffer.extend_from_slice(&ch_data.residuals);
}
_ if frame_type.is_alpc() => {
buffer.push(ch_data.predictor_coeffs.len() as u8);
for &coeff in &ch_data.predictor_coeffs {
buffer.extend_from_slice(&coeff.to_le_bytes());
}
buffer.push(ch_data.shift_bits);
buffer.push(ch_data.residual_encoding as u8);
if ch_data.residual_encoding == ResidualEncoding::Rice {
buffer.push(ch_data.rice_parameter);
}
buffer.extend_from_slice(&ch_data.residuals);
}
_ => {
}
}
}
}
impl Default for Writer {
fn default() -> Self {
Self::new()
}
}