Feat: add hidden mode and new documentation

This commit is contained in:
2026-02-26 12:31:56 +01:00
parent e1cf57918e
commit 70032acc75
95 changed files with 1055 additions and 286 deletions

View File

@@ -125,6 +125,11 @@ pub enum SeqCommand {
muted: std::collections::HashSet<(usize, usize)>,
soloed: std::collections::HashSet<(usize, usize)>,
},
ScriptUpdate {
script: String,
speed: crate::model::PatternSpeed,
length: usize,
},
StopAll,
ResetScriptState,
Shutdown,
@@ -166,6 +171,7 @@ pub struct SharedSequencerState {
pub event_count: usize,
pub tempo: f64,
pub beat: f64,
pub script_trace: Option<ExecutionTrace>,
}
pub struct SequencerSnapshot {
@@ -174,6 +180,7 @@ pub struct SequencerSnapshot {
pub event_count: usize,
pub tempo: f64,
pub beat: f64,
script_trace: Option<ExecutionTrace>,
}
impl From<&SharedSequencerState> for SequencerSnapshot {
@@ -184,6 +191,7 @@ impl From<&SharedSequencerState> for SequencerSnapshot {
event_count: s.event_count,
tempo: s.tempo,
beat: s.beat,
script_trace: s.script_trace.clone(),
}
}
}
@@ -197,6 +205,7 @@ impl SequencerSnapshot {
event_count: 0,
tempo: 0.0,
beat: 0.0,
script_trace: None,
}
}
@@ -236,6 +245,10 @@ impl SequencerSnapshot {
pub fn get_trace(&self, bank: usize, pattern: usize, step: usize) -> Option<&ExecutionTrace> {
self.step_traces.get(&(bank, pattern, step))
}
pub fn script_trace(&self) -> Option<&ExecutionTrace> {
self.script_trace.as_ref()
}
}
pub struct SequencerHandle {
@@ -555,6 +568,12 @@ pub struct SequencerState {
soloed: std::collections::HashSet<(usize, usize)>,
last_tempo: f64,
last_beat: f64,
script_text: String,
script_speed: crate::model::PatternSpeed,
script_length: usize,
script_frontier: f64,
script_step: usize,
script_trace: Option<ExecutionTrace>,
}
impl SequencerState {
@@ -586,6 +605,12 @@ impl SequencerState {
soloed: std::collections::HashSet::new(),
last_tempo: 0.0,
last_beat: 0.0,
script_text: String::new(),
script_speed: crate::model::PatternSpeed::default(),
script_length: 16,
script_frontier: -1.0,
script_step: 0,
script_trace: None,
}
}
@@ -670,6 +695,11 @@ impl SequencerState {
self.audio_state.flush_midi_notes = true;
}
}
SeqCommand::ScriptUpdate { script, speed, length } => {
self.script_text = script;
self.script_speed = speed;
self.script_length = length;
}
SeqCommand::StopAll => {
// Flush pending updates so cache stays current for future launches
for ((bank, pattern), snapshot) in self.pending_updates.drain() {
@@ -728,6 +758,20 @@ impl SequencerState {
input.mouse_down,
);
self.execute_periodic_script(
input.beat,
frontier,
lookahead_end,
input.tempo,
input.quantum,
input.fill,
input.nudge_secs,
input.engine_time,
input.mouse_x,
input.mouse_y,
input.mouse_down,
);
let new_tempo = self.read_tempo_variable(steps.any_step_fired);
self.apply_follow_ups();
@@ -754,6 +798,9 @@ impl SequencerState {
}
}
self.audio_state.prev_beat = -1.0;
self.script_frontier = -1.0;
self.script_step = 0;
self.script_trace = None;
self.buf_audio_commands.clear();
let flush = std::mem::take(&mut self.audio_state.flush_midi_notes);
TickOutput {
@@ -985,6 +1032,87 @@ impl SequencerState {
result
}
#[allow(clippy::too_many_arguments)]
fn execute_periodic_script(
&mut self,
beat: f64,
frontier: f64,
lookahead_end: f64,
tempo: f64,
quantum: f64,
fill: bool,
nudge_secs: f64,
engine_time: f64,
mouse_x: f64,
mouse_y: f64,
mouse_down: f64,
) {
if self.script_text.trim().is_empty() {
return;
}
let script_frontier = if self.script_frontier < 0.0 {
frontier.max(0.0)
} else {
self.script_frontier
};
let speed_mult = self.script_speed.multiplier();
let fire_beats = substeps_in_window(script_frontier, lookahead_end, speed_mult);
for step_beat in fire_beats {
let beat_delta = step_beat - beat;
let time_delta = if tempo > 0.0 {
(beat_delta / tempo) * 60.0
} else {
0.0
};
let event_time = Some(engine_time + time_delta);
let step_in_cycle = self.script_step % self.script_length;
if step_in_cycle == 0 {
let ctx = StepContext {
step: 0,
beat: step_beat,
bank: 0,
pattern: 0,
tempo,
phase: step_beat % quantum,
slot: 0,
runs: self.script_step / self.script_length,
iter: self.script_step / self.script_length,
speed: speed_mult,
fill,
nudge_secs,
cc_access: self.cc_access.as_deref(),
speed_key: "",
mouse_x,
mouse_y,
mouse_down,
};
let mut trace = ExecutionTrace::default();
if let Ok(cmds) =
self.script_engine
.evaluate_with_trace(&self.script_text, &ctx, &mut trace)
{
for cmd in cmds {
self.event_count += 1;
self.buf_audio_commands.push(TimestampedCommand {
cmd,
time: event_time,
});
}
}
self.script_trace = Some(trace);
}
self.script_step += 1;
}
self.script_frontier = lookahead_end;
}
fn read_tempo_variable(&self, any_step_fired: bool) -> Option<f64> {
if !any_step_fired {
return None;
@@ -1056,6 +1184,7 @@ impl SequencerState {
event_count: self.event_count,
tempo: self.last_tempo,
beat: self.last_beat,
script_trace: self.script_trace.clone(),
}
}
}