mirror of
https://github.com/italicsjenga/agb.git
synced 2024-12-23 00:01:34 +11:00
Really basic export
This commit is contained in:
parent
f3e3c243a4
commit
a77b536e69
|
@ -8,8 +8,10 @@ repository = "https://github.com/agbrs/agb"
|
|||
|
||||
[features]
|
||||
default = ["quote"]
|
||||
quote = ["dep:quote", "std"]
|
||||
quote = ["dep:quote", "dep:proc-macro2", "std"]
|
||||
std = []
|
||||
|
||||
[dependencies]
|
||||
quote = { version = "1", optional = true }
|
||||
quote = { version = "1", optional = true }
|
||||
proc-macro2 = { version = "1", optional = true }
|
||||
agb_fixnum = { version = "0.15.0", path = "../../agb-fixnum" }
|
|
@ -1 +1,124 @@
|
|||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
use agb_fixnum::Num;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Track<'a> {
|
||||
pub samples: &'a [Sample<'a>],
|
||||
pub pattern_data: &'a [PatternSlot],
|
||||
pub patterns: &'a [Pattern],
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Sample<'a> {
|
||||
pub data: &'a [u8],
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Pattern {
|
||||
pub num_channels: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PatternSlot {
|
||||
pub volume: Num<i16, 4>,
|
||||
pub speed: Num<u32, 8>,
|
||||
pub panning: Num<i16, 4>,
|
||||
pub sample: usize,
|
||||
}
|
||||
|
||||
#[cfg(feature = "quote")]
|
||||
impl<'a> quote::ToTokens for Track<'a> {
|
||||
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
||||
use quote::{quote, TokenStreamExt};
|
||||
|
||||
let samples = self.samples;
|
||||
let pattern_data = self.pattern_data;
|
||||
let patterns = self.patterns;
|
||||
|
||||
tokens.append_all(quote! {
|
||||
{
|
||||
use agb_tracker_interop::*;
|
||||
|
||||
const SAMPLES: &[Sample<'static>] = &[#(#samples),*];
|
||||
const PATTERN_DATA: &[PatternSlot] = &[#(#pattern_data),*];
|
||||
const PATTERNS: &[Pattern] = &[#(#patterns),*];
|
||||
|
||||
Track {
|
||||
samples: SAMPLES,
|
||||
pattern_data: PATTERN_DATA,
|
||||
patterns: PATTERNS,
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "quote")]
|
||||
impl<'a> quote::ToTokens for Sample<'a> {
|
||||
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
||||
use quote::{quote, TokenStreamExt};
|
||||
|
||||
let self_as_u8s = self.data.iter().map(|i| *i as u8);
|
||||
|
||||
tokens.append_all(quote! {
|
||||
{
|
||||
use agb_tracker_interop::*;
|
||||
|
||||
const SAMPLE_DATA: &[u8] = &[#(#self_as_u8s),*];
|
||||
agb_tracker_interop::Sample { data: SAMPLE_DATA }
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "quote")]
|
||||
impl quote::ToTokens for PatternSlot {
|
||||
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
||||
use quote::{quote, TokenStreamExt};
|
||||
|
||||
let PatternSlot {
|
||||
volume,
|
||||
speed,
|
||||
panning,
|
||||
sample,
|
||||
} = &self;
|
||||
|
||||
let volume = volume.to_raw();
|
||||
let speed = speed.to_raw();
|
||||
let panning = panning.to_raw();
|
||||
|
||||
tokens.append_all(quote! {
|
||||
{
|
||||
use agb_tracker::__private::*;
|
||||
use agb::fixnum::Num;
|
||||
|
||||
PatternSlot {
|
||||
volume: Num::from_raw(#volume),
|
||||
speed: Num::from_raw(#speed),
|
||||
panning: Num::from_raw(#panning),
|
||||
sample: #sample,
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "quote")]
|
||||
impl quote::ToTokens for Pattern {
|
||||
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
||||
use quote::{quote, TokenStreamExt};
|
||||
|
||||
let num_channels = self.num_channels;
|
||||
|
||||
tokens.append_all(quote! {
|
||||
{
|
||||
use agb_tracker_interop::*;
|
||||
|
||||
Pattern {
|
||||
num_channels: #num_channels,
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
BIN
tracker/agb-tracker/examples/ajoj.xm
Normal file
BIN
tracker/agb-tracker/examples/ajoj.xm
Normal file
Binary file not shown.
21
tracker/agb-tracker/examples/basic.rs
Normal file
21
tracker/agb-tracker/examples/basic.rs
Normal file
|
@ -0,0 +1,21 @@
|
|||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use agb::sound::mixer::Frequency;
|
||||
use agb::Gba;
|
||||
use agb_tracker::{import_xm, Track};
|
||||
|
||||
const AJOJ: Track = import_xm!("examples/ajoj.xm");
|
||||
|
||||
#[agb::entry]
|
||||
fn main(mut gba: Gba) -> ! {
|
||||
let vblank_provider = agb::interrupt::VBlank::get();
|
||||
|
||||
let mut mixer = gba.mixer.mixer(Frequency::Hz18157);
|
||||
mixer.enable();
|
||||
|
||||
loop {
|
||||
mixer.frame();
|
||||
vblank_provider.wait_for_vblank();
|
||||
}
|
||||
}
|
|
@ -5,6 +5,13 @@
|
|||
#![cfg_attr(test, reexport_test_harness_main = "test_main")]
|
||||
#![cfg_attr(test, test_runner(agb::test_runner::test_runner))]
|
||||
|
||||
#[cfg(feature = "xm")]
|
||||
pub use agb_xm::import_xm;
|
||||
|
||||
pub use agb_tracker_interop as __private;
|
||||
|
||||
pub use __private::Track;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test_case]
|
||||
|
|
|
@ -11,5 +11,9 @@ repository = "https://github.com/agbrs/agb"
|
|||
proc-macro-error = "1"
|
||||
proc-macro2 = "1"
|
||||
quote = "1"
|
||||
syn = "2"
|
||||
|
||||
agb_tracker_interop = { version = "0.15.0", path = "../agb-tracker-interop" }
|
||||
agb_tracker_interop = { version = "0.15.0", path = "../agb-tracker-interop" }
|
||||
agb_fixnum = { version = "0.15.0", path = "../../agb-fixnum" }
|
||||
|
||||
xmrs = "0.3"
|
|
@ -1,14 +1,136 @@
|
|||
use std::{collections::HashMap, error::Error, fs, path::Path};
|
||||
|
||||
use proc_macro2::TokenStream;
|
||||
use proc_macro_error::abort;
|
||||
|
||||
use quote::quote;
|
||||
use syn::LitStr;
|
||||
|
||||
use agb_fixnum::Num;
|
||||
|
||||
use xmrs::{prelude::*, xm::xmmodule::XmModule};
|
||||
|
||||
pub fn agb_xm_core(args: TokenStream) -> TokenStream {
|
||||
if args.is_empty() {
|
||||
abort!(args, "must pass a filename");
|
||||
}
|
||||
let input = match syn::parse::<LitStr>(args.into()) {
|
||||
Ok(input) => input,
|
||||
Err(err) => return proc_macro2::TokenStream::from(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! {
|
||||
fn hello_world() {}
|
||||
{
|
||||
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 mut instruments_map = HashMap::new();
|
||||
|
||||
let mut samples = vec![];
|
||||
|
||||
for (instrument_index, instrument) in instruments.iter().enumerate() {
|
||||
let InstrumentType::Default(ref instrument) = instrument.instr_type else { continue; };
|
||||
|
||||
for (sample_index, sample) in instrument.sample.iter().enumerate() {
|
||||
let sample = match &sample.data {
|
||||
SampleDataType::Depth8(depth8) => depth8
|
||||
.iter()
|
||||
.map(|value| *value as u8)
|
||||
.collect::<Vec<_>>()
|
||||
.clone(),
|
||||
SampleDataType::Depth16(depth16) => depth16
|
||||
.iter()
|
||||
.map(|sample| (sample >> 8) as i8 as u8)
|
||||
.collect::<Vec<_>>(),
|
||||
};
|
||||
|
||||
instruments_map.insert((instrument_index, sample_index), samples.len());
|
||||
samples.push(sample);
|
||||
}
|
||||
}
|
||||
|
||||
let mut patterns = vec![];
|
||||
let mut pattern_data = vec![];
|
||||
|
||||
for pattern in &module.pattern {
|
||||
let mut num_channels = 0;
|
||||
|
||||
for row in pattern.iter() {
|
||||
for slot in row {
|
||||
let sample = if slot.instrument == 0 {
|
||||
0
|
||||
} else {
|
||||
let instrument_index = (slot.instrument - 1) as usize;
|
||||
|
||||
if let InstrumentType::Default(ref instrument) =
|
||||
module.instrument[instrument_index].instr_type
|
||||
{
|
||||
let sample_slot = instrument.sample_for_note[slot.note as usize] as usize;
|
||||
instruments_map
|
||||
.get(&(instrument_index, sample_slot))
|
||||
.cloned()
|
||||
.unwrap_or(0)
|
||||
} else {
|
||||
0
|
||||
}
|
||||
};
|
||||
|
||||
let volume = Num::new(
|
||||
if slot.volume == 0 {
|
||||
64
|
||||
} else {
|
||||
slot.volume as i16
|
||||
} / 64,
|
||||
);
|
||||
let speed = Num::new(1); // TODO: Calculate speed for the correct note here
|
||||
let panning = Num::new(0);
|
||||
|
||||
pattern_data.push(agb_tracker_interop::PatternSlot {
|
||||
volume,
|
||||
speed,
|
||||
panning,
|
||||
sample,
|
||||
});
|
||||
}
|
||||
|
||||
num_channels = row.len();
|
||||
}
|
||||
|
||||
patterns.push(agb_tracker_interop::Pattern { num_channels });
|
||||
}
|
||||
|
||||
let samples: Vec<_> = samples
|
||||
.iter()
|
||||
.map(|sample| agb_tracker_interop::Sample { data: &sample })
|
||||
.collect();
|
||||
|
||||
let interop = agb_tracker_interop::Track {
|
||||
samples: &samples,
|
||||
pattern_data: &pattern_data,
|
||||
patterns: &patterns,
|
||||
};
|
||||
|
||||
quote!(#interop)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue