So much better
This commit is contained in:
@@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user