feat(weld): add sidechain test build
Weld will phase-align a bass layer to another layer it hears through the sidechain. This first build only checks that FL Studio delivers the sidechain to a CLAP aux port: it passes the main layer through unchanged, meters the sidechain and offers Layer/Sum/Sum dry listening. No latency.
This commit is contained in:
1 parent
b3ebe4d952
commit
e3286f9094
21 files changed
+3471
-9
No files matched your search
@@ -0,0 +1,115 @@
|
||||
//! Weld editor, shared by the CLAP wrapper and the headless renderer.
|
||||
use common_ui::*;
|
||||
use egui::{self, FontId, RichText};
|
||||
use weld_core::{AnalyzerFrame, Listen, Settings};
|
||||
|
||||
pub use common_ui::style;
|
||||
pub const WIDTH: u32 = 1040;
|
||||
pub const HEIGHT: u32 = 650;
|
||||
|
||||
pub struct State {
|
||||
pub analyzer: AnalyzerFrame,
|
||||
/// True while a pointer button is held, so knob drags become one host gesture.
|
||||
pub gesture_active: bool,
|
||||
pub help: String,
|
||||
}
|
||||
impl Default for State {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
analyzer: AnalyzerFrame::default(),
|
||||
gesture_active: false,
|
||||
help: DEFAULT_HELP.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
const DEFAULT_HELP: &str =
|
||||
"Put Weld on one bass layer and send the other layer to its sidechain input.";
|
||||
impl State {
|
||||
pub fn accept_frame(&mut self, frame: AnalyzerFrame) {
|
||||
let old = self.analyzer;
|
||||
self.analyzer = frame;
|
||||
// Meters fall smoothly between frames.
|
||||
self.analyzer.main_db = frame.main_db.max(old.main_db - 3.);
|
||||
self.analyzer.side_db = frame.side_db.max(old.side_db - 3.);
|
||||
self.analyzer.side_peak_db = frame.side_peak_db.max(old.side_peak_db - 3.);
|
||||
}
|
||||
}
|
||||
fn hint(r: &egui::Response, state: &mut State, text: &str) {
|
||||
if r.hovered() || r.has_focus() {
|
||||
state.help = text.into();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn show(ui: &mut egui::Ui, settings: &mut Settings, state: &mut State) {
|
||||
backdrop(ui);
|
||||
state.help = DEFAULT_HELP.into();
|
||||
state.gesture_active = ui.input(|i| i.pointer.any_down());
|
||||
ui.add_space(2.);
|
||||
header(ui, "WELD", "LAYER ALIGN", |ui| {
|
||||
let r = ui
|
||||
.selectable_label(settings.bypass, RichText::new("BYPASS").size(13.))
|
||||
.on_hover_text("Pass the main layer through unchanged.");
|
||||
if r.clicked() {
|
||||
settings.bypass = !settings.bypass;
|
||||
}
|
||||
});
|
||||
ui.add_space(6.);
|
||||
let frame = state.analyzer;
|
||||
panel(ui, |ui| {
|
||||
ui.set_width(ui.available_width());
|
||||
ui.set_min_height(380.);
|
||||
ui.vertical(|ui| {
|
||||
caption(ui, "SIDECHAIN");
|
||||
let (text, color) = if frame.side_db > -90. {
|
||||
(format!("sidechain: {:.0} dBFS", frame.side_db), TEXT)
|
||||
} else if frame.side_seen {
|
||||
("sidechain: silent".to_string(), MUTED)
|
||||
} else {
|
||||
("no sidechain signal".to_string(), DIM)
|
||||
};
|
||||
ui.label(
|
||||
RichText::new(text)
|
||||
.font(FontId::new(40., egui::FontFamily::Name("medium".into())))
|
||||
.color(color),
|
||||
);
|
||||
ui.add_space(12.);
|
||||
meter(ui, "MAIN", frame.main_db, STEEL, 520.);
|
||||
meter(ui, "SIDECHAIN", frame.side_db, ACCENT, 520.);
|
||||
meter(ui, "SIDE PEAK", frame.side_peak_db, FOCUS, 520.);
|
||||
ui.add_space(18.);
|
||||
ui.label(
|
||||
RichText::new(
|
||||
"FL Studio: on the other layer's mixer track, right-click the route arrow \
|
||||
to this track and choose \"Sidechain to this track\". Then open Weld's \
|
||||
wrapper settings and set the sidechain input to that track.",
|
||||
)
|
||||
.size(13.)
|
||||
.color(MUTED),
|
||||
);
|
||||
});
|
||||
});
|
||||
ui.add_space(6.);
|
||||
panel(ui, |ui| {
|
||||
ui.set_width(ui.available_width());
|
||||
ui.horizontal(|ui| {
|
||||
caption(ui, "LISTEN");
|
||||
for (listen, label, help) in [
|
||||
(Listen::Layer, "LAYER", "Output the main layer only. Use this in the mix."),
|
||||
(Listen::Sum, "SUM", "Output main + sidechain layer. Mute the other layer's own send while listening."),
|
||||
(Listen::SumDry, "SUM DRY", "Output the unprocessed main + sidechain layer, to compare with SUM."),
|
||||
] {
|
||||
let r = ui.selectable_label(settings.listen == listen, RichText::new(label).size(13.));
|
||||
if r.clicked() {
|
||||
settings.listen = listen;
|
||||
}
|
||||
hint(&r, state, help);
|
||||
}
|
||||
ui.add_space(40.);
|
||||
let d = Settings::default();
|
||||
let r = rotary(ui, "Output", &mut settings.output, -12. ..=12., d.output, " dB", 54., ACCENT);
|
||||
hint(&r, state, "Output trim.");
|
||||
});
|
||||
});
|
||||
ui.separator();
|
||||
ui.label(RichText::new(&state.help).color(MUTED));
|
||||
}
|
||||
Reference in new issue
Block a user