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:
+37
-4
@@ -5,7 +5,7 @@ use nih_plug_egui::{create_egui_editor, egui, widgets, EguiState};
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
// ──────────────────────────────────────────────
|
// ──────────────────────────────────────────────
|
||||||
// Params — time, feedback, mix
|
// Params — time, feedback, mix, sync
|
||||||
// ──────────────────────────────────────────────
|
// ──────────────────────────────────────────────
|
||||||
|
|
||||||
const MAX_DELAY_MS: f32 = 2_000.0;
|
const MAX_DELAY_MS: f32 = 2_000.0;
|
||||||
@@ -16,7 +16,7 @@ struct KolmekiviParams {
|
|||||||
#[persist = "editor-state"]
|
#[persist = "editor-state"]
|
||||||
editor_state: Arc<EguiState>,
|
editor_state: Arc<EguiState>,
|
||||||
|
|
||||||
/// Delay time in milliseconds.
|
/// Delay time in milliseconds (sync off) or beat subdivision (sync on).
|
||||||
#[id = "time"]
|
#[id = "time"]
|
||||||
time: FloatParam,
|
time: FloatParam,
|
||||||
|
|
||||||
@@ -27,12 +27,17 @@ struct KolmekiviParams {
|
|||||||
/// Wet/dry mix (0 = dry only, 1 = wet only).
|
/// Wet/dry mix (0 = dry only, 1 = wet only).
|
||||||
#[id = "mix"]
|
#[id = "mix"]
|
||||||
mix: FloatParam,
|
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 {
|
impl Default for KolmekiviParams {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
editor_state: EguiState::from_size(320, 160),
|
editor_state: EguiState::from_size(320, 180),
|
||||||
time: FloatParam::new(
|
time: FloatParam::new(
|
||||||
"Time",
|
"Time",
|
||||||
500.0,
|
500.0,
|
||||||
@@ -54,6 +59,8 @@ impl Default for KolmekiviParams {
|
|||||||
FloatRange::Linear { min: 0.0, max: 1.0 },
|
FloatRange::Linear { min: 0.0, max: 1.0 },
|
||||||
)
|
)
|
||||||
.with_smoother(SmoothingStyle::Logarithmic(32.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| {
|
move |egui_ctx, setter, _state| {
|
||||||
|
let sync = params.sync.value();
|
||||||
egui::CentralPanel::default().show(egui_ctx, |ui| {
|
egui::CentralPanel::default().show(egui_ctx, |ui| {
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
|
if sync {
|
||||||
|
ui.label("Time (1/n)");
|
||||||
|
} else {
|
||||||
ui.label("Time");
|
ui.label("Time");
|
||||||
|
}
|
||||||
ui.add(widgets::ParamSlider::for_param(¶ms.time, setter));
|
ui.add(widgets::ParamSlider::for_param(¶ms.time, setter));
|
||||||
});
|
});
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
@@ -181,6 +193,14 @@ impl Plugin for Kolmekivi {
|
|||||||
ui.label("Mix");
|
ui.label("Mix");
|
||||||
ui.add(widgets::ParamSlider::for_param(¶ms.mix, setter));
|
ui.add(widgets::ParamSlider::for_param(¶ms.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(¶ms.sync);
|
||||||
|
setter.set_parameter(¶ms.sync, sync_val);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -222,7 +242,20 @@ impl Plugin for Kolmekivi {
|
|||||||
let time_val = self.params.time.smoothed.next();
|
let time_val = self.params.time.smoothed.next();
|
||||||
let feedback = self.params.feedback.smoothed.next();
|
let feedback = self.params.feedback.smoothed.next();
|
||||||
let mix = self.params.mix.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
|
// Process each sample in the buffer
|
||||||
for mut frame in buffer.iter_samples() {
|
for mut frame in buffer.iter_samples() {
|
||||||
|
|||||||
Reference in New Issue
Block a user