feat(ui): MAIN and ADVANCED pages, Anvil main page around Mode, Split and Sub level
Producers found the panels crowded: every control had the same weight. The header now carries a MAIN / ADVANCED switch (common-ui header_paged). The main page keeps the controls a first result needs; the advanced page shows everything, as before. - Anvil: Mode (Replace or Enhance), Split and Sub level, plus Octave, Ghost, Keep and Output. Kick lock, Dynamics, Drive, Glide, Phase, Gate and the pitch switches move to Advanced. The note readout says when the original sub plays because tracking is unsure. - Lathe, Temper, Knurl, Planish, Braze: tracking and fine-tuning controls (Gate, Smooth, Snap, Harmonics, Push at, Floor, Bright, Normalize, band gains, Phase) move to Advanced. Lathe labels the octaves of the note in Hz on the harmonic axis. - Forge passes the page to its module pages. - Weld: the main page shows the level won back at full height, Mode, Response and Output; the phase plot, Sections, Low, High and Gate move to Advanced. - editor-check renders the advanced pages with --advanced.
This commit is contained in:
1 parent
d6939a753a
commit
01fd3241eb
10 files changed
+594
-227
No files matched your search
+171
-11
@@ -1,6 +1,6 @@
|
||||
//! Anvil editor, shared by the CLAP wrapper and the headless renderer.
|
||||
use anvil_core::{
|
||||
AnalyzerFrame, Settings, Source, AUDITION, AUDITION_HITS, GHOST_COUNT, KICK_OFFSET_MAX,
|
||||
AnalyzerFrame, Mode, Settings, Source, AUDITION, AUDITION_HITS, GHOST_COUNT, KICK_OFFSET_MAX,
|
||||
PHONE_LOW, SCOPE,
|
||||
};
|
||||
use common_ui::*;
|
||||
@@ -20,10 +20,13 @@ pub struct State {
|
||||
/// True while a pointer button is held, so knob drags become one host gesture.
|
||||
pub gesture_active: bool,
|
||||
pub help: String,
|
||||
/// The advanced page shows instead of the main one. Not saved.
|
||||
pub advanced: bool,
|
||||
}
|
||||
impl Default for State {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
advanced: false,
|
||||
solo: false,
|
||||
phone: false,
|
||||
audition: false,
|
||||
@@ -206,7 +209,8 @@ pub fn show(ui: &mut egui::Ui, settings: &mut Settings, state: &mut State) {
|
||||
state.help = DEFAULT_HELP.into();
|
||||
state.gesture_active = ui.input(|i| i.pointer.any_down());
|
||||
ui.add_space(2.);
|
||||
header(ui, "ANVIL", "SUB ANCHOR", |ui| {
|
||||
let mut advanced = state.advanced;
|
||||
header_paged(ui, "ANVIL", "SUB ANCHOR", Some(&mut advanced), |ui| {
|
||||
let r = ui
|
||||
.selectable_label(settings.bypass, RichText::new("BYPASS").size(13.))
|
||||
.on_hover_text("Crossfade to the delayed dry signal.");
|
||||
@@ -232,6 +236,7 @@ pub fn show(ui: &mut egui::Ui, settings: &mut Settings, state: &mut State) {
|
||||
pitch_from(ui, &mut mode, &mut settings.pitch);
|
||||
settings.source = Source::from_mode(mode);
|
||||
});
|
||||
state.advanced = advanced;
|
||||
ui.add_space(6.);
|
||||
let frame = state.analyzer;
|
||||
ui.horizontal_top(|ui| {
|
||||
@@ -247,15 +252,24 @@ pub fn show(ui: &mut egui::Ui, settings: &mut Settings, state: &mut State) {
|
||||
});
|
||||
});
|
||||
ui.add_space(6.);
|
||||
panel(ui, |ui| {
|
||||
ui.set_width(ui.available_width());
|
||||
ui.vertical(|ui| controls(ui, settings, state));
|
||||
});
|
||||
ui.add_space(6.);
|
||||
panel(ui, |ui| {
|
||||
ui.set_width(ui.available_width());
|
||||
kick_panel(ui, settings, state);
|
||||
});
|
||||
if state.advanced {
|
||||
panel(ui, |ui| {
|
||||
ui.set_width(ui.available_width());
|
||||
ui.vertical(|ui| controls(ui, settings, state));
|
||||
});
|
||||
ui.add_space(6.);
|
||||
panel(ui, |ui| {
|
||||
ui.set_width(ui.available_width());
|
||||
kick_panel(ui, settings, state);
|
||||
});
|
||||
} else {
|
||||
let height = ui.available_height() - 64.;
|
||||
panel(ui, |ui| {
|
||||
ui.set_width(ui.available_width());
|
||||
ui.set_min_height(height);
|
||||
ui.vertical(|ui| main_controls(ui, settings, state));
|
||||
});
|
||||
}
|
||||
ui.separator();
|
||||
ui.label(RichText::new(&state.help).color(MUTED));
|
||||
}
|
||||
@@ -264,6 +278,7 @@ fn note_panel(ui: &mut egui::Ui, settings: &Settings, frame: &AnalyzerFrame) {
|
||||
caption(ui, "NOTE");
|
||||
let (name, hz) = match frame.note {
|
||||
Some(n) if frame.hz > 0. => (common_dsp::note_name(n), format!("{:.1} Hz", frame.hz)),
|
||||
_ if frame.neutral => ("?".into(), "unsure: original sub plays".into()),
|
||||
_ => ("—".into(), "no note".into()),
|
||||
};
|
||||
ui.label(
|
||||
@@ -286,6 +301,131 @@ fn note_panel(ui: &mut egui::Ui, settings: &Settings, frame: &AnalyzerFrame) {
|
||||
.color(MUTED),
|
||||
);
|
||||
}
|
||||
/// The main page: what happens to the original sub, where the sub ends and how loud the
|
||||
/// new one plays.
|
||||
fn main_controls(ui: &mut egui::Ui, settings: &mut Settings, state: &mut State) {
|
||||
let defaults = Settings::default();
|
||||
ui.add_space(24.);
|
||||
ui.columns(3, |c| {
|
||||
c[0].vertical_centered(|ui| {
|
||||
caption(ui, "MODE");
|
||||
ui.add_space(6.);
|
||||
ui.spacing_mut().item_spacing.x = 8.;
|
||||
let r = choice(
|
||||
ui,
|
||||
&mut settings.mode,
|
||||
&[
|
||||
(
|
||||
Mode::Replace,
|
||||
"REPLACE",
|
||||
"Swap the sub below Split for a clean sine: one level and phase on every note.",
|
||||
),
|
||||
(
|
||||
Mode::Enhance,
|
||||
"ENHANCE",
|
||||
"Keep the original sub and add the clean sine under it for weight.",
|
||||
),
|
||||
],
|
||||
);
|
||||
if let Some(r) = r {
|
||||
hint(&r, state, "Replace swaps the sub; Enhance keeps it and adds weight.");
|
||||
}
|
||||
ui.add_space(14.);
|
||||
let text = match settings.mode {
|
||||
Mode::Replace => {
|
||||
"The band below Split is replaced by a clean sine on the played note. \
|
||||
Every note gets the same level and starts at the same phase."
|
||||
}
|
||||
Mode::Enhance => {
|
||||
"Your sub stays as it is. A clean sine on the played note is added \
|
||||
under it. Try Octave −1 in Advanced for a sub-octave."
|
||||
}
|
||||
};
|
||||
ui.label(RichText::new(text).size(13.).color(MUTED));
|
||||
ui.add_space(10.);
|
||||
ui.label(
|
||||
RichText::new(
|
||||
"When the note is unclear, Anvil plays your original sub instead of guessing.",
|
||||
)
|
||||
.size(12.)
|
||||
.color(DIM),
|
||||
);
|
||||
});
|
||||
knob(
|
||||
&mut c[1],
|
||||
state,
|
||||
"Split",
|
||||
&mut settings.split,
|
||||
40. ..=200.,
|
||||
defaults.split,
|
||||
" Hz",
|
||||
120.,
|
||||
"Set it just above the sub. Below it the sub is replaced (or enhanced).",
|
||||
);
|
||||
knob(
|
||||
&mut c[2],
|
||||
state,
|
||||
"Sub level",
|
||||
&mut settings.level,
|
||||
-30. ..=0.,
|
||||
defaults.level,
|
||||
" dBFS",
|
||||
120.,
|
||||
"Peak level of the new sub. Every note gets exactly this level.",
|
||||
);
|
||||
});
|
||||
ui.add_space(28.);
|
||||
ui.separator();
|
||||
ui.add_space(12.);
|
||||
ui.columns(4, |c| {
|
||||
let mut octave = settings.octave as f32;
|
||||
knob(
|
||||
&mut c[0],
|
||||
state,
|
||||
"Octave",
|
||||
&mut octave,
|
||||
-2. ..=1.,
|
||||
0.,
|
||||
"",
|
||||
64.,
|
||||
"Move the new sub up or down an octave against the played note.",
|
||||
);
|
||||
settings.octave = octave.round() as i32;
|
||||
knob(
|
||||
&mut c[1],
|
||||
state,
|
||||
"Ghost",
|
||||
&mut settings.ghost,
|
||||
0. ..=100.,
|
||||
defaults.ghost,
|
||||
" %",
|
||||
64.,
|
||||
"Lets phones and laptops hear the sub note: adds its harmonics the patch lacks.",
|
||||
);
|
||||
knob(
|
||||
&mut c[2],
|
||||
state,
|
||||
"Keep",
|
||||
&mut settings.keep,
|
||||
0. ..=100.,
|
||||
defaults.keep,
|
||||
" %",
|
||||
64.,
|
||||
"Blend some of the original sub back under the new one (Replace mode).",
|
||||
);
|
||||
knob(
|
||||
&mut c[3],
|
||||
state,
|
||||
"Output",
|
||||
&mut settings.output,
|
||||
-12. ..=12.,
|
||||
defaults.output,
|
||||
" dB",
|
||||
64.,
|
||||
"Output trim.",
|
||||
);
|
||||
});
|
||||
}
|
||||
fn controls(ui: &mut egui::Ui, settings: &mut Settings, state: &mut State) {
|
||||
let defaults = Settings::default();
|
||||
ui.columns(6, |c| {
|
||||
@@ -445,6 +585,26 @@ fn controls(ui: &mut egui::Ui, settings: &mut Settings, state: &mut State) {
|
||||
"SNAP TO SEMITONE",
|
||||
"Round the detected pitch. Turn off for slides and pitch bends.",
|
||||
);
|
||||
ui.separator();
|
||||
caption(ui, "MODE");
|
||||
for (mode, label, help) in [
|
||||
(
|
||||
Mode::Replace,
|
||||
"REPLACE",
|
||||
"Swap the sub below Split for the clean sine.",
|
||||
),
|
||||
(
|
||||
Mode::Enhance,
|
||||
"ENHANCE",
|
||||
"Keep the original sub and add the sine under it.",
|
||||
),
|
||||
] {
|
||||
let r = ui.selectable_label(settings.mode == mode, RichText::new(label).size(13.));
|
||||
if r.clicked() {
|
||||
settings.mode = mode;
|
||||
}
|
||||
hint(&r, state, help);
|
||||
}
|
||||
});
|
||||
}
|
||||
fn kick_panel(ui: &mut egui::Ui, settings: &mut Settings, state: &mut State) {
|
||||
|
||||
Reference in new issue
Block a user