WIP: consolidate sampling

This commit is contained in:
2026-01-30 00:04:25 +01:00
parent 89e4795e86
commit 7729868939
5 changed files with 158 additions and 59 deletions

View File

@@ -224,7 +224,7 @@ pub fn build_stream(
scope_buffer: Arc<ScopeBuffer>,
spectrum_buffer: Arc<SpectrumBuffer>,
metrics: Arc<EngineMetrics>,
initial_samples: Vec<doux::sample::SampleEntry>,
initial_samples: Vec<doux::sampling::SampleEntry>,
audio_sample_pos: Arc<AtomicU64>,
) -> Result<(Stream, f32, AnalysisHandle), String> {
let host = cpal::default_host();

View File

@@ -40,10 +40,13 @@ impl PatternChange {
}
pub enum AudioCommand {
Evaluate { cmd: String, time: Option<f64> },
Evaluate {
cmd: String,
time: Option<f64>,
},
Hush,
Panic,
LoadSamples(Vec<doux::sample::SampleEntry>),
LoadSamples(Vec<doux::sampling::SampleEntry>),
#[allow(dead_code)]
ResetEngine,
}
@@ -444,11 +447,7 @@ pub(crate) struct SequencerState {
}
impl SequencerState {
pub fn new(
variables: Variables,
dict: Dictionary,
rng: Rng,
) -> Self {
pub fn new(variables: Variables, dict: Dictionary, rng: Rng) -> Self {
let script_engine = ScriptEngine::new(Arc::clone(&variables), dict, rng);
Self {
audio_state: AudioState::new(),
@@ -529,10 +528,14 @@ impl SequencerState {
let prev_beat = self.audio_state.prev_beat;
let activated = self.activate_pending(beat, prev_beat, input.quantum);
self.audio_state.pending_starts.retain(|p| !activated.contains(&p.id));
self.audio_state
.pending_starts
.retain(|p| !activated.contains(&p.id));
let stopped = self.deactivate_pending(beat, prev_beat, input.quantum);
self.audio_state.pending_stops.retain(|p| !stopped.contains(&p.id));
self.audio_state
.pending_stops
.retain(|p| !stopped.contains(&p.id));
let steps = self.execute_steps(
beat,
@@ -582,7 +585,9 @@ impl SequencerState {
let start_step = match pending.sync_mode {
SyncMode::Reset => 0,
SyncMode::PhaseLock => {
if let Some(pat) = self.pattern_cache.get(pending.id.bank, pending.id.pattern) {
if let Some(pat) =
self.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 {
@@ -654,7 +659,8 @@ impl SequencerState {
continue;
};
let speed_mult = self.speed_overrides
let speed_mult = self
.speed_overrides
.get(&(active.bank, active.pattern))
.copied()
.unwrap_or_else(|| pattern.speed.multiplier());
@@ -777,14 +783,24 @@ impl SequencerState {
fn apply_chain_transitions(&mut self, transitions: Vec<(PatternId, PatternId)>) {
for (source, target) in transitions {
if !self.audio_state.pending_stops.iter().any(|p| p.id == source) {
if !self
.audio_state
.pending_stops
.iter()
.any(|p| p.id == source)
{
self.audio_state.pending_stops.push(PendingPattern {
id: source,
quantization: LaunchQuantization::Bar,
sync_mode: SyncMode::Reset,
});
}
if !self.audio_state.pending_starts.iter().any(|p| p.id == target) {
if !self
.audio_state
.pending_starts
.iter()
.any(|p| p.id == target)
{
let (quant, sync) = self
.pattern_cache
.get(target.bank, target.pattern)
@@ -858,7 +874,11 @@ fn sequencer_loop(
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 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 {
@@ -877,7 +897,10 @@ fn sequencer_loop(
let output = seq_state.tick(input);
for tsc in output.audio_commands {
let cmd = AudioCommand::Evaluate { cmd: tsc.cmd, time: tsc.time };
let cmd = AudioCommand::Evaluate {
cmd: tsc.cmd,
time: tsc.time,
};
match audio_tx.load().try_send(cmd) {
Ok(()) => {}
Err(TrySendError::Full(_) | TrySendError::Disconnected(_)) => {
@@ -1087,7 +1110,11 @@ mod tests {
));
assert!(output.shared_state.active_patterns.is_empty());
assert!(!state.audio_state.pending_starts.iter().any(|p| p.id == pid(0, 1)));
assert!(!state
.audio_state
.pending_starts
.iter()
.any(|p| p.id == pid(0, 1)));
}
#[test]
@@ -1128,22 +1155,40 @@ mod tests {
#[test]
fn test_quantization_boundaries() {
assert!(check_quantization_boundary(
LaunchQuantization::Immediate, 1.5, 1.0, 4.0
LaunchQuantization::Immediate,
1.5,
1.0,
4.0
));
assert!(check_quantization_boundary(
LaunchQuantization::Beat, 2.0, 1.9, 4.0
LaunchQuantization::Beat,
2.0,
1.9,
4.0
));
assert!(!check_quantization_boundary(
LaunchQuantization::Beat, 1.5, 1.2, 4.0
LaunchQuantization::Beat,
1.5,
1.2,
4.0
));
assert!(check_quantization_boundary(
LaunchQuantization::Bar, 4.0, 3.9, 4.0
LaunchQuantization::Bar,
4.0,
3.9,
4.0
));
assert!(!check_quantization_boundary(
LaunchQuantization::Bar, 3.5, 3.2, 4.0
LaunchQuantization::Bar,
3.5,
3.2,
4.0
));
assert!(!check_quantization_boundary(
LaunchQuantization::Immediate, 1.0, -1.0, 4.0
LaunchQuantization::Immediate,
1.0,
-1.0,
4.0
));
}
@@ -1153,14 +1198,17 @@ mod tests {
state.tick(tick_with(
vec![SeqCommand::PatternUpdate {
bank: 0, pattern: 0, data: simple_pattern(2),
bank: 0,
pattern: 0,
data: simple_pattern(2),
}],
0.0,
));
state.tick(tick_with(
vec![SeqCommand::PatternStart {
bank: 0, pattern: 0,
bank: 0,
pattern: 0,
quantization: LaunchQuantization::Immediate,
sync_mode: SyncMode::Reset,
}],
@@ -1190,13 +1238,18 @@ mod tests {
pat.speed = crate::model::PatternSpeed::DOUBLE;
state.tick(tick_with(
vec![SeqCommand::PatternUpdate { bank: 0, pattern: 0, data: pat }],
vec![SeqCommand::PatternUpdate {
bank: 0,
pattern: 0,
data: pat,
}],
0.0,
));
state.tick(tick_with(
vec![SeqCommand::PatternStart {
bank: 0, pattern: 0,
bank: 0,
pattern: 0,
quantization: LaunchQuantization::Immediate,
sync_mode: SyncMode::Reset,
}],
@@ -1214,7 +1267,9 @@ mod tests {
state.tick(tick_with(
vec![SeqCommand::PatternUpdate {
bank: 0, pattern: 0, data: simple_pattern(4),
bank: 0,
pattern: 0,
data: simple_pattern(4),
}],
0.0,
));
@@ -1223,12 +1278,14 @@ mod tests {
state.tick(tick_with(
vec![
SeqCommand::PatternStart {
bank: 0, pattern: 0,
bank: 0,
pattern: 0,
quantization: LaunchQuantization::Immediate,
sync_mode: SyncMode::Reset,
},
SeqCommand::PatternStop {
bank: 0, pattern: 0,
bank: 0,
pattern: 0,
quantization: LaunchQuantization::Immediate,
},
],
@@ -1246,13 +1303,18 @@ mod tests {
state.tick(tick_with(
vec![
SeqCommand::PatternUpdate {
bank: 0, pattern: 0, data: simple_pattern(1),
bank: 0,
pattern: 0,
data: simple_pattern(1),
},
SeqCommand::PatternUpdate {
bank: 0, pattern: 1, data: simple_pattern(4),
bank: 0,
pattern: 1,
data: simple_pattern(4),
},
SeqCommand::PatternStart {
bank: 0, pattern: 0,
bank: 0,
pattern: 0,
quantization: LaunchQuantization::Immediate,
sync_mode: SyncMode::Reset,
},
@@ -1279,14 +1341,19 @@ mod tests {
// Chain guard should block transition to pattern 1.
state.tick(tick_with(
vec![SeqCommand::PatternStop {
bank: 0, pattern: 0,
bank: 0,
pattern: 0,
quantization: LaunchQuantization::Immediate,
}],
1.0,
));
assert!(!state.audio_state.active_patterns.contains_key(&pid(0, 1)));
assert!(!state.audio_state.pending_starts.iter().any(|p| p.id == pid(0, 1)));
assert!(!state
.audio_state
.pending_starts
.iter()
.any(|p| p.id == pid(0, 1)));
}
#[test]
@@ -1296,18 +1363,24 @@ mod tests {
state.tick(tick_with(
vec![
SeqCommand::PatternUpdate {
bank: 0, pattern: 0, data: simple_pattern(4),
bank: 0,
pattern: 0,
data: simple_pattern(4),
},
SeqCommand::PatternUpdate {
bank: 0, pattern: 1, data: simple_pattern(4),
bank: 0,
pattern: 1,
data: simple_pattern(4),
},
SeqCommand::PatternStart {
bank: 0, pattern: 0,
bank: 0,
pattern: 0,
quantization: LaunchQuantization::Bar,
sync_mode: SyncMode::Reset,
},
SeqCommand::PatternStart {
bank: 0, pattern: 1,
bank: 0,
pattern: 1,
quantization: LaunchQuantization::Beat,
sync_mode: SyncMode::Reset,
},
@@ -1334,13 +1407,16 @@ mod tests {
state.tick(tick_with(
vec![SeqCommand::PatternUpdate {
bank: 0, pattern: 0, data: simple_pattern(4),
bank: 0,
pattern: 0,
data: simple_pattern(4),
}],
0.0,
));
state.tick(tick_with(
vec![SeqCommand::PatternStart {
bank: 0, pattern: 0,
bank: 0,
pattern: 0,
quantization: LaunchQuantization::Immediate,
sync_mode: SyncMode::Reset,
}],
@@ -1357,7 +1433,9 @@ mod tests {
// beat=1.25: beat_int=5, prev=4, step fires. step_index=3%2=1 fires, advances to (3+1)%2=0
state.tick(tick_with(
vec![SeqCommand::PatternUpdate {
bank: 0, pattern: 0, data: simple_pattern(2),
bank: 0,
pattern: 0,
data: simple_pattern(2),
}],
1.25,
));
@@ -1376,7 +1454,9 @@ mod tests {
state.tick(tick_with(
vec![SeqCommand::PatternUpdate {
bank: 0, pattern: 0, data: simple_pattern(4),
bank: 0,
pattern: 0,
data: simple_pattern(4),
}],
0.0,
));
@@ -1384,7 +1464,8 @@ mod tests {
// Start while paused: pending_starts gets cleared
state.tick(TickInput {
commands: vec![SeqCommand::PatternStart {
bank: 0, pattern: 0,
bank: 0,
pattern: 0,
quantization: LaunchQuantization::Immediate,
sync_mode: SyncMode::Reset,
}],
@@ -1404,13 +1485,16 @@ mod tests {
state.tick(tick_with(
vec![SeqCommand::PatternUpdate {
bank: 0, pattern: 0, data: simple_pattern(4),
bank: 0,
pattern: 0,
data: simple_pattern(4),
}],
0.0,
));
state.tick(tick_with(
vec![SeqCommand::PatternStart {
bank: 0, pattern: 0,
bank: 0,
pattern: 0,
quantization: LaunchQuantization::Immediate,
sync_mode: SyncMode::Reset,
}],
@@ -1434,15 +1518,19 @@ mod tests {
state.tick(tick_with(
vec![
SeqCommand::PatternUpdate {
bank: 0, pattern: 0, data: simple_pattern(4),
bank: 0,
pattern: 0,
data: simple_pattern(4),
},
SeqCommand::PatternStart {
bank: 0, pattern: 0,
bank: 0,
pattern: 0,
quantization: LaunchQuantization::Bar,
sync_mode: SyncMode::Reset,
},
SeqCommand::PatternStart {
bank: 0, pattern: 0,
bank: 0,
pattern: 0,
quantization: LaunchQuantization::Bar,
sync_mode: SyncMode::Reset,
},
@@ -1450,7 +1538,9 @@ mod tests {
0.0,
));
let pending_count = state.audio_state.pending_starts
let pending_count = state
.audio_state
.pending_starts
.iter()
.filter(|p| p.id == pid(0, 0))
.count();
@@ -1464,13 +1554,16 @@ mod tests {
// Pattern of length 16 — won't complete iteration for many ticks
state.tick(tick_with(
vec![SeqCommand::PatternUpdate {
bank: 0, pattern: 0, data: simple_pattern(16),
bank: 0,
pattern: 0,
data: simple_pattern(16),
}],
0.0,
));
state.tick(tick_with(
vec![SeqCommand::PatternStart {
bank: 0, pattern: 0,
bank: 0,
pattern: 0,
quantization: LaunchQuantization::Immediate,
sync_mode: SyncMode::Reset,
}],