mod decoder;
mod encoder;
use super::audio::{Block, BlockTrait};
use hound::{SampleFormat, WavSpec, WavWriter};
use std::io;
pub use decoder::{DecodeError, Decoder};
pub use encoder::{EncodeError, Encoder};
#[derive(Clone, Default)]
#[cfg_attr(feature = "debug", derive(Debug))]
pub struct MonoAudio {
pub blocks: Vec<Block>,
sample99: i16,
index99: i16,
}
impl MonoAudio {
pub fn channel_count(&self) -> usize {
1
}
pub fn to_wav(&self) -> Result<Vec<u8>, io::Error> {
let mut buffer = io::Cursor::new(Vec::new());
{
let mut writer = WavWriter::new(
&mut buffer,
WavSpec {
channels: self.channel_count() as u16,
sample_rate: 22050,
bits_per_sample: 16,
sample_format: SampleFormat::Int,
},
)
.map_err(io::Error::other)?;
let mut samples = Vec::new();
for i in 0..self.blocks.len() {
let pcm16_block = &self.blocks[i].as_pcm16_block();
for j in 0..pcm16_block.data.len() {
samples.push(pcm16_block.data[j]);
}
}
let mut sample_writer = writer.get_i16_writer(samples.len() as u32);
for sample in samples {
sample_writer.write_sample(sample);
}
sample_writer.flush().map_err(io::Error::other)?;
}
Ok(buffer.into_inner())
}
}
#[cfg(test)]
mod tests {
use super::*;
use io::Write;
use pretty_assertions::assert_eq;
use sha2::{Digest, Sha256};
use std::{
ffi::{OsStr, OsString},
fs::File,
path::{Path, PathBuf},
};
fn roundtrip_test(original_bytes: &[u8], a: &MonoAudio) {
let mut encoded_bytes = Vec::new();
Encoder::new(&mut encoded_bytes).encode(a).unwrap();
let original_bytes = original_bytes
.chunks(16)
.map(|chunk| {
chunk
.iter()
.map(|b| format!("{b:02X}"))
.collect::<Vec<_>>()
.join(" ")
})
.collect::<Vec<_>>()
.join("\n");
let encoded_bytes = encoded_bytes
.chunks(16)
.map(|chunk| {
chunk
.iter()
.map(|b| format!("{b:02X}"))
.collect::<Vec<_>>()
.join(" ")
})
.collect::<Vec<_>>()
.join("\n");
assert_eq!(original_bytes, encoded_bytes);
}
#[test]
fn test_decode_a_ayesir() {
let d: PathBuf = [
std::env::var("DARKOMEN_PATH").unwrap().as_str(),
"DARKOMEN",
"SOUND",
"SP_ENG",
"A_AYESIR.MAD",
]
.iter()
.collect();
let original_bytes = std::fs::read(d.clone()).unwrap();
let file = File::open(d).unwrap();
let a = Decoder::new(file).decode().unwrap();
roundtrip_test(&original_bytes, &a);
let wav = a.to_wav().unwrap();
let mut hasher = Sha256::new();
hasher.update(wav.as_slice());
let result = hasher.finalize();
let result_str = format!("{result:x}");
assert_eq!(
result_str,
"eec85ab61603fb079f0b426b8d463739d21831c67b8cf0b8067d773e84074fd0"
);
}
#[test]
fn test_decode_all() {
let d: PathBuf = [std::env::var("DARKOMEN_PATH").unwrap().as_str(), "DARKOMEN"]
.iter()
.collect();
let root_output_dir: PathBuf = [env!("CARGO_MANIFEST_DIR"), "decoded", "mads"]
.iter()
.collect();
std::fs::create_dir_all(&root_output_dir).unwrap();
fn visit_dirs(dir: &Path, cb: &mut dyn FnMut(&Path)) {
println!("Reading dir {:?}", dir.display());
let mut paths = std::fs::read_dir(dir)
.unwrap()
.map(|res| res.map(|e| e.path()))
.collect::<Result<Vec<_>, std::io::Error>>()
.unwrap();
paths.sort();
for path in paths {
if path.is_dir() {
visit_dirs(&path, cb);
} else {
cb(&path);
}
}
}
visit_dirs(&d, &mut |path| {
let Some(ext) = path.extension() else {
return;
};
if ext.to_string_lossy().to_uppercase() != "MAD" {
return;
}
println!("Decoding {:?}", path.file_name().unwrap());
let original_bytes = std::fs::read(path).unwrap();
let file = File::open(path).unwrap();
let a = Decoder::new(file).decode().unwrap();
roundtrip_test(&original_bytes, &a);
let parent_dir = path
.components()
.collect::<Vec<_>>()
.iter()
.rev()
.skip(1) .take_while(|c| c.as_os_str() != "DARKOMEN")
.collect::<Vec<_>>()
.iter()
.rev()
.collect::<PathBuf>();
let output_dir = root_output_dir.join(parent_dir);
std::fs::create_dir_all(&output_dir).unwrap();
let output_path = append_ext("wav", output_dir.join(path.file_name().unwrap()));
let mut output_file = File::create(output_path).unwrap();
output_file
.write_all(a.to_wav().unwrap().as_slice())
.unwrap();
});
}
fn append_ext(ext: impl AsRef<OsStr>, path: PathBuf) -> PathBuf {
let mut os_string: OsString = path.into();
os_string.push(".");
os_string.push(ext.as_ref());
os_string.into()
}
}