use anyhow::{Context, Result};
use byteorder::{LittleEndian, WriteBytesExt};
use std::fs::File;
use std::io::{BufWriter, Seek, Write};
use std::path::Path;
const DSD_CHUNK_ID: &[u8; 4] = b"DSD ";
const FMT_CHUNK_ID: &[u8; 4] = b"fmt ";
const DATA_CHUNK_ID: &[u8; 4] = b"data";
const DSD_CHUNK_SIZE: u64 = 28;
const FMT_CHUNK_SIZE: u64 = 52;
pub const DSF_BLOCK_SIZE_PER_CHANNEL: usize = 4096;
pub struct NopadCarry {
pub channel_tails: Vec<Vec<u8>>,
}
pub struct DsfWriter {
writer: BufWriter<File>,
channel_count: u32,
data_chunk_offset: u64,
bytes_written: u64,
decoded_bytes_total: u64,
channel_buffers: Vec<Vec<u8>>,
channel_buffer_pos: usize,
id3_footer: Option<Vec<u8>>,
nopad: bool,
}
impl DsfWriter {
pub fn create(
path: &Path,
channel_count: u32,
sample_rate: u32,
total_samples_per_channel: u64,
extra_setting: u8,
) -> Result<Self> {
let file = File::create(path)
.with_context(|| format!("Failed to create DSF file: {}", path.display()))?;
let mut writer = BufWriter::new(file);
writer.write_all(DSD_CHUNK_ID)?;
writer.write_u64::<LittleEndian>(DSD_CHUNK_SIZE)?; writer.write_u64::<LittleEndian>(0)?; writer.write_u64::<LittleEndian>(0)?;
writer.write_all(FMT_CHUNK_ID)?;
writer.write_u64::<LittleEndian>(FMT_CHUNK_SIZE)?; writer.write_u32::<LittleEndian>(1)?; writer.write_u32::<LittleEndian>(0)?;
let channel_type = if channel_count == 2 && extra_setting == 0 {
2 } else if channel_count == 5 && extra_setting == 3 {
6 } else if channel_count == 6 && extra_setting == 4 {
7 } else {
match channel_count {
1 => 1, 2 => 2, 3 => 3, 4 => 4, 5 => 6, 6 => 7, _ => 2, }
};
writer.write_u32::<LittleEndian>(channel_type)?; writer.write_u32::<LittleEndian>(channel_count)?; writer.write_u32::<LittleEndian>(sample_rate)?; writer.write_u32::<LittleEndian>(1)?; writer.write_u64::<LittleEndian>(total_samples_per_channel)?; writer.write_u32::<LittleEndian>(DSF_BLOCK_SIZE_PER_CHANNEL as u32)?;
writer.write_u32::<LittleEndian>(0)?;
let data_chunk_offset = writer.stream_position()?;
writer.write_all(DATA_CHUNK_ID)?;
writer.write_u64::<LittleEndian>(0)?;
let channel_buffers =
vec![Vec::with_capacity(DSF_BLOCK_SIZE_PER_CHANNEL); channel_count as usize];
Ok(Self {
writer,
channel_count,
data_chunk_offset,
bytes_written: 0,
decoded_bytes_total: 0,
channel_buffers,
channel_buffer_pos: 0,
id3_footer: None,
nopad: false,
})
}
pub fn set_id3_footer(&mut self, footer: Vec<u8>) {
self.id3_footer = Some(footer);
}
pub fn set_nopad(&mut self, on: bool) {
self.nopad = on;
}
pub fn pre_load_carry(&mut self, carry: NopadCarry) {
assert_eq!(
carry.channel_tails.len(),
self.channel_count as usize,
"carry channel count mismatch"
);
let per_channel = carry.channel_tails.first().map(|v| v.len()).unwrap_or(0);
for (ch, tail) in carry.channel_tails.into_iter().enumerate() {
debug_assert_eq!(tail.len(), per_channel, "carry tails must be equal length");
self.channel_buffers[ch].extend_from_slice(&tail);
}
self.channel_buffer_pos = per_channel * self.channel_count as usize;
}
pub fn write_samples(&mut self, data: &[u8]) -> Result<()> {
self.decoded_bytes_total += data.len() as u64;
for &byte in data {
let channel_idx = self.channel_buffer_pos % self.channel_count as usize;
let reversed_byte = reverse_bits(byte);
self.channel_buffers[channel_idx].push(reversed_byte);
self.channel_buffer_pos += 1;
if self
.channel_buffers
.iter()
.all(|buf| buf.len() >= DSF_BLOCK_SIZE_PER_CHANNEL)
{
for channel in 0..self.channel_count as usize {
let block: Vec<u8> = self.channel_buffers[channel]
.drain(..DSF_BLOCK_SIZE_PER_CHANNEL)
.collect();
self.writer.write_all(&block)?;
self.bytes_written += DSF_BLOCK_SIZE_PER_CHANNEL as u64;
}
}
}
Ok(())
}
fn flush_channel_block(&mut self) -> Result<()> {
for channel in 0..self.channel_count as usize {
if self.channel_buffers[channel].len() >= DSF_BLOCK_SIZE_PER_CHANNEL {
let block: Vec<u8> = self.channel_buffers[channel]
.drain(..DSF_BLOCK_SIZE_PER_CHANNEL)
.collect();
self.writer.write_all(&block)?;
self.bytes_written += DSF_BLOCK_SIZE_PER_CHANNEL as u64;
}
}
Ok(())
}
pub fn finalize(mut self) -> Result<Option<NopadCarry>> {
let has_partial = self.channel_buffers.iter().any(|buf| !buf.is_empty());
let carry = if self.nopad && has_partial {
let channel_tails: Vec<Vec<u8>> = self
.channel_buffers
.iter_mut()
.map(std::mem::take)
.collect();
Some(NopadCarry { channel_tails })
} else {
if has_partial {
for channel_buf in &mut self.channel_buffers {
if channel_buf.len() < DSF_BLOCK_SIZE_PER_CHANNEL {
channel_buf.resize(DSF_BLOCK_SIZE_PER_CHANNEL, 0);
}
}
self.flush_channel_block()?;
}
None
};
let audio_end_pos = self.writer.stream_position()?;
let footer_size = if let Some(ref footer) = self.id3_footer {
self.writer.write_all(footer)?;
footer.len() as u64
} else {
0
};
let total_file_size = audio_end_pos + footer_size;
self.writer.seek(std::io::SeekFrom::Start(12))?;
self.writer.write_u64::<LittleEndian>(total_file_size)?;
let metadata_offset = if footer_size > 0 { audio_end_pos } else { 0 };
self.writer.write_u64::<LittleEndian>(metadata_offset)?;
let channels = self.channel_count.max(1) as u64;
let sample_count = if carry.is_some() {
self.bytes_written / channels * 8
} else {
self.decoded_bytes_total / channels * 8
};
self.writer.seek(std::io::SeekFrom::Start(64))?;
self.writer.write_u64::<LittleEndian>(sample_count)?;
let data_chunk_size = 12 + self.bytes_written;
self.writer
.seek(std::io::SeekFrom::Start(self.data_chunk_offset + 4))?;
self.writer.write_u64::<LittleEndian>(data_chunk_size)?;
self.writer.flush()?;
Ok(carry)
}
}
#[inline]
pub fn reverse_bits(byte: u8) -> u8 {
let mut result = byte;
result = (result & 0xF0) >> 4 | (result & 0x0F) << 4;
result = (result & 0xCC) >> 2 | (result & 0x33) << 2;
result = (result & 0xAA) >> 1 | (result & 0x55) << 1;
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_reverse_bits() {
assert_eq!(reverse_bits(0b00000000), 0b00000000);
assert_eq!(reverse_bits(0b11111111), 0b11111111);
assert_eq!(reverse_bits(0b10000000), 0b00000001);
assert_eq!(reverse_bits(0b00000001), 0b10000000);
assert_eq!(reverse_bits(0b10110010), 0b01001101);
assert_eq!(reverse_bits(0b11001010), 0b01010011);
}
}