WIP: consolidate sampling
This commit is contained in:
@@ -18,7 +18,7 @@ path = "src/main.rs"
|
|||||||
cagire-forth = { path = "crates/forth" }
|
cagire-forth = { path = "crates/forth" }
|
||||||
cagire-project = { path = "crates/project" }
|
cagire-project = { path = "crates/project" }
|
||||||
cagire-ratatui = { path = "crates/ratatui" }
|
cagire-ratatui = { path = "crates/ratatui" }
|
||||||
doux = { git = "https://github.com/sova-org/doux", features = ["native"] }
|
doux = { path = "/Users/bubo/doux", features = ["native"] }
|
||||||
rusty_link = "0.4"
|
rusty_link = "0.4"
|
||||||
ratatui = "0.29"
|
ratatui = "0.29"
|
||||||
crossterm = "0.28"
|
crossterm = "0.28"
|
||||||
|
|||||||
@@ -224,7 +224,7 @@ pub fn build_stream(
|
|||||||
scope_buffer: Arc<ScopeBuffer>,
|
scope_buffer: Arc<ScopeBuffer>,
|
||||||
spectrum_buffer: Arc<SpectrumBuffer>,
|
spectrum_buffer: Arc<SpectrumBuffer>,
|
||||||
metrics: Arc<EngineMetrics>,
|
metrics: Arc<EngineMetrics>,
|
||||||
initial_samples: Vec<doux::sample::SampleEntry>,
|
initial_samples: Vec<doux::sampling::SampleEntry>,
|
||||||
audio_sample_pos: Arc<AtomicU64>,
|
audio_sample_pos: Arc<AtomicU64>,
|
||||||
) -> Result<(Stream, f32, AnalysisHandle), String> {
|
) -> Result<(Stream, f32, AnalysisHandle), String> {
|
||||||
let host = cpal::default_host();
|
let host = cpal::default_host();
|
||||||
|
|||||||
@@ -40,10 +40,13 @@ impl PatternChange {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub enum AudioCommand {
|
pub enum AudioCommand {
|
||||||
Evaluate { cmd: String, time: Option<f64> },
|
Evaluate {
|
||||||
|
cmd: String,
|
||||||
|
time: Option<f64>,
|
||||||
|
},
|
||||||
Hush,
|
Hush,
|
||||||
Panic,
|
Panic,
|
||||||
LoadSamples(Vec<doux::sample::SampleEntry>),
|
LoadSamples(Vec<doux::sampling::SampleEntry>),
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
ResetEngine,
|
ResetEngine,
|
||||||
}
|
}
|
||||||
@@ -444,11 +447,7 @@ pub(crate) struct SequencerState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl SequencerState {
|
impl SequencerState {
|
||||||
pub fn new(
|
pub fn new(variables: Variables, dict: Dictionary, rng: Rng) -> Self {
|
||||||
variables: Variables,
|
|
||||||
dict: Dictionary,
|
|
||||||
rng: Rng,
|
|
||||||
) -> Self {
|
|
||||||
let script_engine = ScriptEngine::new(Arc::clone(&variables), dict, rng);
|
let script_engine = ScriptEngine::new(Arc::clone(&variables), dict, rng);
|
||||||
Self {
|
Self {
|
||||||
audio_state: AudioState::new(),
|
audio_state: AudioState::new(),
|
||||||
@@ -529,10 +528,14 @@ impl SequencerState {
|
|||||||
let prev_beat = self.audio_state.prev_beat;
|
let prev_beat = self.audio_state.prev_beat;
|
||||||
|
|
||||||
let activated = self.activate_pending(beat, prev_beat, input.quantum);
|
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);
|
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(
|
let steps = self.execute_steps(
|
||||||
beat,
|
beat,
|
||||||
@@ -582,7 +585,9 @@ impl SequencerState {
|
|||||||
let start_step = match pending.sync_mode {
|
let start_step = match pending.sync_mode {
|
||||||
SyncMode::Reset => 0,
|
SyncMode::Reset => 0,
|
||||||
SyncMode::PhaseLock => {
|
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();
|
let speed_mult = pat.speed.multiplier();
|
||||||
((beat * 4.0 * speed_mult) as usize) % pat.length
|
((beat * 4.0 * speed_mult) as usize) % pat.length
|
||||||
} else {
|
} else {
|
||||||
@@ -654,7 +659,8 @@ impl SequencerState {
|
|||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
let speed_mult = self.speed_overrides
|
let speed_mult = self
|
||||||
|
.speed_overrides
|
||||||
.get(&(active.bank, active.pattern))
|
.get(&(active.bank, active.pattern))
|
||||||
.copied()
|
.copied()
|
||||||
.unwrap_or_else(|| pattern.speed.multiplier());
|
.unwrap_or_else(|| pattern.speed.multiplier());
|
||||||
@@ -777,14 +783,24 @@ impl SequencerState {
|
|||||||
|
|
||||||
fn apply_chain_transitions(&mut self, transitions: Vec<(PatternId, PatternId)>) {
|
fn apply_chain_transitions(&mut self, transitions: Vec<(PatternId, PatternId)>) {
|
||||||
for (source, target) in transitions {
|
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 {
|
self.audio_state.pending_stops.push(PendingPattern {
|
||||||
id: source,
|
id: source,
|
||||||
quantization: LaunchQuantization::Bar,
|
quantization: LaunchQuantization::Bar,
|
||||||
sync_mode: SyncMode::Reset,
|
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
|
let (quant, sync) = self
|
||||||
.pattern_cache
|
.pattern_cache
|
||||||
.get(target.bank, target.pattern)
|
.get(target.bank, target.pattern)
|
||||||
@@ -858,7 +874,11 @@ fn sequencer_loop(
|
|||||||
|
|
||||||
let sr = sample_rate.load(Ordering::Relaxed) as f64;
|
let sr = sample_rate.load(Ordering::Relaxed) as f64;
|
||||||
let audio_samples = audio_sample_pos.load(Ordering::Relaxed);
|
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 lookahead_secs = lookahead_ms.load(Ordering::Relaxed) as f64 / 1000.0;
|
||||||
|
|
||||||
let input = TickInput {
|
let input = TickInput {
|
||||||
@@ -877,7 +897,10 @@ fn sequencer_loop(
|
|||||||
let output = seq_state.tick(input);
|
let output = seq_state.tick(input);
|
||||||
|
|
||||||
for tsc in output.audio_commands {
|
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) {
|
match audio_tx.load().try_send(cmd) {
|
||||||
Ok(()) => {}
|
Ok(()) => {}
|
||||||
Err(TrySendError::Full(_) | TrySendError::Disconnected(_)) => {
|
Err(TrySendError::Full(_) | TrySendError::Disconnected(_)) => {
|
||||||
@@ -1087,7 +1110,11 @@ mod tests {
|
|||||||
));
|
));
|
||||||
|
|
||||||
assert!(output.shared_state.active_patterns.is_empty());
|
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]
|
#[test]
|
||||||
@@ -1128,22 +1155,40 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_quantization_boundaries() {
|
fn test_quantization_boundaries() {
|
||||||
assert!(check_quantization_boundary(
|
assert!(check_quantization_boundary(
|
||||||
LaunchQuantization::Immediate, 1.5, 1.0, 4.0
|
LaunchQuantization::Immediate,
|
||||||
|
1.5,
|
||||||
|
1.0,
|
||||||
|
4.0
|
||||||
));
|
));
|
||||||
assert!(check_quantization_boundary(
|
assert!(check_quantization_boundary(
|
||||||
LaunchQuantization::Beat, 2.0, 1.9, 4.0
|
LaunchQuantization::Beat,
|
||||||
|
2.0,
|
||||||
|
1.9,
|
||||||
|
4.0
|
||||||
));
|
));
|
||||||
assert!(!check_quantization_boundary(
|
assert!(!check_quantization_boundary(
|
||||||
LaunchQuantization::Beat, 1.5, 1.2, 4.0
|
LaunchQuantization::Beat,
|
||||||
|
1.5,
|
||||||
|
1.2,
|
||||||
|
4.0
|
||||||
));
|
));
|
||||||
assert!(check_quantization_boundary(
|
assert!(check_quantization_boundary(
|
||||||
LaunchQuantization::Bar, 4.0, 3.9, 4.0
|
LaunchQuantization::Bar,
|
||||||
|
4.0,
|
||||||
|
3.9,
|
||||||
|
4.0
|
||||||
));
|
));
|
||||||
assert!(!check_quantization_boundary(
|
assert!(!check_quantization_boundary(
|
||||||
LaunchQuantization::Bar, 3.5, 3.2, 4.0
|
LaunchQuantization::Bar,
|
||||||
|
3.5,
|
||||||
|
3.2,
|
||||||
|
4.0
|
||||||
));
|
));
|
||||||
assert!(!check_quantization_boundary(
|
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(
|
state.tick(tick_with(
|
||||||
vec![SeqCommand::PatternUpdate {
|
vec![SeqCommand::PatternUpdate {
|
||||||
bank: 0, pattern: 0, data: simple_pattern(2),
|
bank: 0,
|
||||||
|
pattern: 0,
|
||||||
|
data: simple_pattern(2),
|
||||||
}],
|
}],
|
||||||
0.0,
|
0.0,
|
||||||
));
|
));
|
||||||
|
|
||||||
state.tick(tick_with(
|
state.tick(tick_with(
|
||||||
vec![SeqCommand::PatternStart {
|
vec![SeqCommand::PatternStart {
|
||||||
bank: 0, pattern: 0,
|
bank: 0,
|
||||||
|
pattern: 0,
|
||||||
quantization: LaunchQuantization::Immediate,
|
quantization: LaunchQuantization::Immediate,
|
||||||
sync_mode: SyncMode::Reset,
|
sync_mode: SyncMode::Reset,
|
||||||
}],
|
}],
|
||||||
@@ -1190,13 +1238,18 @@ mod tests {
|
|||||||
pat.speed = crate::model::PatternSpeed::DOUBLE;
|
pat.speed = crate::model::PatternSpeed::DOUBLE;
|
||||||
|
|
||||||
state.tick(tick_with(
|
state.tick(tick_with(
|
||||||
vec![SeqCommand::PatternUpdate { bank: 0, pattern: 0, data: pat }],
|
vec![SeqCommand::PatternUpdate {
|
||||||
|
bank: 0,
|
||||||
|
pattern: 0,
|
||||||
|
data: pat,
|
||||||
|
}],
|
||||||
0.0,
|
0.0,
|
||||||
));
|
));
|
||||||
|
|
||||||
state.tick(tick_with(
|
state.tick(tick_with(
|
||||||
vec![SeqCommand::PatternStart {
|
vec![SeqCommand::PatternStart {
|
||||||
bank: 0, pattern: 0,
|
bank: 0,
|
||||||
|
pattern: 0,
|
||||||
quantization: LaunchQuantization::Immediate,
|
quantization: LaunchQuantization::Immediate,
|
||||||
sync_mode: SyncMode::Reset,
|
sync_mode: SyncMode::Reset,
|
||||||
}],
|
}],
|
||||||
@@ -1214,7 +1267,9 @@ mod tests {
|
|||||||
|
|
||||||
state.tick(tick_with(
|
state.tick(tick_with(
|
||||||
vec![SeqCommand::PatternUpdate {
|
vec![SeqCommand::PatternUpdate {
|
||||||
bank: 0, pattern: 0, data: simple_pattern(4),
|
bank: 0,
|
||||||
|
pattern: 0,
|
||||||
|
data: simple_pattern(4),
|
||||||
}],
|
}],
|
||||||
0.0,
|
0.0,
|
||||||
));
|
));
|
||||||
@@ -1223,12 +1278,14 @@ mod tests {
|
|||||||
state.tick(tick_with(
|
state.tick(tick_with(
|
||||||
vec![
|
vec![
|
||||||
SeqCommand::PatternStart {
|
SeqCommand::PatternStart {
|
||||||
bank: 0, pattern: 0,
|
bank: 0,
|
||||||
|
pattern: 0,
|
||||||
quantization: LaunchQuantization::Immediate,
|
quantization: LaunchQuantization::Immediate,
|
||||||
sync_mode: SyncMode::Reset,
|
sync_mode: SyncMode::Reset,
|
||||||
},
|
},
|
||||||
SeqCommand::PatternStop {
|
SeqCommand::PatternStop {
|
||||||
bank: 0, pattern: 0,
|
bank: 0,
|
||||||
|
pattern: 0,
|
||||||
quantization: LaunchQuantization::Immediate,
|
quantization: LaunchQuantization::Immediate,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -1246,13 +1303,18 @@ mod tests {
|
|||||||
state.tick(tick_with(
|
state.tick(tick_with(
|
||||||
vec![
|
vec![
|
||||||
SeqCommand::PatternUpdate {
|
SeqCommand::PatternUpdate {
|
||||||
bank: 0, pattern: 0, data: simple_pattern(1),
|
bank: 0,
|
||||||
|
pattern: 0,
|
||||||
|
data: simple_pattern(1),
|
||||||
},
|
},
|
||||||
SeqCommand::PatternUpdate {
|
SeqCommand::PatternUpdate {
|
||||||
bank: 0, pattern: 1, data: simple_pattern(4),
|
bank: 0,
|
||||||
|
pattern: 1,
|
||||||
|
data: simple_pattern(4),
|
||||||
},
|
},
|
||||||
SeqCommand::PatternStart {
|
SeqCommand::PatternStart {
|
||||||
bank: 0, pattern: 0,
|
bank: 0,
|
||||||
|
pattern: 0,
|
||||||
quantization: LaunchQuantization::Immediate,
|
quantization: LaunchQuantization::Immediate,
|
||||||
sync_mode: SyncMode::Reset,
|
sync_mode: SyncMode::Reset,
|
||||||
},
|
},
|
||||||
@@ -1279,14 +1341,19 @@ mod tests {
|
|||||||
// Chain guard should block transition to pattern 1.
|
// Chain guard should block transition to pattern 1.
|
||||||
state.tick(tick_with(
|
state.tick(tick_with(
|
||||||
vec![SeqCommand::PatternStop {
|
vec![SeqCommand::PatternStop {
|
||||||
bank: 0, pattern: 0,
|
bank: 0,
|
||||||
|
pattern: 0,
|
||||||
quantization: LaunchQuantization::Immediate,
|
quantization: LaunchQuantization::Immediate,
|
||||||
}],
|
}],
|
||||||
1.0,
|
1.0,
|
||||||
));
|
));
|
||||||
|
|
||||||
assert!(!state.audio_state.active_patterns.contains_key(&pid(0, 1)));
|
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]
|
#[test]
|
||||||
@@ -1296,18 +1363,24 @@ mod tests {
|
|||||||
state.tick(tick_with(
|
state.tick(tick_with(
|
||||||
vec![
|
vec![
|
||||||
SeqCommand::PatternUpdate {
|
SeqCommand::PatternUpdate {
|
||||||
bank: 0, pattern: 0, data: simple_pattern(4),
|
bank: 0,
|
||||||
|
pattern: 0,
|
||||||
|
data: simple_pattern(4),
|
||||||
},
|
},
|
||||||
SeqCommand::PatternUpdate {
|
SeqCommand::PatternUpdate {
|
||||||
bank: 0, pattern: 1, data: simple_pattern(4),
|
bank: 0,
|
||||||
|
pattern: 1,
|
||||||
|
data: simple_pattern(4),
|
||||||
},
|
},
|
||||||
SeqCommand::PatternStart {
|
SeqCommand::PatternStart {
|
||||||
bank: 0, pattern: 0,
|
bank: 0,
|
||||||
|
pattern: 0,
|
||||||
quantization: LaunchQuantization::Bar,
|
quantization: LaunchQuantization::Bar,
|
||||||
sync_mode: SyncMode::Reset,
|
sync_mode: SyncMode::Reset,
|
||||||
},
|
},
|
||||||
SeqCommand::PatternStart {
|
SeqCommand::PatternStart {
|
||||||
bank: 0, pattern: 1,
|
bank: 0,
|
||||||
|
pattern: 1,
|
||||||
quantization: LaunchQuantization::Beat,
|
quantization: LaunchQuantization::Beat,
|
||||||
sync_mode: SyncMode::Reset,
|
sync_mode: SyncMode::Reset,
|
||||||
},
|
},
|
||||||
@@ -1334,13 +1407,16 @@ mod tests {
|
|||||||
|
|
||||||
state.tick(tick_with(
|
state.tick(tick_with(
|
||||||
vec![SeqCommand::PatternUpdate {
|
vec![SeqCommand::PatternUpdate {
|
||||||
bank: 0, pattern: 0, data: simple_pattern(4),
|
bank: 0,
|
||||||
|
pattern: 0,
|
||||||
|
data: simple_pattern(4),
|
||||||
}],
|
}],
|
||||||
0.0,
|
0.0,
|
||||||
));
|
));
|
||||||
state.tick(tick_with(
|
state.tick(tick_with(
|
||||||
vec![SeqCommand::PatternStart {
|
vec![SeqCommand::PatternStart {
|
||||||
bank: 0, pattern: 0,
|
bank: 0,
|
||||||
|
pattern: 0,
|
||||||
quantization: LaunchQuantization::Immediate,
|
quantization: LaunchQuantization::Immediate,
|
||||||
sync_mode: SyncMode::Reset,
|
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
|
// 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(
|
state.tick(tick_with(
|
||||||
vec![SeqCommand::PatternUpdate {
|
vec![SeqCommand::PatternUpdate {
|
||||||
bank: 0, pattern: 0, data: simple_pattern(2),
|
bank: 0,
|
||||||
|
pattern: 0,
|
||||||
|
data: simple_pattern(2),
|
||||||
}],
|
}],
|
||||||
1.25,
|
1.25,
|
||||||
));
|
));
|
||||||
@@ -1376,7 +1454,9 @@ mod tests {
|
|||||||
|
|
||||||
state.tick(tick_with(
|
state.tick(tick_with(
|
||||||
vec![SeqCommand::PatternUpdate {
|
vec![SeqCommand::PatternUpdate {
|
||||||
bank: 0, pattern: 0, data: simple_pattern(4),
|
bank: 0,
|
||||||
|
pattern: 0,
|
||||||
|
data: simple_pattern(4),
|
||||||
}],
|
}],
|
||||||
0.0,
|
0.0,
|
||||||
));
|
));
|
||||||
@@ -1384,7 +1464,8 @@ mod tests {
|
|||||||
// Start while paused: pending_starts gets cleared
|
// Start while paused: pending_starts gets cleared
|
||||||
state.tick(TickInput {
|
state.tick(TickInput {
|
||||||
commands: vec![SeqCommand::PatternStart {
|
commands: vec![SeqCommand::PatternStart {
|
||||||
bank: 0, pattern: 0,
|
bank: 0,
|
||||||
|
pattern: 0,
|
||||||
quantization: LaunchQuantization::Immediate,
|
quantization: LaunchQuantization::Immediate,
|
||||||
sync_mode: SyncMode::Reset,
|
sync_mode: SyncMode::Reset,
|
||||||
}],
|
}],
|
||||||
@@ -1404,13 +1485,16 @@ mod tests {
|
|||||||
|
|
||||||
state.tick(tick_with(
|
state.tick(tick_with(
|
||||||
vec![SeqCommand::PatternUpdate {
|
vec![SeqCommand::PatternUpdate {
|
||||||
bank: 0, pattern: 0, data: simple_pattern(4),
|
bank: 0,
|
||||||
|
pattern: 0,
|
||||||
|
data: simple_pattern(4),
|
||||||
}],
|
}],
|
||||||
0.0,
|
0.0,
|
||||||
));
|
));
|
||||||
state.tick(tick_with(
|
state.tick(tick_with(
|
||||||
vec![SeqCommand::PatternStart {
|
vec![SeqCommand::PatternStart {
|
||||||
bank: 0, pattern: 0,
|
bank: 0,
|
||||||
|
pattern: 0,
|
||||||
quantization: LaunchQuantization::Immediate,
|
quantization: LaunchQuantization::Immediate,
|
||||||
sync_mode: SyncMode::Reset,
|
sync_mode: SyncMode::Reset,
|
||||||
}],
|
}],
|
||||||
@@ -1434,15 +1518,19 @@ mod tests {
|
|||||||
state.tick(tick_with(
|
state.tick(tick_with(
|
||||||
vec![
|
vec![
|
||||||
SeqCommand::PatternUpdate {
|
SeqCommand::PatternUpdate {
|
||||||
bank: 0, pattern: 0, data: simple_pattern(4),
|
bank: 0,
|
||||||
|
pattern: 0,
|
||||||
|
data: simple_pattern(4),
|
||||||
},
|
},
|
||||||
SeqCommand::PatternStart {
|
SeqCommand::PatternStart {
|
||||||
bank: 0, pattern: 0,
|
bank: 0,
|
||||||
|
pattern: 0,
|
||||||
quantization: LaunchQuantization::Bar,
|
quantization: LaunchQuantization::Bar,
|
||||||
sync_mode: SyncMode::Reset,
|
sync_mode: SyncMode::Reset,
|
||||||
},
|
},
|
||||||
SeqCommand::PatternStart {
|
SeqCommand::PatternStart {
|
||||||
bank: 0, pattern: 0,
|
bank: 0,
|
||||||
|
pattern: 0,
|
||||||
quantization: LaunchQuantization::Bar,
|
quantization: LaunchQuantization::Bar,
|
||||||
sync_mode: SyncMode::Reset,
|
sync_mode: SyncMode::Reset,
|
||||||
},
|
},
|
||||||
@@ -1450,7 +1538,9 @@ mod tests {
|
|||||||
0.0,
|
0.0,
|
||||||
));
|
));
|
||||||
|
|
||||||
let pending_count = state.audio_state.pending_starts
|
let pending_count = state
|
||||||
|
.audio_state
|
||||||
|
.pending_starts
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|p| p.id == pid(0, 0))
|
.filter(|p| p.id == pid(0, 0))
|
||||||
.count();
|
.count();
|
||||||
@@ -1464,13 +1554,16 @@ mod tests {
|
|||||||
// Pattern of length 16 — won't complete iteration for many ticks
|
// Pattern of length 16 — won't complete iteration for many ticks
|
||||||
state.tick(tick_with(
|
state.tick(tick_with(
|
||||||
vec![SeqCommand::PatternUpdate {
|
vec![SeqCommand::PatternUpdate {
|
||||||
bank: 0, pattern: 0, data: simple_pattern(16),
|
bank: 0,
|
||||||
|
pattern: 0,
|
||||||
|
data: simple_pattern(16),
|
||||||
}],
|
}],
|
||||||
0.0,
|
0.0,
|
||||||
));
|
));
|
||||||
state.tick(tick_with(
|
state.tick(tick_with(
|
||||||
vec![SeqCommand::PatternStart {
|
vec![SeqCommand::PatternStart {
|
||||||
bank: 0, pattern: 0,
|
bank: 0,
|
||||||
|
pattern: 0,
|
||||||
quantization: LaunchQuantization::Immediate,
|
quantization: LaunchQuantization::Immediate,
|
||||||
sync_mode: SyncMode::Reset,
|
sync_mode: SyncMode::Reset,
|
||||||
}],
|
}],
|
||||||
|
|||||||
@@ -414,7 +414,7 @@ fn handle_modal_input(ctx: &mut InputContext, key: KeyEvent) -> InputResult {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
if let Some(path) = sample_path {
|
if let Some(path) = sample_path {
|
||||||
let index = doux::loader::scan_samples_dir(&path);
|
let index = doux::sampling::scan_samples_dir(&path);
|
||||||
let count = index.len();
|
let count = index.len();
|
||||||
let _ = ctx.audio_tx.load().send(AudioCommand::LoadSamples(index));
|
let _ = ctx.audio_tx.load().send(AudioCommand::LoadSamples(index));
|
||||||
ctx.app.audio.config.sample_count += count;
|
ctx.app.audio.config.sample_count += count;
|
||||||
@@ -698,7 +698,10 @@ fn handle_panel_input(ctx: &mut InputContext, key: KeyEvent) -> InputResult {
|
|||||||
let folder = &entry.folder;
|
let folder = &entry.folder;
|
||||||
let idx = entry.index;
|
let idx = entry.index;
|
||||||
let cmd = format!("/sound/{folder}/n/{idx}/gain/0.5/dur/1");
|
let cmd = format!("/sound/{folder}/n/{idx}/gain/0.5/dur/1");
|
||||||
let _ = ctx.audio_tx.load().send(AudioCommand::Evaluate { cmd, time: None });
|
let _ = ctx
|
||||||
|
.audio_tx
|
||||||
|
.load()
|
||||||
|
.send(AudioCommand::Evaluate { cmd, time: None });
|
||||||
}
|
}
|
||||||
_ => state.toggle_expand(),
|
_ => state.toggle_expand(),
|
||||||
}
|
}
|
||||||
@@ -1304,7 +1307,7 @@ fn load_project_samples(ctx: &mut InputContext) {
|
|||||||
let mut total_count = 0;
|
let mut total_count = 0;
|
||||||
for path in &paths {
|
for path in &paths {
|
||||||
if path.is_dir() {
|
if path.is_dir() {
|
||||||
let index = doux::loader::scan_samples_dir(path);
|
let index = doux::sampling::scan_samples_dir(path);
|
||||||
let count = index.len();
|
let count = index.len();
|
||||||
total_count += count;
|
total_count += count;
|
||||||
let _ = ctx.audio_tx.load().send(AudioCommand::LoadSamples(index));
|
let _ = ctx.audio_tx.load().send(AudioCommand::LoadSamples(index));
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ fn main() -> io::Result<()> {
|
|||||||
|
|
||||||
let mut initial_samples = Vec::new();
|
let mut initial_samples = Vec::new();
|
||||||
for path in &app.audio.config.sample_paths {
|
for path in &app.audio.config.sample_paths {
|
||||||
let index = doux::loader::scan_samples_dir(path);
|
let index = doux::sampling::scan_samples_dir(path);
|
||||||
app.audio.config.sample_count += index.len();
|
app.audio.config.sample_count += index.len();
|
||||||
initial_samples.extend(index);
|
initial_samples.extend(index);
|
||||||
}
|
}
|
||||||
@@ -184,7 +184,7 @@ fn main() -> io::Result<()> {
|
|||||||
|
|
||||||
let mut restart_samples = Vec::new();
|
let mut restart_samples = Vec::new();
|
||||||
for path in &app.audio.config.sample_paths {
|
for path in &app.audio.config.sample_paths {
|
||||||
let index = doux::loader::scan_samples_dir(path);
|
let index = doux::sampling::scan_samples_dir(path);
|
||||||
restart_samples.extend(index);
|
restart_samples.extend(index);
|
||||||
}
|
}
|
||||||
app.audio.config.sample_count = restart_samples.len();
|
app.audio.config.sample_count = restart_samples.len();
|
||||||
@@ -233,7 +233,10 @@ fn main() -> io::Result<()> {
|
|||||||
app.metrics.dropped_events = seq_snapshot.dropped_events;
|
app.metrics.dropped_events = seq_snapshot.dropped_events;
|
||||||
|
|
||||||
app.ui.event_flash = (app.ui.event_flash - 0.1).max(0.0);
|
app.ui.event_flash = (app.ui.event_flash - 0.1).max(0.0);
|
||||||
let new_events = app.metrics.event_count.saturating_sub(app.ui.last_event_count);
|
let new_events = app
|
||||||
|
.metrics
|
||||||
|
.event_count
|
||||||
|
.saturating_sub(app.ui.last_event_count);
|
||||||
if new_events > 0 {
|
if new_events > 0 {
|
||||||
app.ui.event_flash = (new_events as f32 * 0.4).min(1.0);
|
app.ui.event_flash = (new_events as f32 * 0.4).min(1.0);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user