feat(patina): per-band compressor gain stage with Threshold, Ratio and Knee

Tester feedback asked for a threshold and a ratio. Each band now cuts like a
small compressor: Threshold (dB above the local floor) replaces Selectivity,
Ratio scales the cut per dB above it (inf:1 is the old behaviour), and a
Soft/Hard knee switch sets the curve. A small graph shows bump height in, cut
out. Mix and Output move to the slider panel so the detection panel fits.
common-ui gains rotary_text for knobs with custom value text.
This commit is contained in:
nvrl committed 2026-11-06 17:29:54 +01:00
1 parent 7b989f7685
commit 9ca4e4f9dd
13 files changed
+568 -102

No files matched your search

+52 -11
View File
@@ -113,6 +113,58 @@ pub fn rotary(
unit: &str,
size: f32,
color: Color32,
) -> egui::Response {
let response = dial(ui, value, range.clone(), default, size, color);
dial_caption(ui, name, size);
ui.add(
egui::DragValue::new(value)
.range(range)
.speed(0.1)
.suffix(unit),
);
response
}
/// A rotary whose value field shows custom text, for values that are not a plain number
/// with a unit (a ratio, a mapped knob position).
#[allow(clippy::too_many_arguments)]
pub fn rotary_text(
ui: &mut egui::Ui,
name: &str,
value: &mut f32,
range: std::ops::RangeInclusive<f32>,
default: f32,
size: f32,
color: Color32,
format: impl Fn(f32) -> String,
parse: impl Fn(&str) -> Option<f32>,
) -> egui::Response {
let response = dial(ui, value, range.clone(), default, size, color);
dial_caption(ui, name, size);
let speed = (*range.end() - *range.start()) as f64 / 200.;
ui.add(
egui::DragValue::new(value)
.range(range)
.speed(speed)
.custom_formatter(|v, _| format(v as f32))
.custom_parser(|s| parse(s).map(f64::from)),
);
response
}
fn dial_caption(ui: &mut egui::Ui, name: &str, size: f32) {
ui.label(
RichText::new(name.to_uppercase())
.size(if size > 100. { 14. } else { 12. })
.color(MUTED),
);
}
/// The knob itself: drag, keys, double-click reset, and drawing.
fn dial(
ui: &mut egui::Ui,
value: &mut f32,
range: std::ops::RangeInclusive<f32>,
default: f32,
size: f32,
color: Color32,
) -> egui::Response {
let low = *range.start();
let high = *range.end();
@@ -212,17 +264,6 @@ pub fn rotary(
if response.has_focus() {
p.circle_stroke(center, radius + 2., Stroke::new(1.0_f32, color));
}
ui.label(
RichText::new(name.to_uppercase())
.size(if size > 100. { 14. } else { 12. })
.color(MUTED),
);
ui.add(
egui::DragValue::new(value)
.range(range)
.speed(0.1)
.suffix(unit),
);
response
}
/// Vertical gradient rectangle, used for panel faces.