This repository has been archived on 2024-01-28. You can view files and clone it, but cannot push or open issues/pull-requests.
brd/src/xact3/adpcm.rs

235 lines
7.2 KiB
Rust

use std::convert::TryInto;
use std::io::{Cursor, Write};
use byteorder::{WriteBytesExt, LE};
use log::{debug, trace};
use thiserror::Error;
/// Standard ADPCM coefficients
#[rustfmt::skip]
const COEFFS: &[CoefSet] = &[
(256, 0),
(512, -256),
( 0, 0),
(192, 64),
(240, 0),
(460, -208),
(392, -232),
];
#[derive(Debug, Error)]
pub enum Error {
/// WAVE only supports file sizes up to 2<sup>32</sup> bytes (2<sup>32</sup> - 82 bytes of
/// usable audio data in this case).
#[error("unable to create file of size {0} (larger than 2^32 - 82 bytes)")]
TooLargeError(usize),
}
/// All wave chunks implement this trait.
trait WaveChunk {
/// Serialize to byte vector that is used as a part of the resulting wave file.
fn to_chunk(&self) -> Vec<u8>;
}
/// One set of ADPCM coefficients
type CoefSet = (i16, i16);
/// `WAVE_FORMAT_ADPCM` header.
///
/// It only includes fields that are usful for usage in conjunction with XACT3. The other fields
/// are static and defined in the [`to_chunk`] method of this type.
///
/// [`to_chunk`]: trait.WaveChunk.html#tymethod.to_chunk
pub struct WaveFormat {
// wFormatTag = 2
/// `nChannels`: Number of channels
pub channels: u16,
/// `nSamplesPerSec`: Sample rate
pub sample_rate: u32,
// nAvgBytesPerSec (calculated),
/// `nBlockAlign`: Block alignment (in bytes)
pub block_align: u16,
// wBitsPerSample = 4
// cbSize = 32
// nSamplesPerBlock (calculated)
// nNumCoeff = 7
// aCoeff = COEFFS
}
impl WaveChunk for WaveFormat {
fn to_chunk(&self) -> Vec<u8> {
let mut buf = Cursor::new(Vec::new());
write!(buf, "fmt ").unwrap();
buf.write_u32::<LE>(2 + 2 + 4 + 4 + 2 + 2 + 2 + 2 + 2 + 4 * COEFFS.len() as u32)
.unwrap();
buf.write_u16::<LE>(2).unwrap(); // WAVE_FORMAT_ADPCM
buf.write_u16::<LE>(self.channels).unwrap();
buf.write_u32::<LE>(self.sample_rate).unwrap();
buf.write_u32::<LE>(self.avg_bytes_per_sec()).unwrap(); // nAvgBytesPerSec
buf.write_u16::<LE>(self.block_align).unwrap();
buf.write_u16::<LE>(4).unwrap(); // wBitsPerSample
buf.write_u16::<LE>(32).unwrap(); // cbSize
buf.write_u16::<LE>(self.samples_per_block()).unwrap();
buf.write_u16::<LE>(COEFFS.len().try_into().unwrap())
.unwrap(); // nNumCoeff
for coef_set in COEFFS {
buf.write_i16::<LE>(coef_set.0).unwrap();
buf.write_i16::<LE>(coef_set.1).unwrap();
}
buf.into_inner()
}
}
impl WaveFormat {
/// Calculate `nSamplesPerBlock`
fn samples_per_block(&self) -> u16 {
(((self.block_align - (7 * self.channels)) * 8) / (4 * self.channels)) + 2
}
/// Calculate `nAvgBytesPerSec`
fn avg_bytes_per_sec(&self) -> u32 {
(self.sample_rate / u32::from(self.samples_per_block())) * u32::from(self.block_align)
}
}
/// Wave fact chunk
struct WaveFact {
/// The length of the audio data in samples
length_samples: u32,
}
impl WaveChunk for WaveFact {
fn to_chunk(&self) -> Vec<u8> {
let mut buf = Cursor::new(Vec::new());
write!(buf, "fact").unwrap();
buf.write_u32::<LE>(4).unwrap(); // length of fact chunk
buf.write_u32::<LE>(self.length_samples).unwrap();
buf.into_inner()
}
}
/// RIFF header chunk
struct RIFFHeader {
/// Size of the file minus 8 bytes (`RIFF` magic number and the file size)
file_size: u32,
}
impl WaveChunk for RIFFHeader {
fn to_chunk(&self) -> Vec<u8> {
let mut buf = Cursor::new(Vec::new());
write!(buf, "RIFF").unwrap();
buf.write_u32::<LE>(self.file_size).unwrap();
write!(buf, "WAVE").unwrap();
buf.into_inner()
}
}
/// Builds wave data from a given [`WaveFormat`] and raw ADPCM data.
///
/// # Errors
///
/// This function returns a [`TooLargeError`] when the length of `data` is greater than or equal to 2<sup>32</sup> - 82
///
/// [`WaveFormat`]: struct.WaveFormat.html
/// [`TooLargeError`]: enum.Error.html#variant.TooLargeError
pub fn build_wav(format: WaveFormat, data: &[u8]) -> Result<Vec<u8>, Error> {
debug!("Building file");
// returning `u32::MAX` will make the next check fail
let length: u32 = data.len().try_into().unwrap_or(u32::MAX);
let riff_header = RIFFHeader {
file_size: length
.checked_add(82)
.ok_or_else(|| Error::TooLargeError(data.len()))?,
};
let fact = WaveFact {
length_samples: ((length / u32::from(format.block_align))
* u32::from((format.block_align - (7 * format.channels)) * 8)
/ 4)
/ u32::from(format.channels),
};
let mut buf = Cursor::new(Vec::new());
trace!("Building RIFF header");
buf.write_all(&riff_header.to_chunk()).unwrap();
trace!("Building fmt chunk");
buf.write_all(&format.to_chunk()).unwrap();
trace!("Building fact chunk");
buf.write_all(&fact.to_chunk()).unwrap();
write!(buf, "data").unwrap();
buf.write_u32::<LE>(length).unwrap();
buf.write_all(data).unwrap();
Ok(buf.into_inner())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_riff_header_to_chunk() {
assert_eq!(
RIFFHeader { file_size: 12345 }.to_chunk(),
b"RIFF\x39\x30\x00\x00WAVE"
);
}
#[test]
fn test_wave_fact_to_chunk() {
assert_eq!(
WaveFact {
length_samples: 12345
}
.to_chunk(),
b"fact\x04\x00\x00\x00\x39\x30\x00\x00"
);
}
#[test]
fn test_wave_format_to_chunk() {
assert_eq!(
WaveFormat {
channels: 2,
sample_rate: 44100,
block_align: 140,
}
.to_chunk(),
vec![
0x66, 0x6d, 0x74, 0x20, 0x32, 0x00, 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, 0x44, 0xac,
0x00, 0x00, 0x20, 0xbc, 0x00, 0x00, 0x8c, 0x00, 0x04, 0x00, 0x20, 0x00, 0x80, 0x00,
0x07, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00,
0xc0, 0x00, 0x40, 0x00, 0xf0, 0x00, 0x00, 0x00, 0xcc, 0x01, 0x30, 0xff, 0x88, 0x01,
0x18, 0xff
]
);
}
#[test]
fn test_build_wav() {
let built_wav = build_wav(
WaveFormat {
channels: 2,
sample_rate: 44100,
block_align: 140,
},
b"data",
);
assert_eq!(
built_wav.unwrap(),
vec![
0x52, 0x49, 0x46, 0x46, 0x56, 0x00, 0x00, 0x00, 0x57, 0x41, 0x56, 0x45, 0x66, 0x6d,
0x74, 0x20, 0x32, 0x00, 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, 0x44, 0xac, 0x00, 0x00,
0x20, 0xbc, 0x00, 0x00, 0x8c, 0x00, 0x04, 0x00, 0x20, 0x00, 0x80, 0x00, 0x07, 0x00,
0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00,
0x40, 0x00, 0xf0, 0x00, 0x00, 0x00, 0xcc, 0x01, 0x30, 0xff, 0x88, 0x01, 0x18, 0xff,
0x66, 0x61, 0x63, 0x74, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x61,
0x74, 0x61, 0x04, 0x00, 0x00, 0x00, 0x64, 0x61, 0x74, 0x61
]
);
}
}