use std::{
fmt::{Debug, Display},
io::{Read, Seek, SeekFrom, Write},
mem::{self},
slice, str,
};
use super::{normalize_sample, truncate_sample, StreamInfo, Streamable};
#[derive(Debug)]
#[repr(C, align(1))]
struct RawWavHeader {
riff: [u8; 4],
file_size: u32,
wave: [u8; 4],
fmt: [u8; 4],
cksize: u32,
audio_format: i16,
num_channels: i16,
frequncy: i32,
bytes_per_sec: i32,
block_align: i16,
bits_per_sample: i16,
data_header: [u8; 4],
file_size_data: u32,
}
impl Default for RawWavHeader {
fn default() -> Self {
Self {
riff: [b'R', b'I', b'F', b'F'],
file_size: 0,
wave: [b'W', b'A', b'V', b'E'],
fmt: [b'f', b'm', b't', b' '],
cksize: 0,
audio_format: 0,
num_channels: 0,
frequncy: 0,
bytes_per_sec: 0,
block_align: 0,
bits_per_sample: 0,
data_header: [b'd', b'a', b't', b'a'],
file_size_data: 0,
}
}
}
impl Display for RawWavHeader {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "riff:{}", str::from_utf8(&self.riff).ok().unwrap())?;
writeln!(f, "file_size:{}", self.file_size)?;
writeln!(f, "wave:{}", str::from_utf8(&self.wave).ok().unwrap())?;
writeln!(f, "fmt:{}", str::from_utf8(&self.fmt).ok().unwrap())?;
writeln!(f, "data_len:{}", self.cksize)?;
writeln!(f, "sample_type:{}", self.audio_format)?;
writeln!(f, "frequency:{}", self.frequncy)?;
writeln!(f, "bytes_per_sec:{}", self.bytes_per_sec)?;
writeln!(f, "block_align:{}", self.block_align)?;
writeln!(f, "bits_per_sample:{}", self.bits_per_sample)?;
writeln!(
f,
"data_header:{}",
str::from_utf8(&self.data_header).ok().unwrap()
)?;
writeln!(f, "file_size(data):{}", self.file_size_data)?;
Ok(())
}
}
impl RawWavHeader {
pub fn as_bytes(&self) -> &[u8] {
unsafe {
let bytes = self as *const Self as *const u8;
slice::from_raw_parts(bytes, mem::size_of::<RawWavHeader>())
}
}
pub fn as_bytes_mut(&mut self) -> &mut [u8] {
unsafe {
let bytes: *mut u8 = self as *mut Self as *mut u8;
slice::from_raw_parts_mut(bytes, mem::size_of::<RawWavHeader>())
}
}
}
pub struct WavCodec {
info: StreamInfo,
pcm: Vec<u8>,
short_cursor: u64,
}
impl WavCodec {
pub fn new(info: StreamInfo) -> Self {
Self {
info,
pcm: Vec::new(),
short_cursor: 0,
}
}
fn info(&self) -> StreamInfo {
self.info
}
pub fn save_to<Resource>(&self, mut dst: Resource) -> Result<(), &'static str>
where
Resource: Write,
{
let mut header = RawWavHeader::default();
header.file_size = mem::size_of::<RawWavHeader>() as u32 - 8 + self.pcm.len() as u32;
header.frequncy = self.info.sample_rate as i32;
header.audio_format = 1;
header.num_channels = self.info.channels as i16;
header.cksize = 16;
header.bytes_per_sec = (header.frequncy * header.num_channels as i32 * 16) / 8;
header.block_align = (header.num_channels * 16) / 8;
header.bits_per_sample = 16;
header.file_size_data = self.pcm.len() as u32;
dst.write(header.as_bytes())
.map_err(|_| "failed to write header")?;
dst.write_all(&self.pcm)
.map_err(|_| "failed to write pcm data")
}
pub fn load<Resource>(mut wav_res: Resource) -> Result<Self, &'static str>
where
Resource: Read + Seek,
{
let mut header_binary = [0u8; 256];
wav_res
.read_exact(&mut header_binary)
.map_err(|_| "failed to read header bytes")?;
let mut header_slice = &header_binary[..];
let mut header = RawWavHeader::default();
let (data_start, _) = header_binary
.windows(4)
.enumerate()
.find(|(_, window)| {
let number = [b'd', b'a', b't', b'a'];
window == &number
})
.ok_or("data region not found")?;
header_slice
.read(header.as_bytes_mut())
.map_err(|_| "header failed to read")?;
header
.data_header
.iter_mut()
.zip(header_slice[data_start..data_start + 4].iter())
.for_each(|(data_header_byte, &b)| {
*data_header_byte = b;
});
header_slice = &header_slice[data_start + 4..];
header.file_size_data = u32::from_le_bytes([
header_slice[0],
header_slice[1],
header_slice[2],
header_slice[3],
]);
if header.bits_per_sample != 16 && header.bits_per_sample != 8 {
return Err("invalid bits per sample, either 16 or 8 bits per sample is supported ");
}
if header.audio_format != 1 {
return Err("formats other than PCM liner isn't supported");
}
wav_res
.seek(SeekFrom::Start(data_start as u64 + 8))
.map_err(|_| "failed to seek to pcm data")?;
let mut pcm = Vec::<u8>::new();
wav_res
.read_to_end(&mut pcm)
.map_err(|_| "failed to read to end")?;
if header.bits_per_sample == 8 {
let len = pcm.len();
pcm.resize(pcm.len() * 2, 0);
#[allow(clippy::identity_op)]
for k in (0..len).rev() {
let sample = pcm[k];
let scaled_sample = ((sample as f32 / 255.0) * 2.0 - 1.0).clamp(-1.0, 1.0)
* ((i16::MAX - 1) as f32);
let sample = scaled_sample as i16;
pcm[2 * k + 0] = ((sample >> 0) & 0xff) as u8;
pcm[2 * k + 1] = ((sample >> 8) & 0xff) as u8;
}
}
Ok(Self {
info: StreamInfo {
sample_rate: header.frequncy as u32,
channels: header.num_channels as u32,
},
pcm,
short_cursor: 0,
})
}
fn num_samples(&self) -> usize {
self.pcm.len() / 2
}
fn get_pcm(pcm: &Vec<u8>) -> &[i16] {
unsafe { slice::from_raw_parts(pcm.as_ptr() as *const i16, pcm.len() / 2) }
}
#[allow(dead_code)]
fn get_pcm_mut(pcm: &mut Vec<u8>) -> &mut [i16] {
unsafe { slice::from_raw_parts_mut(pcm.as_ptr() as *mut i16, pcm.len() / 2) }
}
#[allow(clippy::identity_op)]
fn write_sample(&mut self, truncated_sample: i16) {
let upper_byte = ((truncated_sample >> 8) & 0xff) as u8;
let lower_byte = ((truncated_sample >> 0) & 0xff) as u8;
if (self.short_cursor as usize) < self.num_samples() {
let byte_index = (self.short_cursor as usize) << 1;
self.pcm[byte_index + 0] = lower_byte;
self.pcm[byte_index + 1] = upper_byte;
} else {
self.pcm.push(lower_byte);
self.pcm.push(upper_byte);
}
self.offset_cursor(1);
}
fn offset_cursor(&mut self, offset: i64) {
self.short_cursor = (self.short_cursor as i64 + offset).max(0) as u64;
}
}
impl Streamable for WavCodec {
fn info(&self) -> StreamInfo {
self.info()
}
fn filesize_upperbound(&self) -> u64 {
let wav_header_in_bits = mem::size_of::<RawWavHeader>() as u64 * 8;
let wav_pcm_in_bits = self.pcm.len() as u64 * 8;
wav_header_in_bits + wav_pcm_in_bits
}
fn encode(&mut self, samples: &[f32]) -> Option<usize> {
let num_channels = self.info.channels();
let valid_len = (samples.len() / num_channels) * num_channels;
for &samp in &samples[0..valid_len] {
let sample_i16 = truncate_sample(samp);
self.write_sample(sample_i16);
}
Some(valid_len)
}
fn decode(&mut self, out: &mut [f32]) -> Option<usize> {
let mut out_cursor = 0;
let num_channels = self.info().channels();
let stream_length = self.num_samples() as u64;
let cursor = &mut self.short_cursor;
let samples_list = Self::get_pcm(&self.pcm);
let valid_length = (out.len() / num_channels) * num_channels;
while *cursor < stream_length && out_cursor < valid_length {
let sample_i16 = samples_list[*cursor as usize];
let normalized_sample = normalize_sample(sample_i16);
out[out_cursor] = normalized_sample;
*cursor += 1;
out_cursor += 1;
}
(out_cursor > 0).then_some(out_cursor)
}
fn seek(&mut self, dt: SeekFrom) {
let sample_rate = self.info.sample_rate as f32;
let num_channels = self.info.channels() as i64;
let num_samples = self.num_samples() as i64;
let sample_rate_in_milliseconds = sample_rate / 1000.0;
match dt {
SeekFrom::Current(dt) => {
let sample_offset = (sample_rate_in_milliseconds * (dt as f32)) as i64;
let block_index = (self.short_cursor as i64 / num_channels) * num_channels;
self.short_cursor =
(block_index + sample_offset * num_channels).clamp(0, num_samples - 1) as u64;
}
SeekFrom::Start(dt) => {
let sample_offset = (sample_rate_in_milliseconds * (dt as f32)) as i64;
self.short_cursor = (sample_offset * num_channels).clamp(0, num_samples - 1) as u64;
}
SeekFrom::End(dt) => {
let sample_offset = (sample_rate_in_milliseconds * (dt as f32)) as i64;
let block_index = num_samples - sample_offset;
self.short_cursor =
(block_index + sample_offset * num_channels).clamp(0, num_samples - 1) as u64;
}
}
}
}
mod test {
#[allow(unused_imports)]
use super::{StreamInfo, Streamable, WavCodec};
#[allow(unused_imports)]
use std::{
fs,
io::{Cursor, Read, Seek, SeekFrom, Write},
};
#[test]
fn parse_wav_then_re_export_copy_to_disk() {
let file_pointer = fs::File::open("./resources/taunt.wav").expect("misisng file");
let wav_codec = WavCodec::load(file_pointer).unwrap();
let new_file =
fs::File::create("./resources/taunt_out.wav").expect("failed to create file");
wav_codec.save_to(new_file).expect("failed to write");
}
#[test]
fn parse_wav_then_re_export_to_main_memory() {
let mut file_pointer = fs::File::open("./resources/taunt.wav").expect("misisng file");
let mut wav_binary = Vec::new();
file_pointer
.read_to_end(&mut wav_binary)
.expect("failed to write into a buffer");
let wav_codec = WavCodec::load(Cursor::new(wav_binary)).unwrap();
let mut new_wav_file = Vec::new();
wav_codec
.save_to(&mut new_wav_file)
.expect("failed to write");
}
#[test]
fn parse_wav_then_re_export_by_encode_then_copy_to_disk() {
let file_pointer = fs::File::open("./resources/taunt.wav").expect("misisng file");
let mut wav_codec = WavCodec::load(file_pointer).unwrap();
let mut new_wav = WavCodec::new(wav_codec.info());
let mut buffer = [0.0; 1024];
while let Some(samples_read) = wav_codec.decode(&mut buffer) {
new_wav.encode(&buffer[0..samples_read]);
}
let new_file =
fs::File::create("./resources/taunt_copy.wav").expect("failed to create file");
new_wav.save_to(new_file).expect("failed to write");
}
}