Fix: try to fix the non working sync

This commit is contained in:
2026-03-16 22:07:15 +01:00
parent 6d71c64a34
commit 1513d80a8d
5 changed files with 191 additions and 27 deletions

20
Cargo.lock generated
View File

@@ -873,7 +873,7 @@ dependencies = [
"cpal 0.17.1",
"crossbeam-channel",
"crossterm",
"doux 0.0.14",
"doux",
"eframe",
"egui",
"egui_ratatui",
@@ -925,7 +925,7 @@ dependencies = [
"cagire-ratatui",
"crossbeam-channel",
"crossterm",
"doux 0.0.13",
"doux",
"egui_ratatui",
"nih_plug",
"nih_plug_egui",
@@ -1822,22 +1822,6 @@ 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"

View File

@@ -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.13", features = ["native", "soundfont"] }
doux = { git = "https://github.com/sova-org/doux", tag = "v0.0.14", 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"

View File

@@ -185,6 +185,7 @@ impl Plugin for CagirePlugin {
self.sample_rate,
self.output_channels,
64,
buffer_config.max_buffer_size as usize,
);
self.bridge
.sample_registry

View File

@@ -51,7 +51,7 @@ while [[ $# -gt 0 ]]; do
echo ""
echo "Options:"
echo " --platforms <list> Comma-separated: macos-arm64,macos-x86_64,linux-x86_64,linux-aarch64,windows-x86_64"
echo " --targets <list> Comma-separated: cli,desktop,plugins"
echo " --targets <list> Comma-separated: cli,desktop,plugins,installer"
echo " --all Build all platforms and targets"
echo " --yes Skip confirmation prompt"
echo ""
@@ -105,22 +105,30 @@ 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
@@ -129,10 +137,24 @@ 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() {
@@ -148,6 +170,7 @@ confirm_summary() {
$build_cagire && echo " - cagire"
$build_desktop && echo " - cagire-desktop"
$build_plugins && echo " - cagire-plugins (CLAP/VST3)"
$build_installer && echo " - installer (NSIS)"
echo ""
read -rp "Proceed? [Y/n] " yn
case "${yn,,}" in
@@ -328,7 +351,7 @@ copy_artifacts() {
fi
# NSIS installer for Windows targets
if [[ "$os" == "windows" ]] && command -v makensis &>/dev/null; then
if $build_installer && [[ "$os" == "windows" ]] && command -v makensis &>/dev/null; then
echo " Building NSIS installer..."
local version
version=$(grep '^version' Cargo.toml | head -1 | sed 's/.*"\(.*\)"/\1/')
@@ -384,6 +407,7 @@ 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
@@ -405,6 +429,7 @@ 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
@@ -413,13 +438,22 @@ elif [[ -n "$cli_platforms" || -n "$cli_targets" ]]; then
cli) build_cagire=true ;;
desktop) build_desktop=true ;;
plugins) build_plugins=true ;;
*) echo "Unknown target: $t (expected: cli, desktop, plugins)"; exit 1 ;;
installer) build_installer=true ;;
*) echo "Unknown target: $t (expected: cli, desktop, plugins, installer)"; 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

View File

@@ -299,6 +299,7 @@ struct ActivePattern {
step_index: usize,
iter: usize,
last_step_beat: f64,
activation_beat: Option<f64>,
}
#[derive(Clone, Copy)]
@@ -490,6 +491,30 @@ fn check_quantization_boundary(
}
}
fn quantization_boundary_beat(
quantization: LaunchQuantization,
prev_beat: f64,
quantum: f64,
) -> Option<f64> {
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 {
@@ -892,6 +917,8 @@ 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(
@@ -902,6 +929,7 @@ impl SequencerState {
step_index: start_step,
iter: 0,
last_step_beat: beat,
activation_beat: boundary,
},
);
self.buf_activated.push(pending.id);
@@ -982,8 +1010,13 @@ 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 {
for step_beat in &step_beats[skip..] {
let step_beat = *step_beat;
result.any_step_fired = true;
active.last_step_beat = step_beat;
let step_idx = active.step_index % pattern.length;
@@ -2391,6 +2424,118 @@ 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();