agb/tracker/agb-tracker-interop/src/lib.rs

459 lines
15 KiB
Rust
Raw Normal View History

2023-07-12 12:18:02 +01:00
#![cfg_attr(not(feature = "std"), no_std)]
2023-07-12 15:38:09 +01:00
extern crate alloc;
2023-07-12 15:38:09 +01:00
use agb_fixnum::Num;
use alloc::borrow::Cow;
2023-07-12 15:38:09 +01:00
#[derive(Debug)]
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]>,
2023-07-12 17:36:41 +01:00
2023-07-12 18:52:29 +01:00
pub num_channels: usize,
pub frames_per_tick: Num<u32, 8>,
pub ticks_per_step: u32,
pub repeat: usize,
2023-07-12 15:38:09 +01:00
}
#[derive(Debug, Clone)]
pub struct Sample {
pub data: Cow<'static, [u8]>,
2023-07-12 17:36:41 +01:00
pub should_loop: bool,
2023-07-16 23:12:42 +01:00
pub restart_point: u32,
2023-07-23 22:10:25 +01:00
pub volume: Num<i16, 8>,
2023-08-04 23:19:07 +01:00
pub volume_envelope: Option<usize>,
2023-08-05 23:51:12 +01:00
pub fadeout: Num<i32, 8>,
2023-07-12 15:38:09 +01:00
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Jump {
/// Jump to the given pattern position `pattern` at row index 0
Position { pattern: u8 },
/// Jump to the next pattern position, at row index `row`
PatternBreak { row: u8 },
/// Jump to the pattern position `pattern` at row index `row`
Combined { pattern: u8, row: u8 },
}
2024-08-28 17:38:24 +01:00
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RetriggerVolumeChange {
/// Decrease the volume by one each retrigger
DecreaseByOne,
/// Don't change it
NoChange,
}
#[derive(Debug, Clone)]
2023-07-12 15:38:09 +01:00
pub struct Pattern {
2023-07-12 17:36:41 +01:00
pub length: usize,
pub start_position: usize,
2023-07-12 15:38:09 +01:00
}
2023-11-15 14:44:28 +00:00
#[derive(Debug, Clone, Default, PartialEq, Eq)]
2023-07-12 15:38:09 +01:00
pub struct PatternSlot {
pub speed: Num<u16, 8>,
pub sample: u16,
2023-07-16 23:57:11 +01:00
pub effect1: PatternEffect,
pub effect2: PatternEffect,
2023-07-12 15:38:09 +01:00
}
#[derive(Debug, Clone)]
pub struct Envelope {
pub amount: Cow<'static, [Num<i16, 8>]>,
pub sustain: Option<usize>,
pub loop_start: Option<usize>,
pub loop_end: Option<usize>,
2024-07-10 14:47:59 +01:00
pub vib_waveform: Waveform,
2024-07-10 14:51:15 +01:00
pub vib_amount: Num<i16, 12>,
2024-07-10 14:47:59 +01:00
pub vib_speed: u8,
2023-08-04 23:17:59 +01:00
}
2023-11-15 14:44:28 +00:00
#[derive(Debug, Default, Clone, PartialEq, Eq)]
2023-07-16 23:57:11 +01:00
pub enum PatternEffect {
/// Don't play an effect
#[default]
None,
/// Stops playing the current note
Stop,
/// Plays an arpeggiation of three notes in one row, cycling betwen the current note, current note + first speed, current note + second speed
Arpeggio(Num<u16, 8>, Num<u16, 8>),
Panning(Num<i16, 4>),
2023-07-23 22:10:25 +01:00
Volume(Num<i16, 8>),
// bool = maintain vibrato?
VolumeSlide(Num<i16, 8>, bool),
2023-07-23 22:10:25 +01:00
FineVolumeSlide(Num<i16, 8>),
2023-07-19 13:22:26 +01:00
NoteCut(u32),
2024-05-15 22:30:13 +01:00
NoteDelay(u32),
Portamento(Num<u16, 12>),
2024-07-10 12:01:19 +01:00
FinePortamento(Num<u16, 12>),
2023-07-23 21:36:02 +01:00
/// Slide each tick the first amount to at most the second amount
TonePortamento(Num<u16, 12>, Num<u16, 12>),
2024-07-10 11:48:09 +01:00
Vibrato(Waveform, Num<u16, 12>, u8),
2023-08-04 22:30:49 +01:00
SetTicksPerStep(u32),
SetFramesPerTick(Num<u32, 8>),
2023-08-05 22:41:55 +01:00
SetGlobalVolume(Num<i32, 8>),
GlobalVolumeSlide(Num<i32, 8>),
2023-11-15 13:40:13 +00:00
/// Increase / decrease the pitch by the specified amount immediately
PitchBend(Num<u32, 8>),
Jump(Jump),
2024-08-28 16:32:28 +01:00
SampleOffset(u16),
2024-08-28 17:38:24 +01:00
/// Retrigger the note every u8 ticks with the volume change specified
Retrigger(RetriggerVolumeChange, u8),
2023-07-16 23:57:11 +01:00
}
2023-07-12 23:41:30 +01:00
2024-07-10 14:47:59 +01:00
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Waveform {
#[default]
Sine,
Saw,
Square,
}
#[cfg(feature = "quote")]
impl quote::ToTokens for Jump {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
use quote::{quote, TokenStreamExt};
let type_bit = match self {
Jump::Position { pattern } => {
quote! {Position{pattern: #pattern} }
}
Jump::PatternBreak { row } => {
quote! { PatternBreak{row: #row} }
}
Jump::Combined { pattern, row } => {
quote! {
Combined{pattern: #pattern, row: #row }
}
}
};
tokens.append_all(quote! {
agb_tracker::__private::agb_tracker_interop::Jump::#type_bit
});
}
}
2024-08-28 17:38:24 +01:00
#[cfg(feature = "quote")]
impl quote::ToTokens for RetriggerVolumeChange {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
use quote::{quote, TokenStreamExt};
let type_bit = match self {
RetriggerVolumeChange::DecreaseByOne => quote!(DecreaseByOne),
RetriggerVolumeChange::NoChange => quote!(NoChange),
};
tokens.append_all(quote! {
agb_tracker::__private::agb_tracker_interop::RetriggerVolumeChange::#type_bit
});
}
}
2023-07-12 15:38:09 +01:00
#[cfg(feature = "quote")]
impl quote::ToTokens for Track {
2023-07-12 15:38:09 +01:00
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
use quote::{quote, TokenStreamExt};
2023-07-12 17:36:41 +01:00
let Track {
samples,
2023-08-04 23:17:59 +01:00
envelopes,
2023-07-12 17:36:41 +01:00
pattern_data,
patterns,
frames_per_tick,
2023-07-12 18:52:29 +01:00
num_channels,
2023-07-12 19:06:55 +01:00
patterns_to_play,
ticks_per_step,
repeat,
2023-07-12 17:36:41 +01:00
} = self;
2023-07-12 15:38:09 +01:00
let frames_per_tick = frames_per_tick.to_raw();
2023-07-12 15:38:09 +01:00
tokens.append_all(quote! {
{
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),*];
2023-12-10 16:35:15 +00:00
static PATTERNS_TO_PLAY: &[usize] = &[#(#patterns_to_play),*];
static ENVELOPES: &[Envelope] = &[#(#envelopes),*];
2023-07-12 15:38:09 +01:00
agb_tracker::Track {
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),
2023-07-12 17:36:41 +01:00
frames_per_tick: Num::from_raw(#frames_per_tick),
2023-07-12 18:52:29 +01:00
num_channels: #num_channels,
ticks_per_step: #ticks_per_step,
repeat: #repeat,
2023-07-12 15:38:09 +01:00
}
}
})
}
}
2023-08-04 23:17:59 +01:00
#[cfg(feature = "quote")]
impl quote::ToTokens for Envelope {
2023-08-04 23:17:59 +01:00
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
use quote::{quote, TokenStreamExt};
let Envelope {
amount,
sustain,
loop_start,
loop_end,
2024-07-10 14:47:59 +01:00
vib_amount,
vib_speed,
vib_waveform,
2023-08-04 23:17:59 +01:00
} = self;
let amount = amount.iter().map(|value| {
let value = value.to_raw();
quote! { agb_tracker::__private::Num::from_raw(#value) }
2023-08-04 23:17:59 +01:00
});
2024-07-10 14:47:59 +01:00
let vib_amount = {
let value = vib_amount.to_raw();
quote! { agb_tracker::__private::Num::from_raw(#value) }
};
2023-08-04 23:17:59 +01:00
let sustain = match sustain {
Some(value) => quote!(Some(#value)),
None => quote!(None),
};
let loop_start = match loop_start {
Some(value) => quote!(Some(#value)),
None => quote!(None),
};
let loop_end = match loop_end {
Some(value) => quote!(Some(#value)),
None => quote!(None),
};
tokens.append_all(quote! {
{
2023-12-10 16:35:15 +00:00
static AMOUNTS: &[agb_tracker::__private::Num<i16, 8>] = &[#(#amount),*];
2023-08-04 23:17:59 +01:00
agb_tracker::__private::agb_tracker_interop::Envelope {
amount: Cow::Borrowed(AMOUNTS),
sustain: #sustain,
loop_start: #loop_start,
loop_end: #loop_end,
2024-07-10 14:47:59 +01:00
vib_waveform: #vib_waveform,
vib_amount: #vib_amount,
vib_speed: #vib_speed,
}
2023-08-04 23:17:59 +01:00
}
});
}
}
2023-07-12 17:36:41 +01:00
#[cfg(feature = "quote")]
struct ByteString<'a>(&'a [u8]);
#[cfg(feature = "quote")]
impl quote::ToTokens for ByteString<'_> {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
use quote::TokenStreamExt;
tokens.append(proc_macro2::Literal::byte_string(self.0));
}
}
2023-07-12 15:38:09 +01:00
#[cfg(feature = "quote")]
impl quote::ToTokens for Sample {
2023-07-12 15:38:09 +01:00
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
use quote::{quote, TokenStreamExt};
2023-07-18 21:36:37 +01:00
let Sample {
data,
should_loop,
restart_point,
volume,
2023-08-04 23:19:07 +01:00
volume_envelope,
2023-08-05 23:51:12 +01:00
fadeout,
2023-07-18 21:36:37 +01:00
} = self;
2023-08-04 23:19:07 +01:00
let volume_envelope = match volume_envelope {
2023-08-04 23:17:59 +01:00
Some(index) => quote!(Some(#index)),
None => quote!(None),
};
2023-08-05 23:51:12 +01:00
let fadeout = fadeout.to_raw();
2023-08-04 23:17:59 +01:00
2023-07-23 19:08:51 +01:00
let samples = ByteString(data);
2023-07-18 21:36:37 +01:00
let volume = volume.to_raw();
2023-07-12 15:38:09 +01:00
tokens.append_all(quote! {
{
2023-07-12 17:36:41 +01:00
#[repr(align(4))]
struct AlignmentWrapper<const N: usize>([u8; N]);
2023-12-10 16:35:15 +00:00
static SAMPLE_DATA: &[u8] = &AlignmentWrapper(*#samples).0;
2023-07-18 21:36:37 +01:00
agb_tracker::__private::agb_tracker_interop::Sample {
data: Cow::Borrowed(SAMPLE_DATA),
2023-07-18 21:36:37 +01:00
should_loop: #should_loop,
restart_point: #restart_point,
volume: agb_tracker::__private::Num::from_raw(#volume),
2023-08-04 23:19:07 +01:00
volume_envelope: #volume_envelope,
2023-08-05 23:51:12 +01:00
fadeout: agb_tracker::__private::Num::from_raw(#fadeout),
2023-07-18 21:36:37 +01:00
}
2023-07-12 15:38:09 +01:00
}
});
}
}
#[cfg(feature = "quote")]
impl quote::ToTokens for PatternSlot {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
use quote::{quote, TokenStreamExt};
let PatternSlot {
speed,
sample,
2023-07-16 23:57:11 +01:00
effect1,
effect2,
2023-07-12 15:38:09 +01:00
} = &self;
let speed = speed.to_raw();
tokens.append_all(quote! {
agb_tracker::__private::agb_tracker_interop::PatternSlot {
speed: agb_tracker::__private::Num::from_raw(#speed),
sample: #sample,
2023-07-16 23:57:11 +01:00
effect1: #effect1,
effect2: #effect2,
2023-07-12 15:38:09 +01:00
}
});
}
}
#[cfg(feature = "quote")]
impl quote::ToTokens for Pattern {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
use quote::{quote, TokenStreamExt};
2023-07-12 17:36:41 +01:00
let Pattern {
length,
start_position,
} = self;
2023-07-12 15:38:09 +01:00
tokens.append_all(quote! {
agb_tracker::__private::agb_tracker_interop::Pattern {
length: #length,
start_position: #start_position,
2023-07-12 15:38:09 +01:00
}
})
}
}
2023-07-16 23:57:11 +01:00
#[cfg(feature = "quote")]
impl quote::ToTokens for PatternEffect {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
use quote::{quote, TokenStreamExt};
let type_bit = match self {
PatternEffect::None => quote! { None },
PatternEffect::Stop => quote! { Stop },
PatternEffect::Arpeggio(first, second) => {
let first = first.to_raw();
let second = second.to_raw();
quote! { Arpeggio(agb_tracker::__private::Num::from_raw(#first), agb_tracker::__private::Num::from_raw(#second)) }
}
PatternEffect::Panning(panning) => {
let panning = panning.to_raw();
quote! { Panning(agb_tracker::__private::Num::from_raw(#panning))}
}
PatternEffect::Volume(volume) => {
let volume = volume.to_raw();
quote! { Volume(agb_tracker::__private::Num::from_raw(#volume))}
}
PatternEffect::VolumeSlide(amount, vibrato) => {
2023-07-17 00:45:58 +01:00
let amount = amount.to_raw();
quote! { VolumeSlide(agb_tracker::__private::Num::from_raw(#amount), #vibrato)}
2023-07-17 00:45:58 +01:00
}
2023-07-23 22:03:32 +01:00
PatternEffect::FineVolumeSlide(amount) => {
let amount = amount.to_raw();
quote! { FineVolumeSlide(agb_tracker::__private::Num::from_raw(#amount))}
}
2023-07-19 13:22:26 +01:00
PatternEffect::NoteCut(wait) => quote! { NoteCut(#wait) },
2024-05-15 22:30:13 +01:00
PatternEffect::NoteDelay(wait) => quote! { NoteDelay(#wait) },
2023-07-19 13:38:32 +01:00
PatternEffect::Portamento(amount) => {
let amount = amount.to_raw();
quote! { Portamento(agb_tracker::__private::Num::from_raw(#amount))}
}
2024-07-10 12:01:19 +01:00
PatternEffect::FinePortamento(amount) => {
let amount = amount.to_raw();
quote! { FinePortamento(agb_tracker::__private::Num::from_raw(#amount))}
}
2023-07-23 21:36:02 +01:00
PatternEffect::TonePortamento(amount, target) => {
let amount = amount.to_raw();
let target = target.to_raw();
quote! { TonePortamento(agb_tracker::__private::Num::from_raw(#amount), agb_tracker::__private::Num::from_raw(#target))}
}
2023-08-04 22:30:49 +01:00
PatternEffect::SetTicksPerStep(new_ticks) => {
quote! { SetTicksPerStep(#new_ticks) }
}
PatternEffect::SetFramesPerTick(new_frames_per_tick) => {
let amount = new_frames_per_tick.to_raw();
quote! { SetFramesPerTick(agb_tracker::__private::Num::from_raw(#amount)) }
}
2023-08-05 22:41:55 +01:00
PatternEffect::SetGlobalVolume(amount) => {
let amount = amount.to_raw();
quote! { SetGlobalVolume(agb_tracker::__private::Num::from_raw(#amount)) }
}
PatternEffect::GlobalVolumeSlide(amount) => {
let amount = amount.to_raw();
quote! { GlobalVolumeSlide(agb_tracker::__private::Num::from_raw(#amount)) }
}
2023-11-15 13:40:13 +00:00
PatternEffect::PitchBend(amount) => {
let amount = amount.to_raw();
quote! { PitchBend(agb_tracker::__private::Num::from_raw(#amount)) }
}
2024-07-10 11:48:09 +01:00
PatternEffect::Vibrato(waveform, amount, speed) => {
let amount = amount.to_raw();
2024-07-10 12:11:10 +01:00
quote! { Vibrato(#waveform, agb_tracker::__private::Num::from_raw(#amount), #speed) }
2024-07-10 11:48:09 +01:00
}
PatternEffect::Jump(jump) => {
quote! { Jump(#jump) }
}
2024-08-28 16:32:28 +01:00
PatternEffect::SampleOffset(offset) => quote! { SampleOffset(#offset) },
2024-08-28 17:38:24 +01:00
PatternEffect::Retrigger(retrigger_volume_change, ticks) => {
quote! { Retrigger(#retrigger_volume_change, #ticks) }
}
2023-07-16 23:57:11 +01:00
};
tokens.append_all(quote! {
agb_tracker::__private::agb_tracker_interop::PatternEffect::#type_bit
});
}
}
#[cfg(feature = "quote")]
impl quote::ToTokens for Waveform {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
use quote::{quote, TokenStreamExt};
let name = match self {
Waveform::Sine => quote!(Sine),
Waveform::Saw => quote!(Saw),
Waveform::Square => quote!(Square),
};
tokens.append_all(quote! {
agb_tracker::__private::agb_tracker_interop::Waveform::#name
});
}
}