diff --git a/Cargo.lock b/Cargo.lock index 2a07b06..d6400f5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -873,7 +873,7 @@ dependencies = [ "cpal 0.17.1", "crossbeam-channel", "crossterm", - "doux", + "doux 0.0.14", "eframe", "egui", "egui_ratatui", @@ -925,7 +925,7 @@ dependencies = [ "cagire-ratatui", "crossbeam-channel", "crossterm", - "doux", + "doux 0.0.13", "egui_ratatui", "nih_plug", "nih_plug_egui", @@ -1822,6 +1822,22 @@ dependencies = [ "litrs", ] +[[package]] +name = "doux" +version = "0.0.13" +source = "git+https://github.com/sova-org/doux?tag=v0.0.13#b8150d907e4cc2764e82fdaa424df41ceef9b0d2" +dependencies = [ + "arc-swap", + "clap", + "cpal 0.17.1", + "crossbeam-channel", + "ringbuf", + "rosc", + "rustyline", + "soundfont", + "symphonia", +] + [[package]] name = "doux" version = "0.0.14" diff --git a/plugins/cagire-plugins/Cargo.toml b/plugins/cagire-plugins/Cargo.toml index 2fe055f..c7dba50 100644 --- a/plugins/cagire-plugins/Cargo.toml +++ b/plugins/cagire-plugins/Cargo.toml @@ -14,7 +14,7 @@ cagire = { path = "../..", default-features = false, features = ["block-renderer cagire-forth = { path = "../../crates/forth" } cagire-project = { path = "../../crates/project" } cagire-ratatui = { path = "../../crates/ratatui" } -doux = { git = "https://github.com/sova-org/doux", tag = "v0.0.14", features = ["native", "soundfont"] } +doux = { git = "https://github.com/sova-org/doux", tag = "v0.0.13", features = ["native", "soundfont"] } nih_plug = { git = "https://github.com/robbert-vdh/nih-plug", features = ["standalone"] } nih_plug_egui = { git = "https://github.com/robbert-vdh/nih-plug" } egui_ratatui = "2.1" diff --git a/plugins/cagire-plugins/src/lib.rs b/plugins/cagire-plugins/src/lib.rs index 58f9008..0b206dc 100644 --- a/plugins/cagire-plugins/src/lib.rs +++ b/plugins/cagire-plugins/src/lib.rs @@ -185,7 +185,6 @@ impl Plugin for CagirePlugin { self.sample_rate, self.output_channels, 64, - buffer_config.max_buffer_size as usize, ); self.bridge .sample_registry diff --git a/scripts/build-all.sh b/scripts/build-all.sh index f3d1094..55e8d0e 100755 --- a/scripts/build-all.sh +++ b/scripts/build-all.sh @@ -51,7 +51,7 @@ while [[ $# -gt 0 ]]; do echo "" echo "Options:" echo " --platforms Comma-separated: macos-arm64,macos-x86_64,linux-x86_64,linux-aarch64,windows-x86_64" - echo " --targets Comma-separated: cli,desktop,plugins,installer" + echo " --targets Comma-separated: cli,desktop,plugins" echo " --all Build all platforms and targets" echo " --yes Skip confirmation prompt" echo "" @@ -105,30 +105,22 @@ prompt_platforms() { } prompt_targets() { - local show_installer=false - for p in "${selected_platforms[@]}"; do - [[ "$p" == *windows* ]] && show_installer=true - done - echo "" echo "Select targets (0=all, comma-separated):" echo " 0) All" echo " 1) cagire" echo " 2) cagire-desktop" echo " 3) cagire-plugins (CLAP/VST3)" - $show_installer && echo " 4) installer (NSIS, implies cli+desktop+plugins)" read -rp "> " choice build_cagire=false build_desktop=false build_plugins=false - build_installer=false if [[ "$choice" == "0" || -z "$choice" ]]; then build_cagire=true build_desktop=true build_plugins=true - $show_installer && build_installer=true else IFS=',' read -ra targets <<< "$choice" for t in "${targets[@]}"; do @@ -137,24 +129,10 @@ prompt_targets() { 1) build_cagire=true ;; 2) build_desktop=true ;; 3) build_plugins=true ;; - 4) - if $show_installer; then - build_installer=true - else - echo "Invalid target: $t"; exit 1 - fi - ;; *) echo "Invalid target: $t"; exit 1 ;; esac done fi - - # Installer requires cli+desktop+plugins - if $build_installer; then - build_cagire=true - build_desktop=true - build_plugins=true - fi } confirm_summary() { @@ -169,8 +147,7 @@ confirm_summary() { echo "Targets:" $build_cagire && echo " - cagire" $build_desktop && echo " - cagire-desktop" - $build_plugins && echo " - cagire-plugins (CLAP/VST3)" - $build_installer && echo " - installer (NSIS)" + $build_plugins && echo " - cagire-plugins (CLAP/VST3)" echo "" read -rp "Proceed? [Y/n] " yn case "${yn,,}" in @@ -351,7 +328,7 @@ copy_artifacts() { fi # NSIS installer for Windows targets - if $build_installer && [[ "$os" == "windows" ]] && command -v makensis &>/dev/null; then + if [[ "$os" == "windows" ]] && command -v makensis &>/dev/null; then echo " Building NSIS installer..." local version version=$(grep '^version' Cargo.toml | head -1 | sed 's/.*"\(.*\)"/\1/') @@ -407,7 +384,6 @@ if $cli_all; then build_cagire=true build_desktop=true build_plugins=true - build_installer=true elif [[ -n "$cli_platforms" || -n "$cli_targets" ]]; then # Resolve platforms from CLI if [[ -n "$cli_platforms" ]]; then @@ -429,31 +405,21 @@ elif [[ -n "$cli_platforms" || -n "$cli_targets" ]]; then build_cagire=false build_desktop=false build_plugins=false - build_installer=false if [[ -n "$cli_targets" ]]; then IFS=',' read -ra tgts <<< "$cli_targets" for t in "${tgts[@]}"; do t="${t// /}" case "$t" in - cli) build_cagire=true ;; - desktop) build_desktop=true ;; - plugins) build_plugins=true ;; - installer) build_installer=true ;; - *) echo "Unknown target: $t (expected: cli, desktop, plugins, installer)"; exit 1 ;; + cli) build_cagire=true ;; + desktop) build_desktop=true ;; + plugins) build_plugins=true ;; + *) echo "Unknown target: $t (expected: cli, desktop, plugins)"; exit 1 ;; esac done else build_cagire=true build_desktop=true build_plugins=true - build_installer=true - fi - - # Installer requires cli+desktop+plugins - if $build_installer; then - build_cagire=true - build_desktop=true - build_plugins=true fi else prompt_platforms diff --git a/src/engine/sequencer.rs b/src/engine/sequencer.rs index 87cb06e..42bed3c 100644 --- a/src/engine/sequencer.rs +++ b/src/engine/sequencer.rs @@ -299,7 +299,6 @@ struct ActivePattern { step_index: usize, iter: usize, last_step_beat: f64, - activation_beat: Option, } #[derive(Clone, Copy)] @@ -491,30 +490,6 @@ fn check_quantization_boundary( } } -fn quantization_boundary_beat( - quantization: LaunchQuantization, - prev_beat: f64, - quantum: f64, -) -> Option { - match quantization { - LaunchQuantization::Immediate => None, - LaunchQuantization::Beat => Some(prev_beat.floor() + 1.0), - LaunchQuantization::Bar => Some(((prev_beat / quantum).floor() + 1.0) * quantum), - LaunchQuantization::Bars2 => { - let q = quantum * 2.0; - Some(((prev_beat / q).floor() + 1.0) * q) - } - LaunchQuantization::Bars4 => { - let q = quantum * 4.0; - Some(((prev_beat / q).floor() + 1.0) * q) - } - LaunchQuantization::Bars8 => { - let q = quantum * 8.0; - Some(((prev_beat / q).floor() + 1.0) * q) - } - } -} - type StepKey = (usize, usize, usize); struct RunsCounter { @@ -917,8 +892,6 @@ impl SequencerState { } } }; - let boundary = - quantization_boundary_beat(pending.quantization, prev_beat, quantum); self.runs_counter .clear_pattern(pending.id.bank, pending.id.pattern); self.audio_state.active_patterns.insert( @@ -929,7 +902,6 @@ impl SequencerState { step_index: start_step, iter: 0, last_step_beat: beat, - activation_beat: boundary, }, ); self.buf_activated.push(pending.id); @@ -1010,13 +982,8 @@ impl SequencerState { .unwrap_or_else(|| pattern.speed.multiplier()); let step_beats = substeps_in_window(frontier, lookahead_end, speed_mult); - let activation = active.activation_beat.take(); - let skip = activation.map_or(0, |ab| { - step_beats.iter().take_while(|&&b| b < ab).count() - }); - for step_beat in &step_beats[skip..] { - let step_beat = *step_beat; + for step_beat in step_beats { result.any_step_fired = true; active.last_step_beat = step_beat; let step_idx = active.step_index % pattern.length; @@ -2424,118 +2391,6 @@ mod tests { ); } - #[test] - fn test_quantization_boundary_beat() { - let quantum = 4.0; - - // Immediate → None - assert_eq!( - quantization_boundary_beat(LaunchQuantization::Immediate, 1.5, quantum), - None - ); - - // Beat → next integer beat - assert_eq!( - quantization_boundary_beat(LaunchQuantization::Beat, 1.5, quantum), - Some(2.0) - ); - assert_eq!( - quantization_boundary_beat(LaunchQuantization::Beat, 3.0, quantum), - Some(4.0) - ); - - // Bar → next multiple of quantum - assert_eq!( - quantization_boundary_beat(LaunchQuantization::Bar, 3.9, quantum), - Some(4.0) - ); - assert_eq!( - quantization_boundary_beat(LaunchQuantization::Bar, 0.0, quantum), - Some(4.0) - ); - - // Bars2 → next multiple of quantum*2 - assert_eq!( - quantization_boundary_beat(LaunchQuantization::Bars2, 3.9, quantum), - Some(8.0) - ); - - // Bars4 → next multiple of quantum*4 - assert_eq!( - quantization_boundary_beat(LaunchQuantization::Bars4, 3.9, quantum), - Some(16.0) - ); - - // Bars8 → next multiple of quantum*8 - assert_eq!( - quantization_boundary_beat(LaunchQuantization::Bars8, 3.9, quantum), - Some(32.0) - ); - } - - #[test] - fn test_activation_beat_prevents_early_substeps() { - let mut state = make_state(); - - state.tick(tick_with( - vec![SeqCommand::PatternUpdate { - bank: 0, - pattern: 0, - data: simple_pattern(16), - }], - 0.0, - )); - - // Queue Bar-quantized start (boundary at beat 4.0, quantum=4) - state.tick(tick_with( - vec![SeqCommand::PatternStart { - bank: 0, - pattern: 0, - quantization: LaunchQuantization::Bar, - sync_mode: SyncMode::Reset, - }], - 3.5, - )); - assert!(!state.audio_state.active_patterns.contains_key(&pid(0, 0))); - - // Simulate a wide lookahead window that crosses the bar boundary: - // frontier=3.875, lookahead_end=4.125 — spans both sides of beat 4.0 - // Without activation_beat filtering, substeps before 4.0 would fire. - state.tick(TickInput { - commands: Vec::new(), - playing: true, - beat: 4.125, - lookahead_end: 4.125, - tempo: 120.0, - quantum: 4.0, - fill: false, - nudge_secs: 0.0, - current_time_us: 0, - audio_sample_pos: 0, - sr: 48000.0, - mouse_x: 0.5, - mouse_y: 0.5, - mouse_down: 0.0, - }); - - // Pattern should be active - assert!(state.audio_state.active_patterns.contains_key(&pid(0, 0))); - - // The activation_beat should have been consumed (set to None) - let ap = state.audio_state.active_patterns.get(&pid(0, 0)).unwrap(); - assert!(ap.activation_beat.is_none(), "activation_beat should be consumed after first execute"); - - // Step index should reflect only substeps at/after beat 4.0, not before - // At 1x speed: substeps at 0.25-beat intervals. From frontier 3.875 to 4.125: - // substeps_in_window yields beats at 4.0 (= 16/4). Pre-4.0 substeps should be skipped. - assert_eq!(ap.step_index, 1, "Only substep at beat 4.0 should fire, not pre-boundary ones"); - - // Second tick: activation_beat already consumed, all substeps should fire normally - let _output2 = state.tick(tick_at(4.375, true)); - let ap2 = state.audio_state.active_patterns.get(&pid(0, 0)).unwrap(); - assert_eq!(ap2.step_index, 2, "Second tick should advance normally"); - } - #[test] fn test_no_false_boundary_after_pause_within_same_bar() { let mut state = make_state();