mirror of
https://github.com/italicsjenga/agb.git
synced 2024-12-23 00:01:34 +11:00
Update tracker to support PatternBreak and PositionJump (#748)
This pull request adds support for pattern breaks and position jumps to the music tracker module. This PR only adds functionality for XM input files for now. However, these changes would also be applicable to other types of music modules, such as Amiga modules. In `agb-xm-core`: * handle effects Bxx, Dxx, and their combination In `agb-tracker-interop`: * represent jump using a `PatternEffect` In `agb-tracker`: * keep track of any applicable jump in `TrackerInner`, and handle it in `increment_frame()` when needed
This commit is contained in:
commit
a79829068c
|
@ -29,6 +29,16 @@ pub struct Sample {
|
|||
pub fadeout: Num<i32, 8>,
|
||||
}
|
||||
|
||||
#[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 },
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Pattern {
|
||||
pub length: usize,
|
||||
|
@ -82,6 +92,7 @@ pub enum PatternEffect {
|
|||
GlobalVolumeSlide(Num<i32, 8>),
|
||||
/// Increase / decrease the pitch by the specified amount immediately
|
||||
PitchBend(Num<u32, 8>),
|
||||
Jump(Jump),
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
|
@ -92,6 +103,31 @@ pub enum Waveform {
|
|||
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
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "quote")]
|
||||
impl quote::ToTokens for Track {
|
||||
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
||||
|
@ -286,6 +322,7 @@ impl quote::ToTokens for Pattern {
|
|||
} = self;
|
||||
|
||||
tokens.append_all(quote! {
|
||||
|
||||
agb_tracker::__private::agb_tracker_interop::Pattern {
|
||||
length: #length,
|
||||
start_position: #start_position,
|
||||
|
@ -361,6 +398,9 @@ impl quote::ToTokens for PatternEffect {
|
|||
let amount = amount.to_raw();
|
||||
quote! { Vibrato(#waveform, agb_tracker::__private::Num::from_raw(#amount), #speed) }
|
||||
}
|
||||
PatternEffect::Jump(jump) => {
|
||||
quote! { Jump(#jump) }
|
||||
}
|
||||
};
|
||||
|
||||
tokens.append_all(quote! {
|
||||
|
|
|
@ -68,7 +68,7 @@ extern crate alloc;
|
|||
mod lookups;
|
||||
mod mixer;
|
||||
|
||||
use agb_tracker_interop::{PatternEffect, Sample, Waveform};
|
||||
use agb_tracker_interop::{Jump, PatternEffect, Sample, Waveform};
|
||||
use alloc::vec::Vec;
|
||||
|
||||
pub use mixer::{Mixer, SoundChannel};
|
||||
|
@ -111,6 +111,7 @@ pub struct TrackerInner<'track, TChannelId> {
|
|||
|
||||
current_row: usize,
|
||||
current_pattern: usize,
|
||||
current_jump: Option<Jump>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
|
@ -204,6 +205,7 @@ impl<'track, TChannelId> TrackerInner<'track, TChannelId> {
|
|||
|
||||
current_pattern: 0,
|
||||
current_row: 0,
|
||||
current_jump: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -269,12 +271,14 @@ impl<'track, TChannelId> TrackerInner<'track, TChannelId> {
|
|||
self.tick,
|
||||
&mut self.global_settings,
|
||||
&mut self.envelopes[i],
|
||||
&mut self.current_jump,
|
||||
);
|
||||
channel.apply_effect(
|
||||
&pattern_slot.effect2,
|
||||
self.tick,
|
||||
&mut self.global_settings,
|
||||
&mut self.envelopes[i],
|
||||
&mut self.current_jump,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -373,16 +377,21 @@ impl<'track, TChannelId> TrackerInner<'track, TChannelId> {
|
|||
self.frame -= self.global_settings.frames_per_tick;
|
||||
|
||||
if self.tick >= self.global_settings.ticks_per_step {
|
||||
self.current_row += 1;
|
||||
if let Some(jump) = self.current_jump.take() {
|
||||
self.handle_jump(jump);
|
||||
} else {
|
||||
self.current_row += 1;
|
||||
|
||||
if self.current_row
|
||||
>= self.track.patterns[self.track.patterns_to_play[self.current_pattern]].length
|
||||
{
|
||||
self.current_pattern += 1;
|
||||
self.current_row = 0;
|
||||
if self.current_row
|
||||
>= self.track.patterns[self.track.patterns_to_play[self.current_pattern]]
|
||||
.length
|
||||
{
|
||||
self.current_pattern += 1;
|
||||
self.current_row = 0;
|
||||
|
||||
if self.current_pattern >= self.track.patterns_to_play.len() {
|
||||
self.current_pattern = self.track.repeat;
|
||||
if self.current_pattern >= self.track.patterns_to_play.len() {
|
||||
self.current_pattern = self.track.repeat;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -394,6 +403,32 @@ impl<'track, TChannelId> TrackerInner<'track, TChannelId> {
|
|||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_jump(&mut self, jump: Jump) {
|
||||
match jump {
|
||||
Jump::Position { pattern } => {
|
||||
self.current_pattern = pattern as usize;
|
||||
self.current_row = 0;
|
||||
}
|
||||
Jump::PatternBreak { row } => {
|
||||
self.current_pattern += 1;
|
||||
self.current_row = row as usize;
|
||||
}
|
||||
Jump::Combined { pattern, row } => {
|
||||
self.current_pattern = pattern as usize;
|
||||
self.current_row = row as usize;
|
||||
}
|
||||
};
|
||||
if self.current_pattern >= self.track.patterns_to_play.len() {
|
||||
self.current_pattern = self.track.repeat;
|
||||
}
|
||||
if self.current_row
|
||||
>= self.track.patterns[self.track.patterns_to_play[self.current_pattern]].length
|
||||
{
|
||||
// TODO: reconsider this default
|
||||
self.current_row = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TrackerChannel {
|
||||
|
@ -419,6 +454,7 @@ impl TrackerChannel {
|
|||
tick: u32,
|
||||
global_settings: &mut GlobalSettings,
|
||||
envelope_state: &mut Option<EnvelopeState>,
|
||||
current_jump: &mut Option<Jump>,
|
||||
) {
|
||||
match effect {
|
||||
PatternEffect::None => {}
|
||||
|
@ -547,6 +583,9 @@ impl TrackerChannel {
|
|||
self.vibrato.waveform = *waveform;
|
||||
self.vibrato.enable = true;
|
||||
}
|
||||
PatternEffect::Jump(jump) => {
|
||||
*current_jump = Some(jump.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use agb_fixnum::Num;
|
||||
use agb_tracker_interop::{PatternEffect, Waveform};
|
||||
use agb_tracker_interop::{Jump, PatternEffect, Waveform};
|
||||
|
||||
use xmrs::prelude::*;
|
||||
|
||||
|
@ -101,6 +101,9 @@ pub fn parse_module(module: &Module) -> agb_tracker_interop::Track {
|
|||
let mut note_and_sample = vec![None; module.get_num_channels()];
|
||||
|
||||
for row in pattern.iter() {
|
||||
// the combined jump for each row
|
||||
let mut jump = None;
|
||||
|
||||
for (i, slot) in row.iter().enumerate() {
|
||||
let channel_number = i % module.get_num_channels();
|
||||
|
||||
|
@ -319,6 +322,18 @@ pub fn parse_module(module: &Module) -> agb_tracker_interop::Track {
|
|||
)
|
||||
}
|
||||
}
|
||||
0xB => {
|
||||
let pattern_idx = slot.effect_parameter;
|
||||
|
||||
jump = Some((
|
||||
channel_number,
|
||||
Jump::Position {
|
||||
pattern: pattern_idx,
|
||||
},
|
||||
));
|
||||
|
||||
PatternEffect::None
|
||||
}
|
||||
0xC => {
|
||||
if let Some((_, sample)) = maybe_note_and_sample {
|
||||
PatternEffect::Volume(
|
||||
|
@ -328,6 +343,29 @@ pub fn parse_module(module: &Module) -> agb_tracker_interop::Track {
|
|||
PatternEffect::None
|
||||
}
|
||||
}
|
||||
0xD => {
|
||||
// NOTE: this field is generally interpreted as decimal.
|
||||
let first = slot.effect_parameter >> 4;
|
||||
let second = slot.effect_parameter & 0xF;
|
||||
let row_idx = first * 10 + second;
|
||||
|
||||
let pattern_break = Jump::PatternBreak { row: row_idx };
|
||||
|
||||
// if to the *right* of 0xD effect, make combined
|
||||
if let Some((idx, Jump::Position { pattern })) = jump {
|
||||
jump = Some((
|
||||
idx,
|
||||
Jump::Combined {
|
||||
pattern,
|
||||
row: row_idx,
|
||||
},
|
||||
))
|
||||
} else {
|
||||
jump = Some((channel_number, pattern_break));
|
||||
}
|
||||
|
||||
PatternEffect::None
|
||||
}
|
||||
0xE => match slot.effect_parameter >> 4 {
|
||||
0x1 => {
|
||||
let c4_speed: Num<u32, 12> =
|
||||
|
@ -433,6 +471,16 @@ pub fn parse_module(module: &Module) -> agb_tracker_interop::Track {
|
|||
});
|
||||
}
|
||||
}
|
||||
// At the last channel, evaluate the combined jump,
|
||||
// and place at the first jump effect channel index
|
||||
if let Some((jump_channel, jump)) = jump.take() {
|
||||
let jump_effect = PatternEffect::Jump(jump);
|
||||
let pattern_data_idx =
|
||||
pattern_data.len() - module.get_num_channels() + jump_channel;
|
||||
if let Some(data) = pattern_data.get_mut(pattern_data_idx) {
|
||||
data.effect2 = jump_effect;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
patterns.push(agb_tracker_interop::Pattern {
|
||||
|
|
Loading…
Reference in a new issue