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 amount = amount.iter().map(|value| {
|
||||||
let value = value.to_raw();
|
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 {
|
let sustain = match sustain {
|
||||||
|
@ -143,13 +143,15 @@ impl quote::ToTokens for Envelope<'_> {
|
||||||
};
|
};
|
||||||
|
|
||||||
tokens.append_all(quote! {
|
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 {
|
agb_tracker::__private::agb_tracker_interop::Envelope {
|
||||||
amount: AMOUNTS,
|
amount: AMOUNTS,
|
||||||
sustain: #sustain,
|
sustain: #sustain,
|
||||||
loop_start: #loop_start,
|
loop_start: #loop_start,
|
||||||
loop_end: #loop_end,
|
loop_end: #loop_end,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -90,6 +90,7 @@ pub use agb_tracker_interop::Track;
|
||||||
pub struct Tracker {
|
pub struct Tracker {
|
||||||
track: &'static Track<'static>,
|
track: &'static Track<'static>,
|
||||||
channels: Vec<TrackerChannel>,
|
channels: Vec<TrackerChannel>,
|
||||||
|
envelopes: Vec<Option<EnvelopeState>>,
|
||||||
|
|
||||||
frame: Num<u32, 8>,
|
frame: Num<u32, 8>,
|
||||||
tick: u32,
|
tick: u32,
|
||||||
|
@ -101,12 +102,18 @@ pub struct Tracker {
|
||||||
current_pattern: usize,
|
current_pattern: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
struct TrackerChannel {
|
struct TrackerChannel {
|
||||||
channel_id: Option<ChannelId>,
|
channel_id: Option<ChannelId>,
|
||||||
base_speed: Num<u32, 16>,
|
base_speed: Num<u32, 16>,
|
||||||
volume: Num<i32, 8>,
|
volume: Num<i32, 8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct EnvelopeState {
|
||||||
|
frame: usize,
|
||||||
|
envelope_id: usize,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct GlobalSettings {
|
struct GlobalSettings {
|
||||||
ticks_per_step: u32,
|
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.
|
/// 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<'static>) -> Self {
|
||||||
let mut channels = Vec::new();
|
let mut channels = Vec::new();
|
||||||
channels.resize_with(track.num_channels, || TrackerChannel {
|
channels.resize_with(track.num_channels, Default::default);
|
||||||
channel_id: None,
|
|
||||||
base_speed: 0.into(),
|
let mut envelopes = Vec::new();
|
||||||
volume: 0.into(),
|
envelopes.resize_with(track.num_channels, || None);
|
||||||
});
|
|
||||||
|
|
||||||
let global_settings = GlobalSettings {
|
let global_settings = GlobalSettings {
|
||||||
ticks_per_step: track.ticks_per_step,
|
ticks_per_step: track.ticks_per_step,
|
||||||
|
@ -132,6 +138,7 @@ impl Tracker {
|
||||||
Self {
|
Self {
|
||||||
track,
|
track,
|
||||||
channels,
|
channels,
|
||||||
|
envelopes,
|
||||||
|
|
||||||
frame: 0.into(),
|
frame: 0.into(),
|
||||||
first: true,
|
first: true,
|
||||||
|
@ -148,6 +155,7 @@ impl Tracker {
|
||||||
/// See the [example](crate#example) for how to use the tracker.
|
/// See the [example](crate#example) for how to use the tracker.
|
||||||
pub fn step(&mut self, mixer: &mut Mixer) {
|
pub fn step(&mut self, mixer: &mut Mixer) {
|
||||||
if !self.increment_frame() {
|
if !self.increment_frame() {
|
||||||
|
self.update_envelopes(mixer);
|
||||||
return;
|
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 {
|
fn increment_frame(&mut self) -> bool {
|
||||||
|
@ -220,8 +258,6 @@ impl Tracker {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn increment_step(&mut self) {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TrackerChannel {
|
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)]
|
#[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 agb_tracker_interop::PatternEffect;
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::TokenStream;
|
||||||
|
@ -9,7 +9,7 @@ use syn::LitStr;
|
||||||
|
|
||||||
use agb_fixnum::Num;
|
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 {
|
pub fn agb_xm_core(args: TokenStream) -> TokenStream {
|
||||||
let input = match syn::parse::<LitStr>(args.into()) {
|
let input = match syn::parse::<LitStr>(args.into()) {
|
||||||
|
@ -489,7 +489,46 @@ struct EnvelopeData {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&xmrs::envelope::Envelope> for EnvelopeData {
|
impl From<&xmrs::envelope::Envelope> for EnvelopeData {
|
||||||
fn from(value: &xmrs::envelope::Envelope) -> Self {
|
fn from(e: &xmrs::envelope::Envelope) -> Self {
|
||||||
todo!()
|
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