Feat: UI/UX and ducking compressor
Some checks failed
Deploy Website / deploy (push) Failing after 4m52s

This commit is contained in:
2026-02-24 02:57:27 +01:00
parent 7632bc76f7
commit f0de312d6b
24 changed files with 402 additions and 71 deletions

View File

@@ -163,6 +163,15 @@ fn render_visualizers(frame: &mut Frame, app: &App, area: Rect) {
render_spectrum(frame, app, spectrum_area);
}
fn viz_gain(data: &[f32], config: &crate::state::audio::AudioConfig) -> f32 {
if config.normalize_viz {
let peak = data.iter().fold(0.0_f32, |m, s| m.max(s.abs()));
if peak > 0.0001 { 1.0 / peak } else { 1.0 }
} else {
config.gain_boost
}
}
fn render_scope(frame: &mut Frame, app: &App, area: Rect) {
let theme = theme::get();
let block = Block::default()
@@ -173,9 +182,11 @@ fn render_scope(frame: &mut Frame, app: &App, area: Rect) {
let inner = block.inner(area);
frame.render_widget(block, area);
let gain = viz_gain(&app.metrics.scope, &app.audio.config);
let scope = Scope::new(&app.metrics.scope)
.orientation(Orientation::Horizontal)
.color(theme.meter.low);
.color(theme.meter.low)
.gain(gain);
frame.render_widget(scope, inner);
}
@@ -189,8 +200,16 @@ fn render_lissajous(frame: &mut Frame, app: &App, area: Rect) {
let inner = block.inner(area);
frame.render_widget(block, area);
let peak = app.metrics.scope.iter().chain(app.metrics.scope_right.iter())
.fold(0.0_f32, |m, s| m.max(s.abs()));
let gain = if app.audio.config.normalize_viz {
if peak > 0.0001 { 1.0 / peak } else { 1.0 }
} else {
app.audio.config.gain_boost
};
let lissajous = Lissajous::new(&app.metrics.scope, &app.metrics.scope_right)
.color(theme.meter.low);
.color(theme.meter.low)
.gain(gain);
frame.render_widget(lissajous, inner);
}
@@ -204,7 +223,13 @@ fn render_spectrum(frame: &mut Frame, app: &App, area: Rect) {
let inner = block.inner(area);
frame.render_widget(block, area);
let spectrum = Spectrum::new(&app.metrics.spectrum);
let gain = if app.audio.config.normalize_viz {
viz_gain(&app.metrics.spectrum, &app.audio.config)
} else {
1.0
};
let spectrum = Spectrum::new(&app.metrics.spectrum)
.gain(gain);
frame.render_widget(spectrum, inner);
}

View File

@@ -482,6 +482,15 @@ fn render_tile(
}
}
fn viz_gain(data: &[f32], config: &crate::state::audio::AudioConfig) -> f32 {
if config.normalize_viz {
let peak = data.iter().fold(0.0_f32, |m, s| m.max(s.abs()));
if peak > 0.0001 { 1.0 / peak } else { 1.0 }
} else {
config.gain_boost
}
}
fn render_scope(frame: &mut Frame, app: &App, area: Rect, orientation: Orientation) {
let theme = theme::get();
let block = Block::default()
@@ -490,9 +499,11 @@ fn render_scope(frame: &mut Frame, app: &App, area: Rect, orientation: Orientati
let inner = block.inner(area);
frame.render_widget(block, area);
let gain = viz_gain(&app.metrics.scope, &app.audio.config);
let scope = Scope::new(&app.metrics.scope)
.orientation(orientation)
.color(theme.meter.low);
.color(theme.meter.low)
.gain(gain);
frame.render_widget(scope, inner);
}
@@ -504,7 +515,13 @@ fn render_spectrum(frame: &mut Frame, app: &App, area: Rect) {
let inner = block.inner(area);
frame.render_widget(block, area);
let spectrum = Spectrum::new(&app.metrics.spectrum);
let gain = if app.audio.config.normalize_viz {
viz_gain(&app.metrics.spectrum, &app.audio.config)
} else {
1.0
};
let spectrum = Spectrum::new(&app.metrics.spectrum)
.gain(gain);
frame.render_widget(spectrum, inner);
}
@@ -516,8 +533,16 @@ fn render_lissajous(frame: &mut Frame, app: &App, area: Rect) {
let inner = block.inner(area);
frame.render_widget(block, area);
let peak = app.metrics.scope.iter().chain(app.metrics.scope_right.iter())
.fold(0.0_f32, |m, s| m.max(s.abs()));
let gain = if app.audio.config.normalize_viz {
if peak > 0.0001 { 1.0 / peak } else { 1.0 }
} else {
app.audio.config.gain_boost
};
let lissajous = Lissajous::new(&app.metrics.scope, &app.metrics.scope_right)
.color(theme.meter.low);
.color(theme.meter.low)
.gain(gain);
frame.render_widget(lissajous, inner);
}

View File

@@ -88,6 +88,18 @@ pub fn render(frame: &mut Frame, app: &App, link: &LinkState, area: Rect) {
focus == OptionsFocus::ShowLissajous,
&theme,
),
render_option_line(
"Gain boost",
&gain_boost_label(app.audio.config.gain_boost),
focus == OptionsFocus::GainBoost,
&theme,
),
render_option_line(
"Normalize",
if app.audio.config.normalize_viz { "On" } else { "Off" },
focus == OptionsFocus::NormalizeViz,
&theme,
),
render_option_line(
"Completion",
if app.ui.show_completion { "On" } else { "Off" },
@@ -354,9 +366,11 @@ fn option_description(focus: OptionsFocus) -> Option<&'static str> {
OptionsFocus::HueRotation => Some("Shift all theme colors by a hue angle"),
OptionsFocus::RefreshRate => Some("Lower values reduce CPU usage"),
OptionsFocus::RuntimeHighlight => Some("Highlight executed code spans during playback"),
OptionsFocus::ShowScope => Some("Oscilloscope on the engine page"),
OptionsFocus::ShowSpectrum => Some("Spectrum analyzer on the engine page"),
OptionsFocus::ShowScope => Some("Oscilloscope on the main view"),
OptionsFocus::ShowSpectrum => Some("Spectrum analyzer on the main view"),
OptionsFocus::ShowLissajous => Some("XY stereo phase scope (left vs right)"),
OptionsFocus::GainBoost => Some("Amplify scope and lissajous waveforms"),
OptionsFocus::NormalizeViz => Some("Auto-scale visualizations to fill the display"),
OptionsFocus::ShowCompletion => Some("Word completion popup in the editor"),
OptionsFocus::ShowPreview => Some("Step script preview on the sequencer grid"),
OptionsFocus::PerformanceMode => Some("Hide header and footer bars"),
@@ -386,6 +400,10 @@ fn render_description_line(desc: &str, theme: &ThemeColors) -> Line<'static> {
))
}
fn gain_boost_label(gain: f32) -> String {
format!("{:.0}x", gain)
}
fn render_readonly_line(label: &str, value: &str, value_style: Style, theme: &theme::ThemeColors) -> Line<'static> {
let label_style = Style::new().fg(theme.ui.text_muted);
let label_width = 20;