WIP: better precision?

This commit is contained in:
2026-01-29 18:50:54 +01:00
parent 00a90f1c15
commit 89e4795e86
13 changed files with 477 additions and 224 deletions

View File

@@ -1,7 +1,7 @@
use arc_swap::ArcSwap;
use crossbeam_channel::{bounded, Receiver, Sender, TrySendError};
use std::collections::HashMap;
use std::sync::atomic::AtomicI64;
use std::sync::atomic::{AtomicI64, AtomicU64};
use std::sync::Arc;
use std::thread::{self, JoinHandle};
use std::time::Duration;
@@ -40,7 +40,7 @@ impl PatternChange {
}
pub enum AudioCommand {
Evaluate(String),
Evaluate { cmd: String, time: Option<f64> },
Hush,
Panic,
LoadSamples(Vec<doux::sample::SampleEntry>),
@@ -199,6 +199,12 @@ impl AudioState {
}
}
pub struct SequencerConfig {
pub audio_sample_pos: Arc<AtomicU64>,
pub sample_rate: Arc<std::sync::atomic::AtomicU32>,
pub lookahead_ms: Arc<std::sync::atomic::AtomicU32>,
}
#[allow(clippy::too_many_arguments)]
pub fn spawn_sequencer(
link: Arc<LinkState>,
@@ -209,6 +215,7 @@ pub fn spawn_sequencer(
quantum: f64,
live_keys: Arc<LiveKeyState>,
nudge_us: Arc<AtomicI64>,
config: SequencerConfig,
) -> (SequencerHandle, Receiver<AudioCommand>) {
let (cmd_tx, cmd_rx) = bounded::<SeqCommand>(64);
let (audio_tx, audio_rx) = bounded::<AudioCommand>(256);
@@ -233,6 +240,9 @@ pub fn spawn_sequencer(
shared_state_clone,
live_keys,
nudge_us,
config.audio_sample_pos,
config.sample_rate,
config.lookahead_ms,
);
})
.expect("Failed to spawn sequencer thread");
@@ -359,10 +369,18 @@ pub(crate) struct TickInput {
pub quantum: f64,
pub fill: bool,
pub nudge_secs: f64,
pub current_time_us: i64,
pub engine_time: f64,
pub lookahead_secs: f64,
}
pub struct TimestampedCommand {
pub cmd: String,
pub time: Option<f64>,
}
pub(crate) struct TickOutput {
pub audio_commands: Vec<String>,
pub audio_commands: Vec<TimestampedCommand>,
pub new_tempo: Option<f64>,
pub shared_state: SharedSequencerState,
}
@@ -422,7 +440,7 @@ pub(crate) struct SequencerState {
variables: Variables,
speed_overrides: HashMap<(usize, usize), f64>,
key_cache: KeyCache,
buf_audio_commands: Vec<String>,
buf_audio_commands: Vec<TimestampedCommand>,
}
impl SequencerState {
@@ -516,7 +534,17 @@ impl SequencerState {
let stopped = self.deactivate_pending(beat, prev_beat, input.quantum);
self.audio_state.pending_stops.retain(|p| !stopped.contains(&p.id));
let steps = self.execute_steps(beat, prev_beat, input.tempo, input.quantum, input.fill, input.nudge_secs);
let steps = self.execute_steps(
beat,
prev_beat,
input.tempo,
input.quantum,
input.fill,
input.nudge_secs,
input.current_time_us,
input.engine_time,
input.lookahead_secs,
);
let vars = self.read_variables(&steps.completed_iterations, &stopped, steps.any_step_fired);
self.apply_chain_transitions(vars.chain_transitions);
@@ -591,6 +619,7 @@ impl SequencerState {
stopped
}
#[allow(clippy::too_many_arguments)]
fn execute_steps(
&mut self,
beat: f64,
@@ -599,6 +628,9 @@ impl SequencerState {
quantum: f64,
fill: bool,
nudge_secs: f64,
_current_time_us: i64,
engine_time: f64,
lookahead_secs: f64,
) -> StepResult {
self.buf_audio_commands.clear();
let mut result = StepResult {
@@ -670,9 +702,19 @@ impl SequencerState {
(active.bank, active.pattern, source_idx),
std::mem::take(&mut trace),
);
let event_time = if lookahead_secs > 0.0 {
Some(engine_time + lookahead_secs)
} else {
None
};
for cmd in cmds {
self.event_count += 1;
self.buf_audio_commands.push(cmd);
self.buf_audio_commands.push(TimestampedCommand {
cmd,
time: event_time,
});
}
}
}
@@ -790,6 +832,9 @@ fn sequencer_loop(
shared_state: Arc<ArcSwap<SharedSequencerState>>,
live_keys: Arc<LiveKeyState>,
nudge_us: Arc<AtomicI64>,
audio_sample_pos: Arc<AtomicU64>,
sample_rate: Arc<std::sync::atomic::AtomicU32>,
lookahead_ms: Arc<std::sync::atomic::AtomicU32>,
) {
use std::sync::atomic::Ordering;
@@ -807,10 +852,15 @@ fn sequencer_loop(
}
let state = link.capture_app_state();
let time = link.clock_micros();
let beat = state.beat_at_time(time, quantum);
let current_time_us = link.clock_micros();
let beat = state.beat_at_time(current_time_us, quantum);
let tempo = state.tempo();
let sr = sample_rate.load(Ordering::Relaxed) as f64;
let audio_samples = audio_sample_pos.load(Ordering::Relaxed);
let engine_time = if sr > 0.0 { audio_samples as f64 / sr } else { 0.0 };
let lookahead_secs = lookahead_ms.load(Ordering::Relaxed) as f64 / 1000.0;
let input = TickInput {
commands,
playing: playing.load(Ordering::Relaxed),
@@ -819,15 +869,18 @@ fn sequencer_loop(
quantum,
fill: live_keys.fill(),
nudge_secs: nudge_us.load(Ordering::Relaxed) as f64 / 1_000_000.0,
current_time_us,
engine_time,
lookahead_secs,
};
let output = seq_state.tick(input);
for cmd in output.audio_commands {
match audio_tx.load().try_send(AudioCommand::Evaluate(cmd)) {
for tsc in output.audio_commands {
let cmd = AudioCommand::Evaluate { cmd: tsc.cmd, time: tsc.time };
match audio_tx.load().try_send(cmd) {
Ok(()) => {}
Err(TrySendError::Full(_) | TrySendError::Disconnected(_)) => {
// Lags one tick in shared state: build_shared_state() already ran
seq_state.dropped_events += 1;
}
}
@@ -886,6 +939,9 @@ mod tests {
quantum: 4.0,
fill: false,
nudge_secs: 0.0,
current_time_us: 0,
engine_time: 0.0,
lookahead_secs: 0.0,
}
}
@@ -898,6 +954,9 @@ mod tests {
quantum: 4.0,
fill: false,
nudge_secs: 0.0,
current_time_us: 0,
engine_time: 0.0,
lookahead_secs: 0.0,
}
}