Simon Bruder
f9c0140100
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.
235 lines
7.2 KiB
Rust
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
|
|
]
|
|
);
|
|
}
|
|
}
|