feat: add tempo-sync for delay time

Add a Sync checkbox parameter.  When enabled the Time parameter is
interpreted as a beat subdivision (1.0 = quarter note, 0.5 = eighth,
2.0 = half note) derived from the host transport tempo.  Falls back to
120 BPM when no tempo is available.  The Time label shows 'Time (1/n)'
in the UI when sync is on for visual clarity.
This commit is contained in:
2026-09-04 19:14:08 +09:00
parent fc8eaa4057
commit 1348ac59e0
+38 -5
View File
@@ -5,7 +5,7 @@ use nih_plug_egui::{create_egui_editor, egui, widgets, EguiState};
use std::sync::Arc;
// ──────────────────────────────────────────────
// Params — time, feedback, mix
// Params — time, feedback, mix, sync
// ──────────────────────────────────────────────
const MAX_DELAY_MS: f32 = 2_000.0;
@@ -16,7 +16,7 @@ struct KolmekiviParams {
#[persist = "editor-state"]
editor_state: Arc<EguiState>,
/// Delay time in milliseconds.
/// Delay time in milliseconds (sync off) or beat subdivision (sync on).
#[id = "time"]
time: FloatParam,
@@ -27,12 +27,17 @@ struct KolmekiviParams {
/// Wet/dry mix (0 = dry only, 1 = wet only).
#[id = "mix"]
mix: FloatParam,
/// When true, Time is interpreted as a beat subdivision (1.0 = quarter note)
/// derived from the host tempo. When false, Time is raw milliseconds.
#[id = "sync"]
sync: BoolParam,
}
impl Default for KolmekiviParams {
fn default() -> Self {
Self {
editor_state: EguiState::from_size(320, 160),
editor_state: EguiState::from_size(320, 180),
time: FloatParam::new(
"Time",
500.0,
@@ -54,6 +59,8 @@ impl Default for KolmekiviParams {
FloatRange::Linear { min: 0.0, max: 1.0 },
)
.with_smoother(SmoothingStyle::Logarithmic(32.0)),
sync: BoolParam::new("Sync", false),
}
}
}
@@ -168,9 +175,14 @@ impl Plugin for Kolmekivi {
(),
|_, _| {},
move |egui_ctx, setter, _state| {
let sync = params.sync.value();
egui::CentralPanel::default().show(egui_ctx, |ui| {
ui.horizontal(|ui| {
ui.label("Time");
if sync {
ui.label("Time (1/n)");
} else {
ui.label("Time");
}
ui.add(widgets::ParamSlider::for_param(&params.time, setter));
});
ui.horizontal(|ui| {
@@ -181,6 +193,14 @@ impl Plugin for Kolmekivi {
ui.label("Mix");
ui.add(widgets::ParamSlider::for_param(&params.mix, setter));
});
ui.horizontal(|ui| {
let mut sync_val = params.sync.value();
let changed = ui.checkbox(&mut sync_val as &mut bool, "Sync to tempo");
if changed.changed() {
setter.begin_set_parameter(&params.sync);
setter.set_parameter(&params.sync, sync_val);
}
});
});
},
)
@@ -222,7 +242,20 @@ impl Plugin for Kolmekivi {
let time_val = self.params.time.smoothed.next();
let feedback = self.params.feedback.smoothed.next();
let mix = self.params.mix.smoothed.next();
let delay_samples = (time_val * sample_rate / 1000.0) as usize;
let sync = self.params.sync.value();
let delay_samples = if sync {
// Time is a beat subdivision multiplier: 1.0 = quarter note,
// 0.5 = eighth, 2.0 = half note, etc.
let tempo = _context.transport().tempo.unwrap_or(120.0) as f32;
let beat_duration_s = 60.0_f32 / tempo; // quarter note in seconds
let subdivisions = time_val.clamp(0.015625, 8.0); // 32nd to 4x whole
let delay_s = subdivisions * beat_duration_s;
(delay_s * sample_rate) as usize
} else {
// Time is raw milliseconds
(time_val * sample_rate / 1000.0) as usize
};
// Process each sample in the buffer
for mut frame in buffer.iter_samples() {