use std::io::{Read, Seek, SeekFrom};
use crate::read_bytes::ReadBytesExt;
type Error = crate::NcwError;
const HEADER_SIZE: usize = 120;
const BLOCK_HEADER_SIZE: usize = 16;
const BLOCK_MAGIC: u32 = 0x160C9A3E;
const FILE_MAGICS: [u64; 2] = [0x01A89ED631010000, 0x01A89ED630010000];
pub const SAMPLES_PER_BLOCK: usize = 512;
#[derive(Debug)]
pub struct NcwReader<R> {
pub reader: R,
pub header: NcwHeader,
pub block_offsets: Vec<u32>,
pub sample_format: SampleFormat,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NcwHeader {
pub channels: u16,
pub bits_per_sample: u16,
pub sample_rate: u32,
pub num_samples: u32,
pub blocks_offset: u32,
pub data_offset: u32,
pub data_size: u32,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BlockHeader {
pub base_value: i32,
pub bits: i16,
pub flags: u16,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ChannelEncoding {
LeftRight,
MidSide,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SampleFormat {
Pcm,
Float,
}
impl<R: Read + Seek> NcwReader<R> {
pub fn read(mut reader: R) -> Result<Self, Error> {
let header = NcwHeader::read(&mut reader)?;
if header.channels == 0 {
return Err(Error::InvalidHeader("channel count is zero"));
}
if header.data_offset < header.blocks_offset {
return Err(Error::InvalidHeader(
"data offset precedes block offset table",
));
}
let table_entries = (header.data_offset - header.blocks_offset) / 4;
let num_blocks = table_entries.saturating_sub(1) as usize;
reader.seek(SeekFrom::Start(header.blocks_offset as u64))?;
let mut block_offsets = Vec::with_capacity(num_blocks);
for _ in 0..num_blocks {
block_offsets.push(reader.read_u32_le()?);
}
let sample_format = match block_offsets.first() {
Some(&offset) => {
reader.seek(SeekFrom::Start(header.data_offset as u64 + offset as u64))?;
BlockHeader::read(&mut reader)?.sample_format()
}
None => SampleFormat::Pcm,
};
Ok(Self {
reader,
header,
block_offsets,
sample_format,
})
}
pub fn decode_samples(&mut self) -> Result<Vec<i32>, Error> {
let num_samples = self.header.num_samples as usize;
let num_channels = self.header.channels as usize;
let mut channels = vec![Vec::with_capacity(num_samples); num_channels];
for (i, &offset) in self.block_offsets.iter().enumerate() {
self.reader.seek(SeekFrom::Start(
self.header.data_offset as u64 + offset as u64,
))?;
for channel in channels.iter_mut() {
let block_header = BlockHeader::read(&mut self.reader)?;
let samples = read_block(&mut self.reader, &self.header, &block_header)?;
channel.extend_from_slice(&samples);
}
if i + 1 == self.block_offsets.len() || channels[0].len() >= num_samples {
break;
}
}
for channel in channels.iter_mut() {
if channel.len() < num_samples {
return Err(Error::TruncatedData {
expected: num_samples,
actual: channel.len(),
});
}
channel.truncate(num_samples);
}
let mut interleaved = Vec::with_capacity(num_samples * num_channels);
for i in 0..num_samples {
for channel in &channels {
interleaved.push(channel[i]);
}
}
Ok(interleaved)
}
}
fn read_block<R: Read>(
reader: &mut R,
header: &NcwHeader,
block: &BlockHeader,
) -> Result<Vec<i32>, Error> {
let bits = block.bits.unsigned_abs() as usize;
if bits > 32 {
return Err(Error::UnsupportedBitDepth(block.bits));
}
match block.bits.cmp(&0) {
std::cmp::Ordering::Greater => {
let data = reader.read_bytes(bits * SAMPLES_PER_BLOCK / 8)?;
Ok(decode_delta_block(block.base_value, &data, bits))
}
std::cmp::Ordering::Less => {
let data = reader.read_bytes(bits * SAMPLES_PER_BLOCK / 8)?;
Ok(read_packed_values(&data, bits))
}
std::cmp::Ordering::Equal => {
let bytes_per_sample = header.bits_per_sample as usize / 8;
if !(1..=4).contains(&bytes_per_sample) || header.bits_per_sample % 8 != 0 {
return Err(Error::InvalidHeader("unsupported bits per sample"));
}
let data = reader.read_bytes(bytes_per_sample * SAMPLES_PER_BLOCK)?;
Ok(data
.chunks_exact(bytes_per_sample)
.map(|chunk| {
let mut raw = [0u8; 4];
raw[..bytes_per_sample].copy_from_slice(chunk);
sign_extend(u32::from_le_bytes(raw), bytes_per_sample * 8)
})
.collect())
}
}
}
fn decode_delta_block(base_sample: i32, deltas: &[u8], bits: usize) -> Vec<i32> {
let delta_values = read_packed_values(deltas, bits);
let mut samples = Vec::with_capacity(delta_values.len());
let mut current = base_sample;
for delta in delta_values {
samples.push(current);
current = current.wrapping_add(delta);
}
samples
}
fn read_packed_values(data: &[u8], bits: usize) -> Vec<i32> {
debug_assert!((1..=32).contains(&bits));
let mut values = Vec::with_capacity(data.len() * 8 / bits);
let mask = (1u64 << bits) - 1;
let mut accumulator: u64 = 0;
let mut available = 0usize;
for &byte in data {
accumulator |= (byte as u64) << available;
available += 8;
while available >= bits {
values.push(sign_extend((accumulator & mask) as u32, bits));
accumulator >>= bits;
available -= bits;
}
}
values
}
fn sign_extend(raw: u32, bits: usize) -> i32 {
let shift = 32 - bits as u32;
((raw << shift) as i32) >> shift
}
impl BlockHeader {
pub fn read<R: Read>(reader: &mut R) -> Result<BlockHeader, Error> {
let buf = reader.read_bytes(BLOCK_HEADER_SIZE)?;
let mut reader = buf.as_slice();
if reader.read_u32_be()? != BLOCK_MAGIC {
return Err(Error::InvalidBlockSignature);
}
Ok(BlockHeader {
base_value: reader.read_i32_le()?,
bits: reader.read_i16_le()?,
flags: reader.read_u16_le()?,
})
}
pub fn channel_encoding(&self) -> ChannelEncoding {
if self.flags & 0b01 != 0 {
ChannelEncoding::MidSide
} else {
ChannelEncoding::LeftRight
}
}
pub fn sample_format(&self) -> SampleFormat {
if self.flags & 0b10 != 0 {
SampleFormat::Float
} else {
SampleFormat::Pcm
}
}
}
impl NcwHeader {
pub fn read<R: Read>(reader: &mut R) -> Result<Self, Error> {
let buf = reader.read_bytes(HEADER_SIZE)?;
let mut reader = buf.as_slice();
if !FILE_MAGICS.contains(&reader.read_u64_be()?) {
return Err(Error::InvalidFileSignature);
}
Ok(Self {
channels: reader.read_u16_le()?,
bits_per_sample: reader.read_u16_le()?,
sample_rate: reader.read_u32_le()?,
num_samples: reader.read_u32_le()?,
blocks_offset: reader.read_u32_le()?,
data_offset: reader.read_u32_le()?,
data_size: reader.read_u32_le()?,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn packed_values_sign_extend() {
assert_eq!(read_packed_values(&[0xF7, 0x08], 4), vec![7, -1, -8, 0]);
}
#[test]
fn packed_values_full_width() {
let bytes = (-24i32).to_le_bytes();
assert_eq!(read_packed_values(&bytes, 32), vec![-24]);
let bytes = (-24i16).to_le_bytes();
assert_eq!(read_packed_values(&bytes, 16), vec![-24]);
}
#[test]
fn packed_values_unaligned_width() {
assert_eq!(read_packed_values(&[0x41, 0x0C], 5), vec![1, 2, 3]);
}
#[test]
fn delta_block_accumulates() {
assert_eq!(
decode_delta_block(10, &[1, 1, 0xFE, 0], 8),
vec![10, 11, 12, 10]
);
}
#[test]
fn bad_magic_is_an_error() {
let bytes = [0u8; HEADER_SIZE];
assert!(matches!(
NcwReader::read(std::io::Cursor::new(bytes)),
Err(Error::InvalidFileSignature)
));
}
}