2021-10-19 07:15:11 +11:00
|
|
|
#![deny(clippy::all)]
|
|
|
|
|
2021-10-18 07:59:01 +11:00
|
|
|
use proc_macro::TokenStream;
|
|
|
|
use quote::quote;
|
2021-10-18 09:22:36 +11:00
|
|
|
use siphasher::sip::SipHasher;
|
|
|
|
use std::{
|
2021-10-19 06:21:22 +11:00
|
|
|
fs,
|
2021-10-18 09:22:36 +11:00
|
|
|
fs::File,
|
|
|
|
hash::{Hash, Hasher},
|
|
|
|
io::Write,
|
|
|
|
path::Path,
|
|
|
|
};
|
2021-10-18 07:59:01 +11:00
|
|
|
use syn::parse_macro_input;
|
|
|
|
|
|
|
|
#[proc_macro]
|
|
|
|
pub fn include_wav(input: TokenStream) -> TokenStream {
|
|
|
|
let input = parse_macro_input!(input as syn::LitStr);
|
|
|
|
|
|
|
|
let filename = input.value();
|
|
|
|
|
|
|
|
let root = std::env::var("CARGO_MANIFEST_DIR").expect("Failed to get cargo manifest dir");
|
|
|
|
let path = Path::new(&root).join(&*filename);
|
|
|
|
|
|
|
|
let include_path = path.to_string_lossy();
|
|
|
|
|
2021-10-18 09:22:36 +11:00
|
|
|
let out_file_path_include = {
|
|
|
|
let out_dir = std::env::var("OUT_DIR").expect("Expected OUT_DIR");
|
|
|
|
let out_filename = get_out_filename(&path);
|
|
|
|
|
|
|
|
let out_file_path = Path::new(&out_dir).with_file_name(&out_filename);
|
|
|
|
|
2021-10-19 06:22:55 +11:00
|
|
|
let out_file_mtime = fs::metadata(&out_file_path).and_then(|metadata| metadata.modified());
|
|
|
|
let in_file_mtime = fs::metadata(&path).and_then(|metadata| metadata.modified());
|
2021-10-18 09:22:36 +11:00
|
|
|
|
2021-10-19 06:21:22 +11:00
|
|
|
let should_write = match (out_file_mtime, in_file_mtime) {
|
2021-10-19 06:22:55 +11:00
|
|
|
(Ok(out_file_mtime), Ok(in_file_mtime)) => out_file_mtime <= in_file_mtime,
|
2021-10-19 06:21:22 +11:00
|
|
|
_ => true,
|
|
|
|
};
|
|
|
|
|
|
|
|
if should_write {
|
2021-10-19 06:22:21 +11:00
|
|
|
let wav_reader = hound::WavReader::open(&path)
|
|
|
|
.unwrap_or_else(|_| panic!("Failed to load file {}", include_path));
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
wav_reader.spec().sample_rate,
|
|
|
|
10512,
|
|
|
|
"agb currently only supports sample rate of 10512Hz"
|
|
|
|
);
|
|
|
|
|
|
|
|
let samples = samples_from_reader(wav_reader);
|
|
|
|
|
2021-10-19 06:21:22 +11:00
|
|
|
let mut out_file =
|
|
|
|
File::create(&out_file_path).expect("Failed to open file for writing");
|
|
|
|
|
|
|
|
out_file
|
|
|
|
.write_all(&samples.collect::<Vec<_>>())
|
|
|
|
.expect("Failed to write to temporary file");
|
|
|
|
}
|
2021-10-18 09:22:36 +11:00
|
|
|
|
|
|
|
out_file_path
|
|
|
|
}
|
|
|
|
.canonicalize()
|
|
|
|
.expect("Failed to canonicalize");
|
|
|
|
|
|
|
|
let out_file_path_include = out_file_path_include.to_string_lossy();
|
|
|
|
|
2021-10-18 07:59:01 +11:00
|
|
|
let result = quote! {
|
|
|
|
{
|
2021-10-27 08:14:14 +11:00
|
|
|
#[repr(align(4))]
|
|
|
|
struct AlignmentWrapper<const N: usize>([u8; N]);
|
|
|
|
|
2021-10-18 07:59:01 +11:00
|
|
|
const _: &[u8] = include_bytes!(#include_path);
|
|
|
|
|
2021-10-27 08:14:14 +11:00
|
|
|
&AlignmentWrapper(*include_bytes!(#out_file_path_include)).0
|
2021-10-18 07:59:01 +11:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
TokenStream::from(result)
|
|
|
|
}
|
2021-10-18 08:37:50 +11:00
|
|
|
|
|
|
|
fn samples_from_reader<'a, R>(reader: hound::WavReader<R>) -> Box<dyn Iterator<Item = u8> + 'a>
|
|
|
|
where
|
|
|
|
R: std::io::Read + 'a,
|
|
|
|
{
|
|
|
|
let bitrate = reader.spec().bits_per_sample;
|
|
|
|
let reduction = bitrate - 8;
|
|
|
|
|
|
|
|
match reader.spec().sample_format {
|
|
|
|
hound::SampleFormat::Float => Box::new(
|
|
|
|
reader
|
|
|
|
.into_samples::<f32>()
|
|
|
|
.map(|sample| (sample.unwrap() * (i8::MAX as f32)) as u8),
|
|
|
|
),
|
|
|
|
hound::SampleFormat::Int => Box::new(
|
|
|
|
reader
|
|
|
|
.into_samples::<i32>()
|
|
|
|
.map(move |sample| (sample.unwrap() >> reduction) as u8),
|
|
|
|
),
|
|
|
|
}
|
|
|
|
}
|
2021-10-18 09:22:36 +11:00
|
|
|
|
|
|
|
fn get_out_filename(path: &Path) -> String {
|
|
|
|
let mut hasher = SipHasher::new();
|
|
|
|
path.hash(&mut hasher);
|
|
|
|
|
|
|
|
format!("{}.raw", hasher.finish())
|
|
|
|
}
|