diff --git a/.github/workflows/publish-sound-converter.yml b/.github/workflows/publish-sound-converter.yml new file mode 100644 index 00000000..6ac465fa --- /dev/null +++ b/.github/workflows/publish-sound-converter.yml @@ -0,0 +1,20 @@ +name: Publish agb-sound-converter + +on: + push: + tags: + - agb-sound-converter/v* + +jobs: + build: + runs-on: ubuntu-20.04 + steps: + - name: Install build tools + run: sudo apt-get update && sudo apt-get install build-essential binutils-arm-none-eabi -y + - name: Check out repository + uses: actions/checkout@v2 + - name: Login to crates.io + run: cargo login ${{ secrets.CRATE_API }} + - name: Publish agb-sound-converter + run: cargo publish + working-directory: ./agb-sound-converter \ No newline at end of file diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index aa504826..fc9bc6f0 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -38,6 +38,9 @@ jobs: - name: Run Clippy on agb image converter working-directory: agb-image-converter run: cargo clippy --verbose + - name: Run Clippy on agb sound converter + working-directory: agb-sound-converter + run: cargo clippy --verbose - name: Run Clippy on agb macros working-directory: agb-macros run: cargo clippy --verbose diff --git a/agb-sound-converter/Cargo.lock b/agb-sound-converter/Cargo.lock new file mode 100644 index 00000000..eacdb02c --- /dev/null +++ b/agb-sound-converter/Cargo.lock @@ -0,0 +1,61 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "agb_sound_converter" +version = "0.1.0" +dependencies = [ + "hound", + "proc-macro2", + "quote", + "siphasher", + "syn", +] + +[[package]] +name = "hound" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a164bb2ceaeff4f42542bdb847c41517c78a60f5649671b2a07312b6e117549" + +[[package]] +name = "proc-macro2" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edc3358ebc67bc8b7fa0c007f945b0b18226f78437d61bec735a9eb96b61ee70" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "siphasher" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "533494a8f9b724d33625ab53c6c4800f7cc445895924a8ef649222dcb76e938b" + +[[package]] +name = "syn" +version = "1.0.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d010a1623fbd906d51d650a9916aaefc05ffa0e4053ff7fe601167f3e715d194" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" diff --git a/agb-sound-converter/Cargo.toml b/agb-sound-converter/Cargo.toml new file mode 100644 index 00000000..78b5ee6e --- /dev/null +++ b/agb-sound-converter/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "agb_sound_converter" +version = "0.1.0" +authors = ["Gwilym Kuiper "] +edition = "2018" +license = "MPL-2.0" +description = "Library for converting wavs for use on the Game Boy Advance" + +[profile.dev] +opt-level = 3 +debug = true + +[profile.release] +lto = true +debug = true + +[lib] +proc-macro = true + +[dependencies] +hound = "3.4.0" +syn = "1.0.73" +proc-macro2 = "1.0.27" +quote = "1.0.9" +siphasher = "0.3.7" \ No newline at end of file diff --git a/agb-sound-converter/src/lib.rs b/agb-sound-converter/src/lib.rs new file mode 100644 index 00000000..4b6ddebf --- /dev/null +++ b/agb-sound-converter/src/lib.rs @@ -0,0 +1,104 @@ +#![deny(clippy::all)] + +use proc_macro::TokenStream; +use quote::quote; +use siphasher::sip::SipHasher; +use std::{ + fs, + fs::File, + hash::{Hash, Hasher}, + io::Write, + path::Path, +}; +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(); + + 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); + + 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()); + + let should_write = match (out_file_mtime, in_file_mtime) { + (Ok(out_file_mtime), Ok(in_file_mtime)) => out_file_mtime <= in_file_mtime, + _ => true, + }; + + if should_write { + 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); + + let mut out_file = + File::create(&out_file_path).expect("Failed to open file for writing"); + + out_file + .write_all(&samples.collect::>()) + .expect("Failed to write to temporary file"); + } + + out_file_path + } + .canonicalize() + .expect("Failed to canonicalize"); + + let out_file_path_include = out_file_path_include.to_string_lossy(); + + let result = quote! { + { + const _: &[u8] = include_bytes!(#include_path); + + include_bytes!(#out_file_path_include) + } + }; + + TokenStream::from(result) +} + +fn samples_from_reader<'a, R>(reader: hound::WavReader) -> Box + '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::() + .map(|sample| (sample.unwrap() * (i8::MAX as f32)) as u8), + ), + hound::SampleFormat::Int => Box::new( + reader + .into_samples::() + .map(move |sample| (sample.unwrap() >> reduction) as u8), + ), + } +} + +fn get_out_filename(path: &Path) -> String { + let mut hasher = SipHasher::new(); + path.hash(&mut hasher); + + format!("{}.raw", hasher.finish()) +} diff --git a/agb/Cargo.lock b/agb/Cargo.lock index cf91b596..8477b1ad 100644 --- a/agb/Cargo.lock +++ b/agb/Cargo.lock @@ -14,6 +14,7 @@ version = "0.7.0" dependencies = [ "agb_image_converter", "agb_macros", + "agb_sound_converter", "bitflags", ] @@ -39,6 +40,17 @@ dependencies = [ "syn", ] +[[package]] +name = "agb_sound_converter" +version = "0.1.0" +dependencies = [ + "hound", + "proc-macro2", + "quote", + "siphasher", + "syn", +] + [[package]] name = "autocfg" version = "1.0.1" @@ -105,6 +117,12 @@ dependencies = [ "wasi", ] +[[package]] +name = "hound" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a164bb2ceaeff4f42542bdb847c41517c78a60f5649671b2a07312b6e117549" + [[package]] name = "image" version = "0.23.14" @@ -272,6 +290,12 @@ dependencies = [ "syn", ] +[[package]] +name = "siphasher" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "533494a8f9b724d33625ab53c6c4800f7cc445895924a8ef649222dcb76e938b" + [[package]] name = "syn" version = "1.0.78" diff --git a/agb/Cargo.toml b/agb/Cargo.toml index 023e2935..d48336f8 100644 --- a/agb/Cargo.toml +++ b/agb/Cargo.toml @@ -21,6 +21,7 @@ alloc = [] [dependencies] bitflags = "1.2" agb_image_converter = { version = "0.6.0", path = "../agb-image-converter" } +agb_sound_converter = { version = "0.1.0", path = "../agb-sound-converter" } agb_macros = { version = "0.1.0", path = "../agb-macros" } [package.metadata.docs.rs] diff --git a/agb/examples/JoshWoodward-DeadCode.wav b/agb/examples/JoshWoodward-DeadCode.wav new file mode 100644 index 00000000..667064ad Binary files /dev/null and b/agb/examples/JoshWoodward-DeadCode.wav differ diff --git a/agb/examples/i-will-not-let-you-let-me-down.raw b/agb/examples/i-will-not-let-you-let-me-down.raw deleted file mode 100644 index cc272fad..00000000 Binary files a/agb/examples/i-will-not-let-you-let-me-down.raw and /dev/null differ diff --git a/agb/examples/mixer_basic.rs b/agb/examples/mixer_basic.rs index dd1bafb3..e9b03dcb 100644 --- a/agb/examples/mixer_basic.rs +++ b/agb/examples/mixer_basic.rs @@ -6,10 +6,10 @@ extern crate agb; use agb::input::{Button, ButtonController, Tri}; use agb::number::Num; use agb::sound::mixer::SoundChannel; -use agb::Gba; +use agb::{include_wav, Gba}; -// Music - "I will not let you let me down" by Josh Woodward, free download at http://joshwoodward.com -const I_WILL_NOT_LET_YOU_LET_ME_DOWN: &[u8] = include_bytes!("i-will-not-let-you-let-me-down.raw"); +// Music - "Dead Code" by Josh Woodward, free download at http://joshwoodward.com +const DEAD_CODE: &[u8] = include_wav!("examples/JoshWoodward-DeadCode.wav"); #[agb::entry] fn main() -> ! { @@ -20,7 +20,7 @@ fn main() -> ! { let mut mixer = gba.mixer.mixer(); mixer.enable(); - let channel = SoundChannel::new(I_WILL_NOT_LET_YOU_LET_ME_DOWN); + let channel = SoundChannel::new(DEAD_CODE); let channel_id = mixer.play_sound(channel).unwrap(); loop { diff --git a/agb/src/lib.rs b/agb/src/lib.rs index 149ad46a..a16ae0fd 100644 --- a/agb/src/lib.rs +++ b/agb/src/lib.rs @@ -26,6 +26,7 @@ pub mod input; pub mod sound; pub use agb_image_converter::include_gfx; +pub use agb_sound_converter::include_wav; pub use agb_macros::entry; diff --git a/release.sh b/release.sh index 7ff20ea3..f70bed8d 100755 --- a/release.sh +++ b/release.sh @@ -35,6 +35,10 @@ case "$PROJECT" in DIRECTORY="agb-image-converter" TAGNAME="agb-image-converter/v$VERSION" ;; + agb-sound-converter) + DIRECTORY="agb-sound-converter" + TAGNAME="agb-sound-converter/v$VERSION" + ;; agb-macros) DIRECTORY="agb-macros" TAGNAME="agb-macros/v$VERSION" @@ -84,6 +88,7 @@ fi # Sanity check to make sure the build works (cd agb && cargo test) (cd agb-image-converter && cargo test) +(cd agb-sound-converter && cargo test) (cd agb-macros && cargo test) if [ ! "$NO_COMMIT" = "--no-commit" ]; then