diff --git a/src/editor.rs b/src/editor.rs index 9bc00da..7659bdc 100644 --- a/src/editor.rs +++ b/src/editor.rs @@ -3,6 +3,7 @@ use crate::ZoomMode; use atomic_float::AtomicF32; use nih_plug::prelude::{util, Editor}; use nih_plug_vizia::vizia::prelude::*; +use nih_plug_vizia::vizia::vg; use nih_plug_vizia::widgets::*; use nih_plug_vizia::{assets, create_vizia_editor, ViziaState, ViziaTheming}; use std::sync::atomic::Ordering; @@ -15,17 +16,12 @@ use cyma::{ visualizers::{Graph, Grid, Meter, UnitRuler}, }; -include!("gain_reduction_meter.rs"); - const METER_MIN: f32 = -32.0; const METER_MAX: f32 = 0.0; #[derive(Lens, Clone)] struct LambData { params: Arc, - // peak_meter: Arc, - gain_reduction_left: Arc, - gain_reduction_right: Arc, level_buffer: Arc>, gr_buffer: Arc>, } @@ -33,17 +29,11 @@ struct LambData { impl LambData { pub(crate) fn new( params: Arc, - // peak_meter: Arc, - gain_reduction_left: Arc, - gain_reduction_right: Arc, level_buffer: Arc>, gr_buffer: Arc>, ) -> Self { Self { params, - // peak_meter, - gain_reduction_left, - gain_reduction_right, level_buffer, gr_buffer, } @@ -62,9 +52,6 @@ pub(crate) fn default_state() -> Arc { pub(crate) fn create( params: Arc, - // peak_meter: Arc, - gain_reduction_left: Arc, - gain_reduction_right: Arc, level_buffer: Arc>, gr_buffer: Arc>, editor_state: Arc, @@ -79,9 +66,6 @@ pub(crate) fn create( LambData { params: params.clone(), - // peak_meter: peak_meter.clone(), - gain_reduction_left: gain_reduction_left.clone(), - gain_reduction_right: gain_reduction_right.clone(), level_buffer: level_buffer.clone(), gr_buffer: gr_buffer.clone(), } @@ -201,39 +185,15 @@ pub(crate) fn create( .width(Percentage(100.0)); // meters - VStack::new(cx, |cx| { - peak_graph(cx); - // Label::new(cx, "input level").class("fader-label"); - // PeakMeter::new( - // cx, - // LambData::peak_meter - // .map(|peak_meter| util::gain_to_db(peak_meter.load(Ordering::Relaxed))), - // Some(Duration::from_millis(600)), - // ); - Label::new(cx, "gain reduction left").class("fader-label"); - GainReductionMeter::new( - cx, - LambData::gain_reduction_left - .map(|gain_reduction_left| gain_reduction_left.load(Ordering::Relaxed)), - Some(Duration::from_millis(600)), - ) - .width(Percentage(100.0)); - Label::new(cx, "gain reduction right").class("fader-label"); - GainReductionMeter::new( - cx, - LambData::gain_reduction_right - .map(|gain_reduction_right| gain_reduction_right.load(Ordering::Relaxed)), - Some(Duration::from_millis(600)), - ) - .width(Percentage(100.0)); - }) // meters - .width(Percentage(100.0)) - // .height(Percentage(100.0)) - .height(Auto) - .class("center"); // meters + // VStack::new(cx, |cx| { + peak_graph(cx); + // }) // meters + // .width(Percentage(100.0)) + // .height(Auto) + // .class("center"); // meters }) // everything .width(Percentage(95.0)) - // .height(Percentage(95.0)) + // .height(Percentage(95.0)) .height(Auto) .left(Percentage(2.5)) .right(Percentage(2.5)) @@ -472,10 +432,9 @@ fn peak_graph(cx: &mut Context) { // .background_color(Color::rgba(160, 0, 0, 60)); }) .top(Pixels(13.0)) - // .height(Pixels(280.0)) - .height(Pixels(200.0)) - .width(Percentage(100.0)) - .col_between(Pixels(8.)) - .border_color(Color::rgb(80, 80, 80)) - .border_width(Pixels(1.)); + .height(Pixels(330.0)) + .width(Percentage(100.0)) + .col_between(Pixels(8.)) + .border_color(Color::rgb(80, 80, 80)) + .border_width(Pixels(1.)); } diff --git a/src/gain_reduction_meter.rs b/src/gain_reduction_meter.rs deleted file mode 100644 index d901c3f..0000000 --- a/src/gain_reduction_meter.rs +++ /dev/null @@ -1,233 +0,0 @@ -use nih_plug_vizia::vizia::vg; -use std::cell::Cell; -use std::time::Instant; - -/// The thickness of a tick inside of the peak meter's bar. -const TICK_WIDTH: f32 = 1.0; -/// The gap between individual ticks. -const TICK_GAP: f32 = 1.0; - -/// The decibel value corresponding to the very left of the bar. -const MIN_TICK: f32 = -24.0; -/// The decibel value corresponding to the very right of the bar. -const MAX_TICK: f32 = 0.0; -/// The ticks that will be shown beneath the peak meter's bar. The first value is shown as -/// -infinity, and at the last position we'll draw the `dBFS` string. -// const TEXT_TICKS: [i32; 6] = [-80, -60, -40, -20, 0, 12]; -// const TEXT_TICKS: [i32; 5] = [-24, -18, -12, -6, 0]; -const TEXT_TICKS: [i32; 9] = [-24, -21, -18, -15, -12, -9, -6, -3, 0]; - -/// A simple horizontal peak meter. -/// -/// TODO: There are currently no styling options at all -/// TODO: Vertical peak meter, this is just a proof of concept to fit the gain GUI example. -pub struct GainReductionMeter; - -/// The bar bit for the peak meter, manually drawn using vertical lines. -struct GainReductionMeterBar -where - L: Lens, - P: Lens, -{ - level_dbfs: L, - peak_dbfs: P, -} - -impl GainReductionMeter { - /// Creates a new [`GainReductionMeter`] for the given value in decibel, optionally holding the peak - /// value for a certain amount of time. - pub fn new(cx: &mut Context, level_dbfs: L, hold_time: Option) -> Handle - where - L: Lens, - { - Self.build(cx, |cx| { - // Now for something that may be illegal under some jurisdictions. If a hold time is - // given, then we'll build a new lens that always gives the held peak level for the - // current moment in time by mutating some values captured into the mapping closure. - let held_peak_value_db = Cell::new(f32::MIN); - let last_held_peak_value: Cell> = Cell::new(None); - let peak_dbfs = level_dbfs.map(move |level| -> f32 { - match hold_time { - Some(hold_time) => { - let mut peak_level = held_peak_value_db.get(); - let peak_time = last_held_peak_value.get(); - - let now = Instant::now(); - if *level >= peak_level - || peak_time.is_none() - || now > peak_time.unwrap() + hold_time - { - peak_level = *level; - held_peak_value_db.set(peak_level); - last_held_peak_value.set(Some(now)); - } - - peak_level - } - None => util::MINUS_INFINITY_DB, - } - }); - - GainReductionMeterBar { - level_dbfs, - peak_dbfs, - } - .build(cx, |_| {}) - .class("bar"); - - ZStack::new(cx, |cx| { - const WIDTH_PCT: f32 = 50.0; - for tick_db in TEXT_TICKS { - let tick_fraction = (tick_db as f32 - MIN_TICK) / (MAX_TICK - MIN_TICK); - let tick_pct = tick_fraction * 100.0; - - ZStack::new(cx, |cx| { - let first_tick = tick_db == TEXT_TICKS[0]; - let last_tick = tick_db == TEXT_TICKS[TEXT_TICKS.len() - 1]; - - if !last_tick { - // FIXME: This is not aligned to the pixel grid and some ticks will look - // blurry, is there a way to fix this? - Element::new(cx).class("ticks__tick"); - } - - if first_tick { - Label::new(cx, &MIN_TICK.to_string()) - .class("ticks__label") - .class("ticks__label--inf") - } else if last_tick { - // This is only inclued in the array to make positioning this easier - Label::new(cx, "0 dBFS") - .class("ticks__label") - .class("ticks__label--dbfs") - } else { - Label::new(cx, &tick_db.to_string()).class("ticks__label") - } - .overflow(Overflow::Visible); - }) - .height(Stretch(1.0)) - .left(Percentage(tick_pct - (WIDTH_PCT / 2.0))) - .width(Percentage(WIDTH_PCT)) - .child_left(Stretch(1.0)) - .child_right(Stretch(1.0)) - .overflow(Overflow::Visible); - } - }) - .class("ticks") - .overflow(Overflow::Visible); - }) - .overflow(Overflow::Visible) - } -} - -impl View for GainReductionMeter { - fn element(&self) -> Option<&'static str> { - Some("peak-meter") - } -} - -impl View for GainReductionMeterBar -where - L: Lens, - P: Lens, -{ - fn draw(&self, cx: &mut DrawContext, canvas: &mut Canvas) { - let level_dbfs = self.level_dbfs.get(cx); - let peak_dbfs = self.peak_dbfs.get(cx); - - // These basics are taken directly from the default implementation of this function - let bounds = cx.bounds(); - if bounds.w == 0.0 || bounds.h == 0.0 { - return; - } - - // TODO: It would be cool to allow the text color property to control the gradient here. For - // now we'll only support basic background colors and borders. - let background_color = cx.background_color(); - let border_color = cx.border_color(); - let opacity = cx.opacity(); - let mut background_color: vg::Color = background_color.into(); - background_color.set_alphaf(background_color.a * opacity); - let mut border_color: vg::Color = border_color.into(); - border_color.set_alphaf(border_color.a * opacity); - let border_width = cx.border_width(); - - let mut path = vg::Path::new(); - { - let x = bounds.x + border_width / 2.0; - let y = bounds.y + border_width / 2.0; - let w = bounds.w - border_width; - let h = bounds.h - border_width; - path.move_to(x, y); - path.line_to(x, y + h); - path.line_to(x + w, y + h); - path.line_to(x + w, y); - path.line_to(x, y); - path.close(); - } - - // Fill with background color - let paint = vg::Paint::color(background_color); - canvas.fill_path(&path, &paint); - - // And now for the fun stuff. We'll try to not overlap the border, but we'll draw that last - // just in case. - let bar_bounds = bounds.shrink(border_width / 2.0); - let bar_ticks_start_x = bar_bounds.left().floor() as i32; - let bar_ticks_end_x = bar_bounds.right().ceil() as i32; - - // NOTE: We'll scale this with the nearest integer DPI ratio. That way it will still look - // good at 2x scaling, and it won't look blurry at 1.x times scaling. - let dpi_scale = cx.logical_to_physical(1.0).floor().max(1.0); - let bar_tick_coordinates = (bar_ticks_start_x..bar_ticks_end_x) - .step_by(((TICK_WIDTH + TICK_GAP) * dpi_scale).round() as usize); - for tick_x in bar_tick_coordinates { - let tick_fraction = - (tick_x - bar_ticks_start_x) as f32 / (bar_ticks_end_x - bar_ticks_start_x) as f32; - let tick_db = (tick_fraction * (MAX_TICK - MIN_TICK)) + MIN_TICK; - if tick_db > level_dbfs { - break; - } - - // femtovg draws paths centered on these coordinates, so in order to be pixel perfect we - // need to account for that. Otherwise the ticks will be 2px wide instead of 1px. - let mut path = vg::Path::new(); - path.move_to(tick_x as f32 + (dpi_scale / 2.0), bar_bounds.top()); - path.line_to(tick_x as f32 + (dpi_scale / 2.0), bar_bounds.bottom()); - - let grayscale_color = 0.3 + ((1.0 - tick_fraction) * 0.5); - let mut paint = vg::Paint::color(vg::Color::rgbaf( - grayscale_color, - grayscale_color, - grayscale_color, - opacity, - )); - paint.set_line_width(TICK_WIDTH * dpi_scale); - canvas.stroke_path(&path, &paint); - } - - // Draw the hold peak value if the hold time option has been set - let db_to_x_coord = |db: f32| { - let tick_fraction = (db - MIN_TICK) / (MAX_TICK - MIN_TICK); - bar_ticks_start_x as f32 - + ((bar_ticks_end_x - bar_ticks_start_x) as f32 * tick_fraction).round() - }; - if (MIN_TICK..MAX_TICK).contains(&peak_dbfs) { - // femtovg draws paths centered on these coordinates, so in order to be pixel perfect we - // need to account for that. Otherwise the ticks will be 2px wide instead of 1px. - let peak_x = db_to_x_coord(peak_dbfs); - let mut path = vg::Path::new(); - path.move_to(peak_x + (dpi_scale / 2.0), bar_bounds.top()); - path.line_to(peak_x + (dpi_scale / 2.0), bar_bounds.bottom()); - - let mut paint = vg::Paint::color(vg::Color::rgbaf(0.3, 0.3, 0.3, opacity)); - paint.set_line_width(TICK_WIDTH * dpi_scale); - canvas.stroke_path(&path, &paint); - } - - // Draw border last - let mut paint = vg::Paint::color(border_color); - paint.set_line_width(border_width); - canvas.stroke_path(&path, &paint); - } -} diff --git a/src/lib.rs b/src/lib.rs index 3d4a52b..eee6f8c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,9 +15,6 @@ const MAX_SOUNDCARD_BUFFER_SIZE: usize = 32768; mod editor; -/// The time it takes for the peak meter to decay by 12 dB after switching to complete silence. -// const PEAK_METER_DECAY_MS: f64 = 150.0; - pub struct Lamb { params: Arc, dsp: Box, @@ -29,16 +26,6 @@ pub struct Lamb { /// sample rate sample_rate: f32, - /// Needed to normalize the peak meter's response based on the sample rate. - peak_meter_decay_weight: f32, - /// The current data for the peak meter. This is stored as an [`Arc`] so we can share it between - /// the GUI and the audio processing parts. If you have more state to share, then it's a good - /// idea to put all of that in a struct behind a single `Arc`. - /// - /// This is stored as voltage gain. - // peak_meter: Arc, - gain_reduction_left: Arc, - gain_reduction_right: Arc, // These buffers will hold the sample data for the visualizers. level_buffer: Arc>, @@ -49,11 +36,6 @@ impl Default for Lamb { Self { params: Arc::new(LambParams::default()), - peak_meter_decay_weight: 1.0, - // peak_meter: Arc::new(AtomicF32::new(util::MINUS_INFINITY_DB)), - gain_reduction_left: Arc::new(AtomicF32::new(0.0)), - gain_reduction_right: Arc::new(AtomicF32::new(0.0)), - dsp: dsp::LambRs::default_boxed(), accum_buffer: TempBuffer::default(), @@ -150,9 +132,6 @@ impl Plugin for Lamb { fn editor(&mut self, _async_executor: AsyncExecutor) -> Option> { editor::create( self.params.clone(), - // self.peak_meter.clone(), - self.gain_reduction_left.clone(), - self.gain_reduction_right.clone(), self.level_buffer.clone(), self.gr_buffer.clone(), self.params.editor_state.clone(), @@ -226,19 +205,6 @@ impl Plugin for Lamb { } if self.params.editor_state.is_open() { - self.gain_reduction_left.store( - self.dsp - .get_param(GAIN_REDUCTION_LEFT_PI) - .expect("no GR read") as f32, - std::sync::atomic::Ordering::Relaxed, - ); - self.gain_reduction_right.store( - self.dsp - .get_param(GAIN_REDUCTION_RIGHT_PI) - .expect("no GR read") as f32, - std::sync::atomic::Ordering::Relaxed, - ); - for i in 0..count as usize { self.gr_buffer .lock()