This repository has been archived on 2024-01-28. You can view files and clone it, but cannot push or open issues or pull requests.
brd/src/xact3/adpcm.rs
Simon Bruder f9c0140100
Add documentation and tests for xact3::adpcm
This also renames variables to be easier to understand (rather than
copying the naming scheme of the spec) and moves the calculation of
computable fields in the wave format header into the adpcm module.
2020-07-05 21:17:42 +02:00

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
]
);
}
}