Update tracker to support PatternBreak and PositionJump

This commit is contained in:
wysiwys 2024-08-02 21:23:36 -04:00
parent 4a3792b248
commit da92ec3b8b
3 changed files with 137 additions and 10 deletions

View file

@ -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! {

View file

@ -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());
}
}
}

View file

@ -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 {