So much better

This commit is contained in:
2026-01-26 02:24:04 +01:00
parent bde64e7dc5
commit 1b32a91b0d
16 changed files with 714 additions and 135 deletions

View File

@@ -7,7 +7,7 @@ use std::time::Duration;
use thread_priority::{set_current_thread_priority, ThreadPriority};
use super::LinkState;
use crate::model::{MAX_BANKS, MAX_PATTERNS};
use crate::model::{LaunchQuantization, SyncMode, MAX_BANKS, MAX_PATTERNS};
use crate::model::{Dictionary, ExecutionTrace, Rng, ScriptEngine, StepContext, Value, Variables};
use crate::state::LiveKeyState;
@@ -56,10 +56,13 @@ pub enum SeqCommand {
PatternStart {
bank: usize,
pattern: usize,
quantization: LaunchQuantization,
sync_mode: SyncMode,
},
PatternStop {
bank: usize,
pattern: usize,
quantization: LaunchQuantization,
},
Shutdown,
}
@@ -69,6 +72,8 @@ pub struct PatternSnapshot {
pub speed: crate::model::PatternSpeed,
pub length: usize,
pub steps: Vec<StepSnapshot>,
pub quantization: LaunchQuantization,
pub sync_mode: SyncMode,
}
#[derive(Clone)]
@@ -165,11 +170,18 @@ struct ActivePattern {
iter: usize,
}
#[derive(Clone, Copy)]
struct PendingPattern {
id: PatternId,
quantization: LaunchQuantization,
sync_mode: SyncMode,
}
struct AudioState {
prev_beat: f64,
active_patterns: HashMap<PatternId, ActivePattern>,
pending_starts: Vec<PatternId>,
pending_stops: Vec<PatternId>,
pending_starts: Vec<PendingPattern>,
pending_stops: Vec<PendingPattern>,
}
impl AudioState {
@@ -275,6 +287,41 @@ impl PatternSnapshot {
}
}
fn check_quantization_boundary(
quantization: LaunchQuantization,
beat: f64,
prev_beat: f64,
quantum: f64,
) -> bool {
if prev_beat < 0.0 {
return false;
}
match quantization {
LaunchQuantization::Immediate => true,
LaunchQuantization::Beat => beat.floor() as i64 != prev_beat.floor() as i64,
LaunchQuantization::Bar => {
let bar = (beat / quantum).floor() as i64;
let prev_bar = (prev_beat / quantum).floor() as i64;
bar != prev_bar
}
LaunchQuantization::Bars2 => {
let bars2 = (beat / (quantum * 2.0)).floor() as i64;
let prev_bars2 = (prev_beat / (quantum * 2.0)).floor() as i64;
bars2 != prev_bars2
}
LaunchQuantization::Bars4 => {
let bars4 = (beat / (quantum * 4.0)).floor() as i64;
let prev_bars4 = (prev_beat / (quantum * 4.0)).floor() as i64;
bars4 != prev_bars4
}
LaunchQuantization::Bars8 => {
let bars8 = (beat / (quantum * 8.0)).floor() as i64;
let prev_bars8 = (prev_beat / (quantum * 8.0)).floor() as i64;
bars8 != prev_bars8
}
}
}
type StepKey = (usize, usize, usize);
struct RunsCounter {
@@ -332,18 +379,35 @@ fn sequencer_loop(
} => {
pattern_cache.set(bank, pattern, data);
}
SeqCommand::PatternStart { bank, pattern } => {
SeqCommand::PatternStart {
bank,
pattern,
quantization,
sync_mode,
} => {
let id = PatternId { bank, pattern };
audio_state.pending_stops.retain(|p| *p != id);
if !audio_state.pending_starts.contains(&id) {
audio_state.pending_starts.push(id);
audio_state.pending_stops.retain(|p| p.id != id);
if !audio_state.pending_starts.iter().any(|p| p.id == id) {
audio_state.pending_starts.push(PendingPattern {
id,
quantization,
sync_mode,
});
}
}
SeqCommand::PatternStop { bank, pattern } => {
SeqCommand::PatternStop {
bank,
pattern,
quantization,
} => {
let id = PatternId { bank, pattern };
audio_state.pending_starts.retain(|p| *p != id);
if !audio_state.pending_stops.contains(&id) {
audio_state.pending_stops.push(id);
audio_state.pending_starts.retain(|p| p.id != id);
if !audio_state.pending_stops.iter().any(|p| p.id == id) {
audio_state.pending_stops.push(PendingPattern {
id,
quantization,
sync_mode: SyncMode::Reset,
});
}
}
SeqCommand::Shutdown => {
@@ -362,31 +426,67 @@ fn sequencer_loop(
let beat = state.beat_at_time(time, quantum);
let tempo = state.tempo();
let bar = (beat / quantum).floor() as i64;
let prev_bar = (audio_state.prev_beat / quantum).floor() as i64;
let prev_beat = audio_state.prev_beat;
let mut stopped_chain_keys: Vec<String> = Vec::new();
if bar != prev_bar && audio_state.prev_beat >= 0.0 {
for id in audio_state.pending_starts.drain(..) {
// Process pending starts with per-pattern quantization
let mut started_ids: Vec<PatternId> = Vec::new();
for pending in &audio_state.pending_starts {
let should_start = check_quantization_boundary(
pending.quantization,
beat,
prev_beat,
quantum,
);
if should_start {
let start_step = match pending.sync_mode {
SyncMode::Reset => 0,
SyncMode::PhaseLock => {
if let Some(pat) = pattern_cache.get(pending.id.bank, pending.id.pattern) {
let speed_mult = pat.speed.multiplier();
((beat * 4.0 * speed_mult) as usize) % pat.length
} else {
0
}
}
};
audio_state.active_patterns.insert(
id,
pending.id,
ActivePattern {
bank: id.bank,
pattern: id.pattern,
step_index: 0,
bank: pending.id.bank,
pattern: pending.id.pattern,
step_index: start_step,
iter: 0,
},
);
}
for id in audio_state.pending_stops.drain(..) {
audio_state.active_patterns.remove(&id);
step_traces.retain(|&(bank, pattern, _), _| {
bank != id.bank || pattern != id.pattern
});
stopped_chain_keys.push(format!("__chain_{}_{}__", id.bank, id.pattern));
started_ids.push(pending.id);
}
}
audio_state.pending_starts.retain(|p| !started_ids.contains(&p.id));
// Process pending stops with per-pattern quantization
let mut stopped_ids: Vec<PatternId> = Vec::new();
for pending in &audio_state.pending_stops {
let should_stop = check_quantization_boundary(
pending.quantization,
beat,
prev_beat,
quantum,
);
if should_stop {
audio_state.active_patterns.remove(&pending.id);
step_traces.retain(|&(bank, pattern, _), _| {
bank != pending.id.bank || pattern != pending.id.pattern
});
stopped_chain_keys.push(format!(
"__chain_{}_{}__",
pending.id.bank, pending.id.pattern
));
stopped_ids.push(pending.id);
}
}
audio_state.pending_stops.retain(|p| !stopped_ids.contains(&p.id));
let prev_beat = audio_state.prev_beat;
let mut chain_transitions: Vec<(PatternId, PatternId)> = Vec::new();
let mut chain_keys_to_remove: Vec<String> = Vec::new();
let mut new_tempo: Option<f64> = None;
@@ -515,13 +615,25 @@ fn sequencer_loop(
link.set_tempo(t);
}
// Apply chain transitions
// Apply chain transitions (use Bar quantization for chains)
for (source, target) in chain_transitions {
if !audio_state.pending_stops.contains(&source) {
audio_state.pending_stops.push(source);
if !audio_state.pending_stops.iter().any(|p| p.id == source) {
audio_state.pending_stops.push(PendingPattern {
id: source,
quantization: LaunchQuantization::Bar,
sync_mode: SyncMode::Reset,
});
}
if !audio_state.pending_starts.contains(&target) {
audio_state.pending_starts.push(target);
if !audio_state.pending_starts.iter().any(|p| p.id == target) {
let (quant, sync) = pattern_cache
.get(target.bank, target.pattern)
.map(|p| (p.quantization, p.sync_mode))
.unwrap_or((LaunchQuantization::Bar, SyncMode::Reset));
audio_state.pending_starts.push(PendingPattern {
id: target,
quantization: quant,
sync_mode: sync,
});
}
}