Make agb-xm-core return the interop rather than the tokens

This commit is contained in:
Gwilym Inzani 2024-06-05 09:55:07 +01:00
parent 5829d71b6b
commit d00de7b2a4
9 changed files with 120 additions and 99 deletions

View file

@ -1,4 +1,5 @@
use std::{ use std::{
borrow::Cow,
collections::HashMap, collections::HashMap,
error::Error, error::Error,
fs::{self, File}, fs::{self, File},
@ -386,7 +387,7 @@ pub fn parse_midi(midi_info: &MidiInfo) -> TokenStream {
let samples: Vec<_> = samples let samples: Vec<_> = samples
.iter() .iter()
.map(|sample| Sample { .map(|sample| Sample {
data: &sample.data, data: sample.data.clone().into(),
should_loop: sample.restart_point.is_some(), should_loop: sample.restart_point.is_some(),
restart_point: sample.restart_point.unwrap_or(0), restart_point: sample.restart_point.unwrap_or(0),
volume: 256.into(), volume: 256.into(),
@ -409,7 +410,7 @@ pub fn parse_midi(midi_info: &MidiInfo) -> TokenStream {
let envelopes: Vec<_> = envelopes let envelopes: Vec<_> = envelopes
.iter() .iter()
.map(|envelope| Envelope { .map(|envelope| Envelope {
amount: &envelope.amounts, amount: envelope.amounts.clone().into(),
sustain: Some(envelope.amounts.len() - 1), sustain: Some(envelope.amounts.len() - 1),
loop_start: None, loop_start: None,
loop_end: None, loop_end: None,
@ -417,14 +418,14 @@ pub fn parse_midi(midi_info: &MidiInfo) -> TokenStream {
.collect(); .collect();
let track = Track { let track = Track {
samples: &samples, samples: samples.into(),
envelopes: &envelopes, envelopes: envelopes.into(),
pattern_data: &pattern, patterns: Cow::from(vec![Pattern {
patterns: &[Pattern {
length: pattern.len() / resulting_num_channels, length: pattern.len() / resulting_num_channels,
start_position: 0, start_position: 0,
}], }]),
patterns_to_play: &[0], pattern_data: pattern.into(),
patterns_to_play: Cow::from(vec![0]),
num_channels: resulting_num_channels, num_channels: resulting_num_channels,
frames_per_tick: Num::from_f64(frames_per_tick), frames_per_tick: Num::from_f64(frames_per_tick),
ticks_per_step: 1, ticks_per_step: 1,

View file

@ -1,14 +1,17 @@
#![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(not(feature = "std"), no_std)]
extern crate alloc;
use agb_fixnum::Num; use agb_fixnum::Num;
use alloc::borrow::Cow;
#[derive(Debug)] #[derive(Debug)]
pub struct Track<'a> { pub struct Track {
pub samples: &'a [Sample<'a>], pub samples: Cow<'static, [Sample]>,
pub envelopes: &'a [Envelope<'a>], pub envelopes: Cow<'static, [Envelope]>,
pub pattern_data: &'a [PatternSlot], pub pattern_data: Cow<'static, [PatternSlot]>,
pub patterns: &'a [Pattern], pub patterns: Cow<'static, [Pattern]>,
pub patterns_to_play: &'a [usize], pub patterns_to_play: Cow<'static, [usize]>,
pub num_channels: usize, pub num_channels: usize,
pub frames_per_tick: Num<u32, 8>, pub frames_per_tick: Num<u32, 8>,
@ -16,9 +19,9 @@ pub struct Track<'a> {
pub repeat: usize, pub repeat: usize,
} }
#[derive(Debug)] #[derive(Debug, Clone)]
pub struct Sample<'a> { pub struct Sample {
pub data: &'a [u8], pub data: Cow<'static, [u8]>,
pub should_loop: bool, pub should_loop: bool,
pub restart_point: u32, pub restart_point: u32,
pub volume: Num<i16, 8>, pub volume: Num<i16, 8>,
@ -26,7 +29,7 @@ pub struct Sample<'a> {
pub fadeout: Num<i32, 8>, pub fadeout: Num<i32, 8>,
} }
#[derive(Debug)] #[derive(Debug, Clone)]
pub struct Pattern { pub struct Pattern {
pub length: usize, pub length: usize,
pub start_position: usize, pub start_position: usize,
@ -40,9 +43,9 @@ pub struct PatternSlot {
pub effect2: PatternEffect, pub effect2: PatternEffect,
} }
#[derive(Debug)] #[derive(Debug, Clone)]
pub struct Envelope<'a> { pub struct Envelope {
pub amount: &'a [Num<i16, 8>], pub amount: Cow<'static, [Num<i16, 8>]>,
pub sustain: Option<usize>, pub sustain: Option<usize>,
pub loop_start: Option<usize>, pub loop_start: Option<usize>,
pub loop_end: Option<usize>, pub loop_end: Option<usize>,
@ -75,7 +78,7 @@ pub enum PatternEffect {
} }
#[cfg(feature = "quote")] #[cfg(feature = "quote")]
impl<'a> quote::ToTokens for Track<'a> { impl quote::ToTokens for Track {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
use quote::{quote, TokenStreamExt}; use quote::{quote, TokenStreamExt};
@ -95,20 +98,24 @@ impl<'a> quote::ToTokens for Track<'a> {
tokens.append_all(quote! { tokens.append_all(quote! {
{ {
static SAMPLES: &[agb_tracker::__private::agb_tracker_interop::Sample<'static>] = &[#(#samples),*]; use alloc::borrow::Cow;
static PATTERN_DATA: &[agb_tracker::__private::agb_tracker_interop::PatternSlot] = &[#(#pattern_data),*]; use agb_tracker::__private::agb_tracker_interop::*;
static PATTERNS: &[agb_tracker::__private::agb_tracker_interop::Pattern] = &[#(#patterns),*]; 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 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 { agb_tracker::Track {
samples: SAMPLES, samples: Cow::Borrowed(SAMPLES),
envelopes: ENVELOPES, envelopes: Cow::Borrowed(ENVELOPES),
pattern_data: PATTERN_DATA, pattern_data: Cow::Borrowed(PATTERN_DATA),
patterns: PATTERNS, patterns: Cow::Borrowed(PATTERNS),
patterns_to_play: PATTERNS_TO_PLAY, 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, num_channels: #num_channels,
ticks_per_step: #ticks_per_step, ticks_per_step: #ticks_per_step,
repeat: #repeat, repeat: #repeat,
@ -119,7 +126,7 @@ impl<'a> quote::ToTokens for Track<'a> {
} }
#[cfg(feature = "quote")] #[cfg(feature = "quote")]
impl quote::ToTokens for Envelope<'_> { impl quote::ToTokens for Envelope {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
use quote::{quote, TokenStreamExt}; use quote::{quote, TokenStreamExt};
@ -153,7 +160,7 @@ impl quote::ToTokens for Envelope<'_> {
static AMOUNTS: &[agb_tracker::__private::Num<i16, 8>] = &[#(#amount),*]; static AMOUNTS: &[agb_tracker::__private::Num<i16, 8>] = &[#(#amount),*];
agb_tracker::__private::agb_tracker_interop::Envelope { agb_tracker::__private::agb_tracker_interop::Envelope {
amount: AMOUNTS, amount: Cow::Borrowed(AMOUNTS),
sustain: #sustain, sustain: #sustain,
loop_start: #loop_start, loop_start: #loop_start,
loop_end: #loop_end, loop_end: #loop_end,
@ -175,7 +182,7 @@ impl quote::ToTokens for ByteString<'_> {
} }
#[cfg(feature = "quote")] #[cfg(feature = "quote")]
impl<'a> quote::ToTokens for Sample<'a> { impl quote::ToTokens for Sample {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
use quote::{quote, TokenStreamExt}; use quote::{quote, TokenStreamExt};
@ -204,7 +211,7 @@ impl<'a> quote::ToTokens for Sample<'a> {
static SAMPLE_DATA: &[u8] = &AlignmentWrapper(*#samples).0; static SAMPLE_DATA: &[u8] = &AlignmentWrapper(*#samples).0;
agb_tracker::__private::agb_tracker_interop::Sample { agb_tracker::__private::agb_tracker_interop::Sample {
data: SAMPLE_DATA, data: Cow::Borrowed(SAMPLE_DATA),
should_loop: #should_loop, should_loop: #should_loop,
restart_point: #restart_point, restart_point: #restart_point,
volume: agb_tracker::__private::Num::from_raw(#volume), volume: agb_tracker::__private::Num::from_raw(#volume),

View file

@ -1,6 +1,8 @@
#![no_std] #![no_std]
#![no_main] #![no_main]
extern crate alloc;
use agb::sound::mixer::Frequency; use agb::sound::mixer::Frequency;
use agb::Gba; use agb::Gba;
use agb_tracker::{include_xm, Track, Tracker}; use agb_tracker::{include_xm, Track, Tracker};

View file

@ -1,6 +1,8 @@
#![no_std] #![no_std]
#![no_main] #![no_main]
extern crate alloc;
use agb::sound::mixer::Frequency; use agb::sound::mixer::Frequency;
use agb::Gba; use agb::Gba;
use agb_tracker::{include_xm, Track, Tracker}; use agb_tracker::{include_xm, Track, Tracker};

View file

@ -95,7 +95,7 @@ pub use agb_tracker_interop::Track;
/// Stores the required state in order to play tracker music. /// Stores the required state in order to play tracker music.
pub struct Tracker { pub struct Tracker {
track: &'static Track<'static>, track: &'static Track,
channels: Vec<TrackerChannel>, channels: Vec<TrackerChannel>,
envelopes: Vec<Option<EnvelopeState>>, envelopes: Vec<Option<EnvelopeState>>,
@ -134,7 +134,7 @@ struct GlobalSettings {
impl Tracker { impl Tracker {
/// Create a new tracker playing a specified track. See the [example](crate#example) for how to use the 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(); let mut channels = Vec::new();
channels.resize_with(track.num_channels, Default::default); channels.resize_with(track.num_channels, Default::default);
@ -292,7 +292,7 @@ impl TrackerChannel {
fn play_sound( fn play_sound(
&mut self, &mut self,
mixer: &mut Mixer<'_>, mixer: &mut Mixer<'_>,
sample: &Sample<'static>, sample: &Sample,
global_settings: &GlobalSettings, global_settings: &GlobalSettings,
) { ) {
if let Some(channel) = self if let Some(channel) = self
@ -303,7 +303,12 @@ impl TrackerChannel {
channel.stop(); 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( new_channel.volume(
(sample.volume.change_base() * global_settings.volume) (sample.volume.change_base() * global_settings.volume)
@ -482,7 +487,7 @@ impl TrackerChannel {
&mut self, &mut self,
mixer: &mut Mixer<'_>, mixer: &mut Mixer<'_>,
envelope_state: &EnvelopeState, envelope_state: &EnvelopeState,
envelope: &agb_tracker_interop::Envelope<'_>, envelope: &agb_tracker_interop::Envelope,
global_settings: &GlobalSettings, global_settings: &GlobalSettings,
) -> bool { ) -> bool {
if let Some(channel) = self if let Some(channel) = self

View file

@ -13,7 +13,7 @@ proc-macro2 = "1"
quote = "1" quote = "1"
syn = "2" 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" } agb_fixnum = { version = "0.20.5", path = "../../agb-fixnum" }
xmrs = { version = "0.6.1", features = ["std"] } xmrs = { version = "0.6.1", features = ["std"] }

View file

@ -1,51 +1,11 @@
use std::{collections::HashMap, error::Error, fs, path::Path}; use std::collections::HashMap;
use agb_tracker_interop::PatternEffect;
use proc_macro2::TokenStream;
use proc_macro_error::abort;
use quote::quote;
use syn::LitStr;
use agb_fixnum::Num; 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 { pub fn parse_module(module: &Module) -> agb_tracker_interop::Track {
let input = match syn::parse::<LitStr>(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<Module, Box<dyn Error>> {
let file_content = fs::read(xm_path)?;
Ok(XmModule::load(&file_content)?.to_module())
}
pub fn parse_module(module: &Module) -> TokenStream {
let instruments = &module.instrument; let instruments = &module.instrument;
let mut instruments_map = HashMap::new(); let mut instruments_map = HashMap::new();
@ -414,7 +374,7 @@ pub fn parse_module(module: &Module) -> TokenStream {
let samples: Vec<_> = samples let samples: Vec<_> = samples
.iter() .iter()
.map(|sample| agb_tracker_interop::Sample { .map(|sample| agb_tracker_interop::Sample {
data: &sample.data, data: sample.data.clone().into(),
should_loop: sample.should_loop, should_loop: sample.should_loop,
restart_point: sample.restart_point, restart_point: sample.restart_point,
volume: sample.volume, volume: sample.volume,
@ -432,7 +392,7 @@ pub fn parse_module(module: &Module) -> TokenStream {
let envelopes = envelopes let envelopes = envelopes
.iter() .iter()
.map(|envelope| agb_tracker_interop::Envelope { .map(|envelope| agb_tracker_interop::Envelope {
amount: &envelope.amounts, amount: envelope.amounts.clone().into(),
sustain: envelope.sustain, sustain: envelope.sustain,
loop_start: envelope.loop_start, loop_start: envelope.loop_start,
loop_end: envelope.loop_end, 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 frames_per_tick = bpm_to_frames_per_tick(module.default_bpm as u32);
let ticks_per_step = module.default_tempo; let ticks_per_step = module.default_tempo;
let interop = agb_tracker_interop::Track { agb_tracker_interop::Track {
samples: &samples, samples: samples.into(),
pattern_data: &pattern_data, pattern_data: pattern_data.into(),
patterns: &patterns, patterns: patterns.into(),
num_channels: module.get_num_channels(), num_channels: module.get_num_channels(),
patterns_to_play: &patterns_to_play, patterns_to_play: patterns_to_play.into(),
envelopes: &envelopes, envelopes: envelopes.into(),
frames_per_tick, frames_per_tick,
ticks_per_step: ticks_per_step.into(), ticks_per_step: ticks_per_step.into(),
repeat: module.restart_position as usize, repeat: module.restart_position as usize,
}; }
quote!(#interop)
} }
fn bpm_to_frames_per_tick(bpm: u32) -> Num<u32, 8> { fn bpm_to_frames_per_tick(bpm: u32) -> Num<u32, 8> {

View file

@ -14,3 +14,8 @@ proc-macro = true
agb_xm_core = { version = "0.20.5", path = "../agb-xm-core" } agb_xm_core = { version = "0.20.5", path = "../agb-xm-core" }
proc-macro-error = "1" proc-macro-error = "1"
proc-macro2 = "1" proc-macro2 = "1"
quote = "1"
syn = "2"
xmrs = "0.6"

View file

@ -1,8 +1,49 @@
use std::{error::Error, fs, path::Path};
use agb_xm_core::parse_module;
use proc_macro::TokenStream; 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_error]
#[proc_macro] #[proc_macro]
pub fn include_xm(args: TokenStream) -> TokenStream { 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::<LitStr>(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<Module, Box<dyn Error>> {
let file_content = fs::read(xm_path)?;
Ok(XmModule::load(&file_content)?.to_module())
} }