use alloc::{vec, vec::Vec};
use xmrs::prelude::*;
#[derive(Clone, Default)]
struct PatternLoopSlot {
origin: usize,
count: usize,
}
#[derive(Debug, Clone, Copy)]
pub enum TickOutcome {
Tick { tick: usize },
RowStart {
pattern: usize,
row: usize,
pattern_changed: bool,
},
SongEnd,
}
pub struct Sequencer<'a> {
module: &'a Module,
current_song: usize,
tempo: usize,
bpm: usize,
current_tick: usize,
extra_ticks: usize,
bpm_slide_delta: isize,
current_table_index: usize,
current_row: usize,
playing_row: usize,
playing_pattern: usize,
position_jump: bool,
pattern_break: bool,
jump_dest: usize,
jump_row: usize,
pattern_loop: Vec<PatternLoopSlot>,
row_loop_count: Vec<Vec<usize>>,
loop_count: usize,
max_loop_count: usize,
}
impl<'a> Sequencer<'a> {
pub(crate) fn new(module: &'a Module, song: usize) -> Self {
let current_song = if song < module.pattern_order.len() {
song
} else {
0
};
let num_channels = module.get_num_channels();
let song_len = module.get_song_length(current_song);
Self {
module,
current_song,
tempo: module.default_tempo,
bpm: module.default_bpm,
current_tick: 0,
extra_ticks: 0,
bpm_slide_delta: 0,
current_table_index: 0,
current_row: 0,
playing_row: 0,
playing_pattern: module.pattern_order[current_song]
.get(0)
.copied()
.unwrap_or(0),
position_jump: false,
pattern_break: false,
jump_dest: 0,
jump_row: 0,
pattern_loop: vec![PatternLoopSlot::default(); num_channels],
row_loop_count: vec![vec![0; MAX_NUM_ROWS]; song_len],
loop_count: 0,
max_loop_count: 0,
}
}
pub fn current_song(&self) -> usize {
self.current_song
}
pub fn tempo(&self) -> usize {
self.tempo
}
pub fn bpm(&self) -> usize {
self.bpm
}
pub fn current_tick(&self) -> usize {
self.current_tick
}
pub fn current_table_index(&self) -> usize {
self.current_table_index
}
pub fn current_row(&self) -> usize {
self.current_row
}
pub fn current_pattern(&self) -> usize {
self.module.pattern_order[self.current_song][self.current_table_index]
}
pub fn playing_row(&self) -> usize {
self.playing_row
}
pub fn playing_pattern(&self) -> usize {
self.playing_pattern
}
pub fn loop_count(&self) -> usize {
self.loop_count
}
pub fn max_loop_count(&self) -> usize {
self.max_loop_count
}
pub fn set_max_loop_count(&mut self, v: usize) {
self.max_loop_count = v;
}
pub fn num_channels(&self) -> usize {
self.pattern_loop.len()
}
pub fn ticks_per_second(&self) -> f32 {
self.bpm as f32 * 0.4
}
pub(crate) fn goto(&mut self, table_position: usize, row: usize, speed: usize) -> bool {
if table_position >= self.module.get_song_length(self.current_song) {
return false;
}
let num_row = self.module.pattern_order[self.current_song][table_position];
if row >= self.module.get_num_rows(num_row) {
return false;
}
self.jump_dest = table_position;
self.jump_row = row;
self.position_jump = true;
self.tempo = if speed == 0 {
self.module.default_tempo
} else {
speed
};
self.bpm = self.module.default_bpm;
self.bpm_slide_delta = 0;
self.current_tick = 0;
true
}
pub(crate) fn advance_tick(&mut self) -> TickOutcome {
if self.is_exhausted() {
return TickOutcome::SongEnd;
}
if self.current_tick == 0 {
let outcome = self.row_start_tick();
if self.is_exhausted() {
return TickOutcome::SongEnd;
}
self.advance_tick_counter();
outcome
} else {
let tick_processed = self.current_tick;
if self.bpm_slide_delta != 0 {
let new_bpm = (self.bpm as isize + self.bpm_slide_delta).clamp(32, 255) as usize;
self.bpm = new_bpm;
}
self.advance_tick_counter();
TickOutcome::Tick {
tick: tick_processed,
}
}
}
fn is_exhausted(&self) -> bool {
self.max_loop_count > 0 && self.loop_count >= self.max_loop_count
}
fn row_start_tick(&mut self) -> TickOutcome {
let mut pattern_changed = false;
if self.position_jump {
self.current_table_index = self.jump_dest;
self.current_row = self.jump_row;
self.position_jump = false;
self.pattern_break = false;
self.jump_row = 0;
self.post_pattern_change();
pattern_changed = true;
} else if self.pattern_break {
self.current_table_index += 1;
self.current_row = self.jump_row;
self.pattern_break = false;
self.jump_row = 0;
self.post_pattern_change();
pattern_changed = true;
}
let pat_idx = {
let raw = self.module.pattern_order[self.current_song][self.current_table_index];
if raw < self.module.pattern.len() {
raw
} else {
self.current_table_index = 0;
self.module.pattern_order[self.current_song][self.current_table_index]
}
};
let current_row = self.current_row;
let in_a_loop = self.apply_row_navigation_effects(pat_idx, current_row);
if !in_a_loop {
self.loop_count = self.row_loop_count[self.current_table_index][current_row];
self.row_loop_count[self.current_table_index][current_row] += 1;
}
self.playing_pattern = pat_idx;
self.playing_row = current_row;
self.current_row += 1;
let pattern_len = self.module.pattern[pat_idx].len();
if !self.position_jump && !self.pattern_break && self.current_row >= pattern_len {
self.current_table_index += 1;
self.current_row = self.jump_row;
self.jump_row = 0;
self.post_pattern_change();
pattern_changed = true;
}
TickOutcome::RowStart {
pattern: pat_idx,
row: current_row,
pattern_changed,
}
}
fn apply_row_navigation_effects(&mut self, pat_idx: usize, row: usize) -> bool {
let num_channels = self.pattern_loop.len();
let mut in_a_loop = false;
self.bpm_slide_delta = 0;
for ch_index in 0..num_channels {
let cell = match self.module.pattern[pat_idx][row].get(ch_index) {
Some(c) => c,
None => continue,
};
for gfx in cell.global_effects.clone() {
match gfx {
GlobalEffect::Bpm(bpm) => {
self.bpm = bpm;
}
GlobalEffect::BpmSlide(delta) => {
self.bpm_slide_delta = delta;
}
GlobalEffect::MidiMacro(_m) => {
}
GlobalEffect::PatternBreak(position) => {
self.pattern_break = true;
self.jump_row = position;
}
GlobalEffect::PatternDelay {
quantity: q,
tempo: t,
} => self.extra_ticks = if t { q * self.tempo } else { q },
GlobalEffect::PatternLoop(value) => {
let slot = &mut self.pattern_loop[ch_index];
if value != 0 {
if value == slot.count {
slot.count = 0;
if self.module.profile.quirks.pattern_loop_resumes {
slot.origin = self.current_row + 1;
}
} else {
slot.count += 1;
self.position_jump = true;
self.jump_row = slot.origin;
self.jump_dest = self.current_table_index;
}
} else {
slot.origin = self.current_row;
if self.module.profile.quirks.e60_leaks_to_next_pattern {
self.jump_row = slot.origin;
}
}
}
GlobalEffect::PositionJump(position) => {
if position < self.module.pattern_order[self.current_song].len() {
self.position_jump = true;
self.jump_dest = position;
self.jump_row = 0;
}
}
GlobalEffect::Speed(speed) => {
self.tempo = speed;
}
GlobalEffect::Volume(_) | GlobalEffect::VolumeSlide { .. } => {
}
}
}
if self.pattern_loop[ch_index].count > 0 {
in_a_loop = true;
}
}
in_a_loop
}
fn post_pattern_change(&mut self) {
if self.current_table_index >= self.module.pattern_order[self.current_song].len() {
self.current_table_index = self.module.restart_position;
}
}
fn advance_tick_counter(&mut self) {
self.current_tick += 1;
if self.tempo != 0 && self.current_tick >= self.tempo + self.extra_ticks {
self.current_tick = 0;
self.extra_ticks = 0;
}
}
}