mirror of
https://github.com/italicsjenga/agb.git
synced 2024-12-23 08:11:33 +11:00
First pass at implementing envelope playing
This commit is contained in:
parent
ca4cb55b39
commit
47455a0377
|
@ -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,
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue