First pass at implementing envelope playing

This commit is contained in:
Gwilym Inzani 2023-08-05 00:02:50 +01:00
parent ca4cb55b39
commit 47455a0377
3 changed files with 121 additions and 19 deletions

View file

@ -126,7 +126,7 @@ impl quote::ToTokens for Envelope<'_> {
let amount = amount.iter().map(|value| {
let value = value.to_raw();
quote! { agb_tracker::__private::agb_tracker_interop::Num::from_raw(#value) }
quote! { agb_tracker::__private::Num::from_raw(#value) }
});
let sustain = match sustain {
@ -143,7 +143,8 @@ impl quote::ToTokens for Envelope<'_> {
};
tokens.append_all(quote! {
const AMOUNTS: &[agb_tracker::__private::agb_tracker_interop::Num<i16, 8>] = &[#(#amount),*];
{
const AMOUNTS: &[agb_tracker::__private::Num<i16, 8>] = &[#(#amount),*];
agb_tracker::__private::agb_tracker_interop::Envelope {
amount: AMOUNTS,
@ -151,6 +152,7 @@ impl quote::ToTokens for Envelope<'_> {
loop_start: #loop_start,
loop_end: #loop_end,
}
}
});
}
}

View file

@ -90,6 +90,7 @@ pub use agb_tracker_interop::Track;
pub struct Tracker {
track: &'static Track<'static>,
channels: Vec<TrackerChannel>,
envelopes: Vec<Option<EnvelopeState>>,
frame: Num<u32, 8>,
tick: u32,
@ -101,12 +102,18 @@ pub struct Tracker {
current_pattern: usize,
}
#[derive(Default)]
struct TrackerChannel {
channel_id: Option<ChannelId>,
base_speed: Num<u32, 16>,
volume: Num<i32, 8>,
}
struct EnvelopeState {
frame: usize,
envelope_id: usize,
}
#[derive(Clone)]
struct GlobalSettings {
ticks_per_step: u32,
@ -118,11 +125,10 @@ 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 {
let mut channels = Vec::new();
channels.resize_with(track.num_channels, || TrackerChannel {
channel_id: None,
base_speed: 0.into(),
volume: 0.into(),
});
channels.resize_with(track.num_channels, Default::default);
let mut envelopes = Vec::new();
envelopes.resize_with(track.num_channels, || None);
let global_settings = GlobalSettings {
ticks_per_step: track.ticks_per_step,
@ -132,6 +138,7 @@ impl Tracker {
Self {
track,
channels,
envelopes,
frame: 0.into(),
first: true,
@ -148,6 +155,7 @@ impl Tracker {
/// See the [example](crate#example) for how to use the tracker.
pub fn step(&mut self, mixer: &mut Mixer) {
if !self.increment_frame() {
self.update_envelopes(mixer);
return;
}
@ -183,7 +191,37 @@ impl Tracker {
);
}
self.increment_step();
self.update_envelopes(mixer);
}
fn update_envelopes(&mut self, mixer: &mut Mixer) {
for (channel, envelope_state_option) in self.channels.iter_mut().zip(&mut self.envelopes) {
if let Some(envelope_state) = envelope_state_option {
let envelope = &self.track.envelopes[envelope_state.envelope_id];
if !channel.update_volume_envelope(mixer, envelope_state.frame, envelope) {
envelope_state_option.take();
} else {
envelope_state.frame += 1;
if let Some(sustain) = envelope.sustain {
if envelope_state.frame >= sustain {
envelope_state.frame = sustain;
}
}
if let Some(loop_end) = envelope.loop_end {
if envelope_state.frame >= loop_end {
envelope_state.frame = envelope.loop_start.unwrap_or(0);
}
}
if envelope_state.frame >= envelope.amount.len() {
envelope_state_option.take();
}
}
}
}
}
fn increment_frame(&mut self) -> bool {
@ -220,8 +258,6 @@ impl Tracker {
false
}
}
fn increment_step(&mut self) {}
}
impl TrackerChannel {
@ -350,6 +386,31 @@ impl TrackerChannel {
_ => {}
}
}
#[must_use]
fn update_volume_envelope(
&self,
mixer: &mut Mixer<'_>,
frame: usize,
envelope: &agb_tracker_interop::Envelope<'_>,
) -> bool {
if let Some(channel) = self
.channel_id
.as_ref()
.and_then(|channel_id| mixer.channel(channel_id))
{
let amount = envelope.amount[frame];
channel.volume(
(self.volume * amount.change_base())
.try_change_base()
.unwrap(),
);
true
} else {
false
}
}
}
#[cfg(test)]

View file

@ -1,4 +1,4 @@
use std::{collections::HashMap, env, error::Error, fs, path::Path};
use std::{collections::HashMap, error::Error, fs, path::Path};
use agb_tracker_interop::PatternEffect;
use proc_macro2::TokenStream;
@ -9,7 +9,7 @@ use syn::LitStr;
use agb_fixnum::Num;
use xmrs::{envelope, prelude::*, xm::xmmodule::XmModule};
use xmrs::{prelude::*, xm::xmmodule::XmModule};
pub fn agb_xm_core(args: TokenStream) -> TokenStream {
let input = match syn::parse::<LitStr>(args.into()) {
@ -489,7 +489,46 @@ struct EnvelopeData {
}
impl From<&xmrs::envelope::Envelope> for EnvelopeData {
fn from(value: &xmrs::envelope::Envelope) -> Self {
todo!()
fn from(e: &xmrs::envelope::Envelope) -> Self {
let mut amounts = vec![Num::new(e.point[0].value as i16) / 0xff];
// it should be sampled at 50fps, but we're sampling at 60fps, so need to do a bit of cheating here.
for frame in 1..(e.point.last().unwrap().frame * 60 / 50) {
let xm_frame = (frame * 50 / 60).max(1);
let index = e
.point
.iter()
.rposition(|point| point.frame < xm_frame)
.unwrap();
let first_point = &e.point[index];
let second_point = &e.point[index + 1];
let amount = EnvelopePoint::lerp(first_point, second_point, xm_frame) / 255.0;
let amount = Num::from_raw((amount * (1 << 8) as f32) as i16);
amounts.push(amount);
}
let sustain = if e.sustain_enabled {
Some(e.point[e.sustain_point as usize].frame as usize * 60 / 50)
} else {
None
};
let (loop_start, loop_end) = if e.loop_enabled {
(
Some(e.point[e.loop_start_point as usize].frame as usize * 60 / 50),
Some(e.point[e.loop_end_point as usize].frame as usize * 60 / 50),
)
} else {
(None, None)
};
EnvelopeData {
amounts,
sustain,
loop_start,
loop_end,
}
}
}