WIP: consolidate sampling
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
}],
|
||||
|
||||
Reference in New Issue
Block a user