diff --git a/tracker/agb-midi-core/src/lib.rs b/tracker/agb-midi-core/src/lib.rs index e9f03446..0c012cbc 100644 --- a/tracker/agb-midi-core/src/lib.rs +++ b/tracker/agb-midi-core/src/lib.rs @@ -1,4 +1,5 @@ use std::{ + borrow::Cow, collections::HashMap, error::Error, fs::{self, File}, @@ -386,7 +387,7 @@ pub fn parse_midi(midi_info: &MidiInfo) -> TokenStream { let samples: Vec<_> = samples .iter() .map(|sample| Sample { - data: &sample.data, + data: sample.data.clone().into(), should_loop: sample.restart_point.is_some(), restart_point: sample.restart_point.unwrap_or(0), volume: 256.into(), @@ -409,7 +410,7 @@ pub fn parse_midi(midi_info: &MidiInfo) -> TokenStream { let envelopes: Vec<_> = envelopes .iter() .map(|envelope| Envelope { - amount: &envelope.amounts, + amount: envelope.amounts.clone().into(), sustain: Some(envelope.amounts.len() - 1), loop_start: None, loop_end: None, @@ -417,14 +418,14 @@ pub fn parse_midi(midi_info: &MidiInfo) -> TokenStream { .collect(); let track = Track { - samples: &samples, - envelopes: &envelopes, - pattern_data: &pattern, - patterns: &[Pattern { + samples: samples.into(), + envelopes: envelopes.into(), + patterns: Cow::from(vec![Pattern { length: pattern.len() / resulting_num_channels, start_position: 0, - }], - patterns_to_play: &[0], + }]), + pattern_data: pattern.into(), + patterns_to_play: Cow::from(vec![0]), num_channels: resulting_num_channels, frames_per_tick: Num::from_f64(frames_per_tick), ticks_per_step: 1, diff --git a/tracker/agb-tracker-interop/src/lib.rs b/tracker/agb-tracker-interop/src/lib.rs index e9ccebc0..57aa64ce 100644 --- a/tracker/agb-tracker-interop/src/lib.rs +++ b/tracker/agb-tracker-interop/src/lib.rs @@ -1,14 +1,17 @@ #![cfg_attr(not(feature = "std"), no_std)] +extern crate alloc; + use agb_fixnum::Num; +use alloc::borrow::Cow; #[derive(Debug)] -pub struct Track<'a> { - pub samples: &'a [Sample<'a>], - pub envelopes: &'a [Envelope<'a>], - pub pattern_data: &'a [PatternSlot], - pub patterns: &'a [Pattern], - pub patterns_to_play: &'a [usize], +pub struct Track { + pub samples: Cow<'static, [Sample]>, + pub envelopes: Cow<'static, [Envelope]>, + pub pattern_data: Cow<'static, [PatternSlot]>, + pub patterns: Cow<'static, [Pattern]>, + pub patterns_to_play: Cow<'static, [usize]>, pub num_channels: usize, pub frames_per_tick: Num, @@ -16,9 +19,9 @@ pub struct Track<'a> { pub repeat: usize, } -#[derive(Debug)] -pub struct Sample<'a> { - pub data: &'a [u8], +#[derive(Debug, Clone)] +pub struct Sample { + pub data: Cow<'static, [u8]>, pub should_loop: bool, pub restart_point: u32, pub volume: Num, @@ -26,7 +29,7 @@ pub struct Sample<'a> { pub fadeout: Num, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Pattern { pub length: usize, pub start_position: usize, @@ -40,9 +43,9 @@ pub struct PatternSlot { pub effect2: PatternEffect, } -#[derive(Debug)] -pub struct Envelope<'a> { - pub amount: &'a [Num], +#[derive(Debug, Clone)] +pub struct Envelope { + pub amount: Cow<'static, [Num]>, pub sustain: Option, pub loop_start: Option, pub loop_end: Option, @@ -75,7 +78,7 @@ pub enum PatternEffect { } #[cfg(feature = "quote")] -impl<'a> quote::ToTokens for Track<'a> { +impl quote::ToTokens for Track { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { use quote::{quote, TokenStreamExt}; @@ -95,20 +98,24 @@ impl<'a> quote::ToTokens for Track<'a> { tokens.append_all(quote! { { - static SAMPLES: &[agb_tracker::__private::agb_tracker_interop::Sample<'static>] = &[#(#samples),*]; - static PATTERN_DATA: &[agb_tracker::__private::agb_tracker_interop::PatternSlot] = &[#(#pattern_data),*]; - static PATTERNS: &[agb_tracker::__private::agb_tracker_interop::Pattern] = &[#(#patterns),*]; + use alloc::borrow::Cow; + use agb_tracker::__private::agb_tracker_interop::*; + use agb_tracker::__private::Num; + + static SAMPLES: &[Sample] = &[#(#samples),*]; + static PATTERN_DATA: &[PatternSlot] = &[#(#pattern_data),*]; + static PATTERNS: &[Pattern] = &[#(#patterns),*]; static PATTERNS_TO_PLAY: &[usize] = &[#(#patterns_to_play),*]; - static ENVELOPES: &[agb_tracker::__private::agb_tracker_interop::Envelope<'static>] = &[#(#envelopes),*]; + static ENVELOPES: &[Envelope] = &[#(#envelopes),*]; agb_tracker::Track { - samples: SAMPLES, - envelopes: ENVELOPES, - pattern_data: PATTERN_DATA, - patterns: PATTERNS, - patterns_to_play: PATTERNS_TO_PLAY, + samples: Cow::Borrowed(SAMPLES), + envelopes: Cow::Borrowed(ENVELOPES), + pattern_data: Cow::Borrowed(PATTERN_DATA), + patterns: Cow::Borrowed(PATTERNS), + patterns_to_play: Cow::Borrowed(PATTERNS_TO_PLAY), - frames_per_tick: agb_tracker::__private::Num::from_raw(#frames_per_tick), + frames_per_tick: Num::from_raw(#frames_per_tick), num_channels: #num_channels, ticks_per_step: #ticks_per_step, repeat: #repeat, @@ -119,7 +126,7 @@ impl<'a> quote::ToTokens for Track<'a> { } #[cfg(feature = "quote")] -impl quote::ToTokens for Envelope<'_> { +impl quote::ToTokens for Envelope { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { use quote::{quote, TokenStreamExt}; @@ -153,7 +160,7 @@ impl quote::ToTokens for Envelope<'_> { static AMOUNTS: &[agb_tracker::__private::Num] = &[#(#amount),*]; agb_tracker::__private::agb_tracker_interop::Envelope { - amount: AMOUNTS, + amount: Cow::Borrowed(AMOUNTS), sustain: #sustain, loop_start: #loop_start, loop_end: #loop_end, @@ -175,7 +182,7 @@ impl quote::ToTokens for ByteString<'_> { } #[cfg(feature = "quote")] -impl<'a> quote::ToTokens for Sample<'a> { +impl quote::ToTokens for Sample { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { use quote::{quote, TokenStreamExt}; @@ -204,7 +211,7 @@ impl<'a> quote::ToTokens for Sample<'a> { static SAMPLE_DATA: &[u8] = &AlignmentWrapper(*#samples).0; agb_tracker::__private::agb_tracker_interop::Sample { - data: SAMPLE_DATA, + data: Cow::Borrowed(SAMPLE_DATA), should_loop: #should_loop, restart_point: #restart_point, volume: agb_tracker::__private::Num::from_raw(#volume), diff --git a/tracker/agb-tracker/examples/basic.rs b/tracker/agb-tracker/examples/basic.rs index fc9b827e..53740009 100644 --- a/tracker/agb-tracker/examples/basic.rs +++ b/tracker/agb-tracker/examples/basic.rs @@ -1,6 +1,8 @@ #![no_std] #![no_main] +extern crate alloc; + use agb::sound::mixer::Frequency; use agb::Gba; use agb_tracker::{include_xm, Track, Tracker}; diff --git a/tracker/agb-tracker/examples/timing.rs b/tracker/agb-tracker/examples/timing.rs index 7ee7d034..81addb6a 100644 --- a/tracker/agb-tracker/examples/timing.rs +++ b/tracker/agb-tracker/examples/timing.rs @@ -1,6 +1,8 @@ #![no_std] #![no_main] +extern crate alloc; + use agb::sound::mixer::Frequency; use agb::Gba; use agb_tracker::{include_xm, Track, Tracker}; diff --git a/tracker/agb-tracker/src/lib.rs b/tracker/agb-tracker/src/lib.rs index 8312717f..0ef3a4cf 100644 --- a/tracker/agb-tracker/src/lib.rs +++ b/tracker/agb-tracker/src/lib.rs @@ -95,7 +95,7 @@ pub use agb_tracker_interop::Track; /// Stores the required state in order to play tracker music. pub struct Tracker { - track: &'static Track<'static>, + track: &'static Track, channels: Vec, envelopes: Vec>, @@ -134,7 +134,7 @@ struct GlobalSettings { impl Tracker { /// Create a new tracker playing a specified track. See the [example](crate#example) for how to use the tracker. - pub fn new(track: &'static Track<'static>) -> Self { + pub fn new(track: &'static Track) -> Self { let mut channels = Vec::new(); channels.resize_with(track.num_channels, Default::default); @@ -292,7 +292,7 @@ impl TrackerChannel { fn play_sound( &mut self, mixer: &mut Mixer<'_>, - sample: &Sample<'static>, + sample: &Sample, global_settings: &GlobalSettings, ) { if let Some(channel) = self @@ -303,7 +303,12 @@ impl TrackerChannel { channel.stop(); } - let mut new_channel = SoundChannel::new(sample.data); + let mut new_channel = SoundChannel::new(match sample.data { + alloc::borrow::Cow::Borrowed(data) => data, + alloc::borrow::Cow::Owned(_) => { + unimplemented!("Must use borrowed COW data for tracker") + } + }); new_channel.volume( (sample.volume.change_base() * global_settings.volume) @@ -482,7 +487,7 @@ impl TrackerChannel { &mut self, mixer: &mut Mixer<'_>, envelope_state: &EnvelopeState, - envelope: &agb_tracker_interop::Envelope<'_>, + envelope: &agb_tracker_interop::Envelope, global_settings: &GlobalSettings, ) -> bool { if let Some(channel) = self diff --git a/tracker/agb-xm-core/Cargo.toml b/tracker/agb-xm-core/Cargo.toml index daa83a32..abb0bde3 100644 --- a/tracker/agb-xm-core/Cargo.toml +++ b/tracker/agb-xm-core/Cargo.toml @@ -13,7 +13,7 @@ proc-macro2 = "1" quote = "1" syn = "2" -agb_tracker_interop = { version = "0.20.5", path = "../agb-tracker-interop" } +agb_tracker_interop = { version = "0.20.5", path = "../agb-tracker-interop", default-features = false } agb_fixnum = { version = "0.20.5", path = "../../agb-fixnum" } xmrs = { version = "0.6.1", features = ["std"] } diff --git a/tracker/agb-xm-core/src/lib.rs b/tracker/agb-xm-core/src/lib.rs index 5181ca80..e64f5cf4 100644 --- a/tracker/agb-xm-core/src/lib.rs +++ b/tracker/agb-xm-core/src/lib.rs @@ -1,51 +1,11 @@ -use std::{collections::HashMap, error::Error, fs, path::Path}; - -use agb_tracker_interop::PatternEffect; -use proc_macro2::TokenStream; -use proc_macro_error::abort; - -use quote::quote; -use syn::LitStr; +use std::collections::HashMap; use agb_fixnum::Num; +use agb_tracker_interop::PatternEffect; -use xmrs::{prelude::*, xm::xmmodule::XmModule}; +use xmrs::prelude::*; -pub fn agb_xm_core(args: TokenStream) -> TokenStream { - let input = match syn::parse::(args.into()) { - Ok(input) => input, - Err(err) => return err.to_compile_error(), - }; - - 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 module = match load_module_from_file(&path) { - Ok(track) => track, - Err(e) => abort!(input, e), - }; - - let parsed = parse_module(&module); - - quote! { - { - const _: &[u8] = include_bytes!(#include_path); - - #parsed - } - } -} - -pub fn load_module_from_file(xm_path: &Path) -> Result> { - let file_content = fs::read(xm_path)?; - Ok(XmModule::load(&file_content)?.to_module()) -} - -pub fn parse_module(module: &Module) -> TokenStream { +pub fn parse_module(module: &Module) -> agb_tracker_interop::Track { let instruments = &module.instrument; let mut instruments_map = HashMap::new(); @@ -414,7 +374,7 @@ pub fn parse_module(module: &Module) -> TokenStream { let samples: Vec<_> = samples .iter() .map(|sample| agb_tracker_interop::Sample { - data: &sample.data, + data: sample.data.clone().into(), should_loop: sample.should_loop, restart_point: sample.restart_point, volume: sample.volume, @@ -432,7 +392,7 @@ pub fn parse_module(module: &Module) -> TokenStream { let envelopes = envelopes .iter() .map(|envelope| agb_tracker_interop::Envelope { - amount: &envelope.amounts, + amount: envelope.amounts.clone().into(), sustain: envelope.sustain, loop_start: envelope.loop_start, loop_end: envelope.loop_end, @@ -442,20 +402,18 @@ pub fn parse_module(module: &Module) -> TokenStream { let frames_per_tick = bpm_to_frames_per_tick(module.default_bpm as u32); let ticks_per_step = module.default_tempo; - let interop = agb_tracker_interop::Track { - samples: &samples, - pattern_data: &pattern_data, - patterns: &patterns, + agb_tracker_interop::Track { + samples: samples.into(), + pattern_data: pattern_data.into(), + patterns: patterns.into(), num_channels: module.get_num_channels(), - patterns_to_play: &patterns_to_play, - envelopes: &envelopes, + patterns_to_play: patterns_to_play.into(), + envelopes: envelopes.into(), frames_per_tick, ticks_per_step: ticks_per_step.into(), repeat: module.restart_position as usize, - }; - - quote!(#interop) + } } fn bpm_to_frames_per_tick(bpm: u32) -> Num { diff --git a/tracker/agb-xm/Cargo.toml b/tracker/agb-xm/Cargo.toml index cc35322b..20bc9e5d 100644 --- a/tracker/agb-xm/Cargo.toml +++ b/tracker/agb-xm/Cargo.toml @@ -14,3 +14,8 @@ proc-macro = true agb_xm_core = { version = "0.20.5", path = "../agb-xm-core" } proc-macro-error = "1" proc-macro2 = "1" + +quote = "1" +syn = "2" + +xmrs = "0.6" diff --git a/tracker/agb-xm/src/lib.rs b/tracker/agb-xm/src/lib.rs index bea1dcb4..749d3c7e 100644 --- a/tracker/agb-xm/src/lib.rs +++ b/tracker/agb-xm/src/lib.rs @@ -1,8 +1,49 @@ +use std::{error::Error, fs, path::Path}; + +use agb_xm_core::parse_module; use proc_macro::TokenStream; -use proc_macro_error::proc_macro_error; +use proc_macro_error::{abort, proc_macro_error}; +use quote::quote; +use syn::LitStr; +use xmrs::{module::Module, xm::xmmodule::XmModule}; #[proc_macro_error] #[proc_macro] pub fn include_xm(args: TokenStream) -> TokenStream { - agb_xm_core::agb_xm_core(args.into()).into() + agb_xm_core(args) +} + +fn agb_xm_core(args: TokenStream) -> TokenStream { + let input = match syn::parse::(args) { + Ok(input) => input, + Err(err) => return err.to_compile_error().into(), + }; + + 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 module = match load_module_from_file(&path) { + Ok(track) => track, + Err(e) => abort!(input, e), + }; + + let parsed = parse_module(&module); + + quote! { + { + const _: &[u8] = include_bytes!(#include_path); + + #parsed + } + } + .into() +} + +fn load_module_from_file(xm_path: &Path) -> Result> { + let file_content = fs::read(xm_path)?; + Ok(XmModule::load(&file_content)?.to_module()) }