From 532c92fbe832a7e2dc7fa7c50f07bc9375c0c6e4 Mon Sep 17 00:00:00 2001 From: 223230 <223230@proton.me> Date: Tue, 30 Apr 2024 12:23:25 +0200 Subject: [PATCH 1/9] Add trait for range modifiers --- src/visualizers/mod.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/visualizers/mod.rs b/src/visualizers/mod.rs index 0531064..43d68d3 100644 --- a/src/visualizers/mod.rs +++ b/src/visualizers/mod.rs @@ -17,3 +17,9 @@ pub use oscilloscope::*; pub use spectrum_analyzer::*; pub use unit_ruler::*; pub use waveform::*; + +use nih_plug_vizia::vizia::binding::Res; + +pub trait RangeModifiers { + fn range(self, range: impl Res<(f32, f32)>) -> Self; +} From 0885af45d22ccdd03291cfe1175370a1c555eea3 Mon Sep 17 00:00:00 2001 From: 223230 <223230@proton.me> Date: Tue, 30 Apr 2024 12:24:55 +0200 Subject: [PATCH 2/9] Implement range modifier, bind on creation --- src/visualizers/graph.rs | 77 ++++++++++++++++++++------------- src/visualizers/grid.rs | 36 ++++++++++++--- src/visualizers/meter.rs | 37 +++++++++++++--- src/visualizers/oscilloscope.rs | 58 +++++++++++++++++-------- 4 files changed, 148 insertions(+), 60 deletions(-) diff --git a/src/visualizers/graph.rs b/src/visualizers/graph.rs index be7ec35..afb57d5 100644 --- a/src/visualizers/graph.rs +++ b/src/visualizers/graph.rs @@ -1,12 +1,7 @@ +use super::RangeModifiers; use crate::utils::{ValueScaling, VisualizerBuffer}; -use nih_plug_vizia::vizia::style::Length::Value; -use nih_plug_vizia::vizia::{ - binding::{Lens, LensExt, Res}, - context::{Context, DrawContext}, - vg, - view::{Canvas, Handle, View}, -}; +use nih_plug_vizia::vizia::{prelude::*, vg}; use std::sync::{Arc, Mutex}; /// Real-time graph displaying information that is stored inside a buffer @@ -33,11 +28,15 @@ where I: VisualizerBuffer + 'static, { buffer: L, - display_range: (f32, f32), + range: (f32, f32), scaling: ValueScaling, fill_from: f32, } +enum GraphEvents { + UpdateRange((f32, f32)), +} + impl Graph where L: Lens>>, @@ -46,17 +45,18 @@ where pub fn new( cx: &mut Context, buffer: L, - display_range: impl Res<(f32, f32)>, - scaling: impl Res, + range: impl Res<(f32, f32)> + Clone, + scaling: impl Res + Clone, ) -> Handle { - let range = display_range.get_val(cx); + let r = range.get_val(cx); Self { buffer, - display_range: range, + range: r, scaling: scaling.get_val(cx), - fill_from: range.0, + fill_from: r.0, } .build(cx, |_| {}) + .range(range) } } @@ -82,21 +82,17 @@ where let binding = self.buffer.get(cx); let ring_buf = &(binding.lock().unwrap()); - let mut peak = self.scaling.value_to_normalized( - ring_buf[0], - self.display_range.0, - self.display_range.1, - ); + let mut peak = self + .scaling + .value_to_normalized(ring_buf[0], self.range.0, self.range.1); stroke.move_to(x, y + h * (1. - peak)); for i in 1..ring_buf.len() { // Normalize peak value - peak = self.scaling.value_to_normalized( - ring_buf[i], - self.display_range.0, - self.display_range.1, - ); + peak = self + .scaling + .value_to_normalized(ring_buf[i], self.range.0, self.range.1); // Draw peak as a new point stroke.line_to( @@ -107,11 +103,7 @@ where let mut fill = stroke.clone(); let fill_from_n = 1.0 - - ValueScaling::Linear.value_to_normalized( - self.fill_from, - self.display_range.0, - self.display_range.1, - ); + - ValueScaling::Linear.value_to_normalized(self.fill_from, self.range.0, self.range.1); fill.line_to(x + w, y + h * fill_from_n); fill.line_to(x, y + h * fill_from_n); @@ -124,6 +116,15 @@ where &vg::Paint::color(cx.font_color().into()).with_line_width(line_width), ); } + fn event( + &mut self, + _cx: &mut nih_plug_vizia::vizia::context::EventContext, + event: &mut nih_plug_vizia::vizia::events::Event, + ) { + event.map(|e, _| match e { + GraphEvents::UpdateRange(v) => self.range = *v, + }); + } } pub trait GraphModifiers { @@ -172,9 +173,9 @@ where fn should_fill_from_top(self, fill_from_top: bool) -> Self { self.modify(|graph| { graph.fill_from = if fill_from_top { - graph.display_range.1 + graph.range.1 } else { - graph.display_range.0 + graph.range.0 }; }) } @@ -184,3 +185,19 @@ where }) } } + +impl<'a, L, I> RangeModifiers for Handle<'a, Graph> +where + L: Lens>>, + I: VisualizerBuffer + 'static, +{ + fn range(mut self, range: impl Res<(f32, f32)>) -> Self { + let e = self.entity(); + + range.set_or_bind(self.context(), e, move |cx, r| { + (*cx).emit_to(e, GraphEvents::UpdateRange(r.clone())); + }); + + self + } +} diff --git a/src/visualizers/grid.rs b/src/visualizers/grid.rs index ac4e7b9..c9b4340 100644 --- a/src/visualizers/grid.rs +++ b/src/visualizers/grid.rs @@ -1,13 +1,9 @@ -use nih_plug_vizia::vizia::{ - binding::Res, - context::{Context, DrawContext}, - vg, - view::{Canvas, Handle, View}, - views::Orientation, -}; +use nih_plug_vizia::vizia::{prelude::*, vg}; use crate::utils::ValueScaling; +use super::RangeModifiers; + /// Generic grid backdrop that displays either horizontal or vertical lines. /// /// Put this grid inside a ZStack, along with your visualizer of choice. @@ -43,6 +39,10 @@ pub struct Grid { orientation: Orientation, } +enum GridEvents { + UpdateRange((f32, f32)), +} + impl Grid { pub fn new( cx: &mut Context, @@ -58,6 +58,7 @@ impl Grid { orientation, } .build(cx, |_| {}) + .range(range) } } @@ -115,4 +116,25 @@ impl View for Grid { &vg::Paint::color(cx.font_color().into()).with_line_width(line_width), ); } + fn event( + &mut self, + _cx: &mut nih_plug_vizia::vizia::context::EventContext, + event: &mut nih_plug_vizia::vizia::events::Event, + ) { + event.map(|e, _| match e { + GridEvents::UpdateRange(v) => self.range = *v, + }); + } +} + +impl<'a> RangeModifiers for Handle<'a, Grid> { + fn range(mut self, range: impl Res<(f32, f32)>) -> Self { + let e = self.entity(); + + range.set_or_bind(self.context(), e, move |cx, r| { + (*cx).emit_to(e, GridEvents::UpdateRange(r.clone())); + }); + + self + } } diff --git a/src/visualizers/meter.rs b/src/visualizers/meter.rs index d80d925..a2ace6b 100644 --- a/src/visualizers/meter.rs +++ b/src/visualizers/meter.rs @@ -2,6 +2,7 @@ use std::sync::{Arc, Mutex}; use nih_plug_vizia::vizia::{prelude::*, vg}; +use super::RangeModifiers; use crate::utils::ValueScaling; use crate::utils::VisualizerBuffer; @@ -29,7 +30,7 @@ where I: VisualizerBuffer + 'static, { buffer: L, - display_range: (f32, f32), + range: (f32, f32), scaling: ValueScaling, orientation: Orientation, } @@ -42,20 +43,25 @@ where pub fn new( cx: &mut Context, buffer: L, - display_range: impl Res<(f32, f32)>, + range: impl Res<(f32, f32)>, scaling: impl Res, orientation: Orientation, ) -> Handle { Self { buffer, - display_range: display_range.get_val(cx), + range: range.get_val(cx), scaling: scaling.get_val(cx), orientation, } .build(cx, |_| {}) + .range(range) } } +enum MeterEvents { + UpdateRange((f32, f32)), +} + impl View for Meter where L: Lens>>, @@ -77,8 +83,8 @@ where let level = self.scaling.value_to_normalized( ring_buf[ring_buf.len() - 1], - self.display_range.0, - self.display_range.1, + self.range.0, + self.range.1, ); let mut path = vg::Path::new(); @@ -113,4 +119,25 @@ where } }; } + fn event(&mut self, _cx: &mut EventContext, event: &mut Event) { + event.map(|e, _| match e { + MeterEvents::UpdateRange(v) => self.range = *v, + }); + } +} + +impl<'a, L, I> RangeModifiers for Handle<'a, Meter> +where + L: Lens>>, + I: VisualizerBuffer + 'static, +{ + fn range(mut self, range: impl Res<(f32, f32)>) -> Self { + let e = self.entity(); + + range.set_or_bind(self.context(), e, move |cx, r| { + (*cx).emit_to(e, MeterEvents::UpdateRange(r.clone())); + }); + + self + } } diff --git a/src/visualizers/oscilloscope.rs b/src/visualizers/oscilloscope.rs index 1e2ab3a..1a2f477 100644 --- a/src/visualizers/oscilloscope.rs +++ b/src/visualizers/oscilloscope.rs @@ -2,6 +2,7 @@ use std::sync::{Arc, Mutex}; use nih_plug_vizia::vizia::{prelude::*, vg}; +use super::RangeModifiers; use crate::utils::{ValueScaling, VisualizerBuffer, WaveformBuffer}; /// Waveform display for real-time input. @@ -29,16 +30,20 @@ where B: Lens>>, { buffer: B, - display_range: (f32, f32), + range: (f32, f32), scaling: ValueScaling, } +enum OscilloscopeEvents { + UpdateRange((f32, f32)), +} + impl Oscilloscope where B: Lens>>, { /// Creates a new Oscilloscope. - /// + /// /// Takes in a `buffer`, which should be used to store the peak values. You /// need to write to it inside your plugin code, thread-safely send it to /// the editor thread, and then pass it into this oscilloscope. Which is @@ -46,15 +51,16 @@ where pub fn new( cx: &mut Context, buffer: B, - display_range: impl Res<(f32, f32)>, + range: impl Res<(f32, f32)>, scaling: impl Res, ) -> Handle { Self { buffer, - display_range: display_range.get_val(cx), + range: range.get_val(cx), scaling: scaling.get_val(cx), } .build(cx, |_| {}) + .range(range) } } @@ -81,18 +87,14 @@ where let width_delta = w / ring_buf.len() as f32; // Local minima (bottom part of waveform) - let mut py = self.scaling.value_to_normalized( - ring_buf[0].0, - self.display_range.0, - self.display_range.1, - ); + let mut py = self + .scaling + .value_to_normalized(ring_buf[0].0, self.range.0, self.range.1); fill.move_to(x, y + h * (1. - py) + 1.); for i in 1..ring_buf.len() { - py = self.scaling.value_to_normalized( - ring_buf[i].0, - self.display_range.0, - self.display_range.1, - ); + py = self + .scaling + .value_to_normalized(ring_buf[i].0, self.range.0, self.range.1); fill.line_to(x + width_delta * i as f32, y + h * (1. - py) + 1.); } @@ -103,16 +105,16 @@ where // Local maxima (top part of waveform) py = self.scaling.value_to_normalized( ring_buf[ring_buf.len() - 1].1, - self.display_range.0, - self.display_range.1, + self.range.0, + self.range.1, ); fill.line_to(x + w, y + h * (1. - py) + 1.); top_stroke.move_to(x + w, y + h * (1. - py) + 1.); for i in 1..ring_buf.len() { py = self.scaling.value_to_normalized( ring_buf[ring_buf.len() - i].1, - self.display_range.0, - self.display_range.1, + self.range.0, + self.range.1, ); fill.line_to(x + w - width_delta * i as f32, y + h * (1. - py) + 1.); @@ -125,4 +127,24 @@ where &vg::Paint::color(cx.font_color().into()).with_line_width(0.), ); } + fn event(&mut self, cx: &mut EventContext, event: &mut Event) { + event.map(|e, _| match e { + OscilloscopeEvents::UpdateRange(v) => self.range = *v, + }); + } +} + +impl<'a, B> RangeModifiers for Handle<'a, Oscilloscope> +where + B: Lens>>, +{ + fn range(mut self, range: impl Res<(f32, f32)>) -> Self { + let e = self.entity(); + + range.set_or_bind(self.context(), e, move |cx, r| { + (*cx).emit_to(e, OscilloscopeEvents::UpdateRange(r.clone())); + }); + + self + } } From 7e03ec61a65a5b29aa2127e477d84372e8fe1d9b Mon Sep 17 00:00:00 2001 From: luna <223230@proton.me> Date: Mon, 6 May 2024 19:18:43 +0200 Subject: [PATCH 3/9] Add optional `value_to_normalized` This version of value_to_optimized checks whether the resulting value is in range and returns an option containing Some(value) if it is, and None if it isn't. --- src/utils/mod.rs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 913db05..bdc4eea 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -66,6 +66,32 @@ impl ValueScaling { } .clamp(0., 1.) } + + pub fn value_to_normalized_optional(&self, value: f32, min: f32, max: f32) -> Option { + let unmap = |x: f32| -> f32 { (x - min) / (max - min) }; + + let value = match self { + ValueScaling::Linear => unmap(value), + + ValueScaling::Power(exponent) => unmap(value).powf(1.0 / *exponent), + + ValueScaling::Frequency => { + let minl = min.log2(); + let range = max.log2() - minl; + (value.log2() - minl) / range + } + + ValueScaling::Decibels => unmap({ + const CONVERSION_FACTOR: f32 = std::f32::consts::LOG10_E * 20.0; + value.ln() * CONVERSION_FACTOR + }), + }; + if (0.0..=1.0).contains(&value) { + Some(value) + } else { + None + } + } } // We can't use impl_res_simple!() since we're using nih_plug's version of VIZIA From dd8f3afe6996a835272d62895bba1e73c2b6fb23 Mon Sep 17 00:00:00 2001 From: luna <223230@proton.me> Date: Mon, 6 May 2024 19:19:05 +0200 Subject: [PATCH 4/9] Discard values that are not in range --- src/visualizers/unit_ruler.rs | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/visualizers/unit_ruler.rs b/src/visualizers/unit_ruler.rs index 2282f19..5bfc7e7 100644 --- a/src/visualizers/unit_ruler.rs +++ b/src/visualizers/unit_ruler.rs @@ -31,23 +31,21 @@ pub struct UnitRuler {} impl UnitRuler { pub fn new<'a>( - cx: &mut Context, - display_range: impl Res<(f32, f32)>, + cx: &'a mut Context, + range: (f32, f32), scaling: ValueScaling, - values: impl Res>, + values: Vec<(f32, &'static str)>, orientation: Orientation, - ) -> Handle { + ) -> Handle<'a, Self> { Self {}.build(cx, |cx| { - let display_range = display_range.get_val(cx); let normalized_values = values - .get_val(cx) .into_iter() - .map(|v| { + .filter_map(|v| { // Normalize the value according to the provided scaling, within the provided range - ( - scaling.value_to_normalized(v.0, display_range.0, display_range.1), - v.1, - ) + scaling + .value_to_normalized_optional(v.0, range.0, range.1) + // If it is not in range, discard it by returning a `None`, which filter_map filters out + .map(|value| (value, v.1)) }) .collect::>(); ZStack::new(cx, |cx| { From 58a08d1be855972d6141176e0162d91b8d5096f9 Mon Sep 17 00:00:00 2001 From: luna <223230@proton.me> Date: Mon, 6 May 2024 19:34:38 +0200 Subject: [PATCH 5/9] Make graph fill from top, bottom or a specific value --- src/visualizers/graph.rs | 39 +++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/src/visualizers/graph.rs b/src/visualizers/graph.rs index afb57d5..144d72a 100644 --- a/src/visualizers/graph.rs +++ b/src/visualizers/graph.rs @@ -30,7 +30,13 @@ where buffer: L, range: (f32, f32), scaling: ValueScaling, - fill_from: f32, + fill_from: FillFrom, +} + +enum FillFrom { + Top, + Bottom, + Value(f32), } enum GraphEvents { @@ -53,7 +59,7 @@ where buffer, range: r, scaling: scaling.get_val(cx), - fill_from: r.0, + fill_from: FillFrom::Bottom, } .build(cx, |_| {}) .range(range) @@ -102,8 +108,13 @@ where } let mut fill = stroke.clone(); - let fill_from_n = 1.0 - - ValueScaling::Linear.value_to_normalized(self.fill_from, self.range.0, self.range.1); + let fill_from_n = match self.fill_from { + FillFrom::Top => 0.0, + FillFrom::Bottom => 1.0, + FillFrom::Value(val) => { + 1.0 - ValueScaling::Linear.value_to_normalized(val, self.range.0, self.range.1) + } + }; fill.line_to(x + w, y + h * fill_from_n); fill.line_to(x, y + h * fill_from_n); @@ -140,11 +151,11 @@ pub trait GraphModifiers { /// /// ``` /// Graph::new(cx, Data::gain_mult, (-32.0, 8.0), ValueScaling::Decibels) - /// .should_fill_from_top(true) + /// .fill_from_top() /// .color(Color::rgba(255, 0, 0, 160)) /// .background_color(Color::rgba(255, 0, 0, 60)); /// ``` - fn should_fill_from_top(self, fill_from_top: bool) -> Self; + fn fill_from_top(self) -> Self; /// Allows for the graph to be filled from any desired level. /// @@ -158,11 +169,11 @@ pub trait GraphModifiers { /// /// ``` /// Graph::new(cx, Data::gain_mult, (-32.0, 6.0), ValueScaling::Decibels) - /// .fill_from(1.0) + /// .fill_from(0.0) // Fills the graph from 0.0dB downwards /// .color(Color::rgba(255, 0, 0, 160)) /// .background_color(Color::rgba(255, 0, 0, 60)); /// ``` - fn fill_from(self, level: f32) -> Self; + fn fill_from_value(self, level: f32) -> Self; } impl<'a, L, I> GraphModifiers for Handle<'a, Graph> @@ -170,18 +181,14 @@ where L: Lens>>, I: VisualizerBuffer + 'static, { - fn should_fill_from_top(self, fill_from_top: bool) -> Self { + fn fill_from_top(self) -> Self { self.modify(|graph| { - graph.fill_from = if fill_from_top { - graph.range.1 - } else { - graph.range.0 - }; + graph.fill_from = FillFrom::Top; }) } - fn fill_from(self, level: f32) -> Self { + fn fill_from_value(self, level: f32) -> Self { self.modify(|graph| { - graph.fill_from = level; + graph.fill_from = FillFrom::Value(level); }) } } From 8153e3cc8c997b3196639e5666882f071c9bcd97 Mon Sep 17 00:00:00 2001 From: luna <223230@proton.me> Date: Mon, 6 May 2024 20:01:41 +0200 Subject: [PATCH 6/9] Turn fill_from modifiers into a crate-wide trait --- src/visualizers/graph.rs | 36 ++++++++++++------------------------ src/visualizers/mod.rs | 14 ++++++++++++++ 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/src/visualizers/graph.rs b/src/visualizers/graph.rs index 144d72a..7b337c9 100644 --- a/src/visualizers/graph.rs +++ b/src/visualizers/graph.rs @@ -1,4 +1,4 @@ -use super::RangeModifiers; +use super::{FillFrom, FillModifiers, RangeModifiers}; use crate::utils::{ValueScaling, VisualizerBuffer}; use nih_plug_vizia::vizia::{prelude::*, vg}; @@ -33,12 +33,6 @@ where fill_from: FillFrom, } -enum FillFrom { - Top, - Bottom, - Value(f32), -} - enum GraphEvents { UpdateRange((f32, f32)), } @@ -138,7 +132,11 @@ where } } -pub trait GraphModifiers { +impl<'a, L, I> FillModifiers for Handle<'a, Graph> +where + L: Lens>>, + I: VisualizerBuffer + 'static, +{ /// Allows for the graph to be filled from the top instead of the bottom. /// /// This is useful for certain graphs like gain reduction meters. @@ -151,12 +149,15 @@ pub trait GraphModifiers { /// /// ``` /// Graph::new(cx, Data::gain_mult, (-32.0, 8.0), ValueScaling::Decibels) - /// .fill_from_top() + /// .fill_from_max() /// .color(Color::rgba(255, 0, 0, 160)) /// .background_color(Color::rgba(255, 0, 0, 60)); /// ``` - fn fill_from_top(self) -> Self; - + fn fill_from_max(self) -> Self { + self.modify(|graph| { + graph.fill_from = FillFrom::Top; + }) + } /// Allows for the graph to be filled from any desired level. /// /// This is useful for certain graphs like gain reduction meters. @@ -173,19 +174,6 @@ pub trait GraphModifiers { /// .color(Color::rgba(255, 0, 0, 160)) /// .background_color(Color::rgba(255, 0, 0, 60)); /// ``` - fn fill_from_value(self, level: f32) -> Self; -} - -impl<'a, L, I> GraphModifiers for Handle<'a, Graph> -where - L: Lens>>, - I: VisualizerBuffer + 'static, -{ - fn fill_from_top(self) -> Self { - self.modify(|graph| { - graph.fill_from = FillFrom::Top; - }) - } fn fill_from_value(self, level: f32) -> Self { self.modify(|graph| { graph.fill_from = FillFrom::Value(level); diff --git a/src/visualizers/mod.rs b/src/visualizers/mod.rs index 43d68d3..872525a 100644 --- a/src/visualizers/mod.rs +++ b/src/visualizers/mod.rs @@ -23,3 +23,17 @@ use nih_plug_vizia::vizia::binding::Res; pub trait RangeModifiers { fn range(self, range: impl Res<(f32, f32)>) -> Self; } + +pub(crate) enum FillFrom { + Top, + Bottom, + Value(f32), +} + +pub trait FillModifiers { + /// Allows for the view to be filled from the max instead of the min value. + fn fill_from_max(self) -> Self; + + /// Allows for the view to be filled from any desired level. + fn fill_from_value(self, level: f32) -> Self; +} From 8f1d37ac79d2f570b6eb93790e3892e9d6bf0f64 Mon Sep 17 00:00:00 2001 From: luna <223230@proton.me> Date: Mon, 6 May 2024 20:08:14 +0200 Subject: [PATCH 7/9] Implement `FillModifiers` for the Meter --- src/visualizers/meter.rs | 84 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 78 insertions(+), 6 deletions(-) diff --git a/src/visualizers/meter.rs b/src/visualizers/meter.rs index a2ace6b..11b3061 100644 --- a/src/visualizers/meter.rs +++ b/src/visualizers/meter.rs @@ -1,8 +1,9 @@ use std::sync::{Arc, Mutex}; +use crate::prelude::FillModifiers; use nih_plug_vizia::vizia::{prelude::*, vg}; -use super::RangeModifiers; +use super::{FillFrom, RangeModifiers}; use crate::utils::ValueScaling; use crate::utils::VisualizerBuffer; @@ -32,6 +33,7 @@ where buffer: L, range: (f32, f32), scaling: ValueScaling, + fill_from: FillFrom, orientation: Orientation, } @@ -51,6 +53,7 @@ where buffer, range: range.get_val(cx), scaling: scaling.get_val(cx), + fill_from: FillFrom::Bottom, orientation, } .build(cx, |_| {}) @@ -97,8 +100,20 @@ where outline.close(); canvas.fill_path(&outline, &vg::Paint::color(cx.font_color().into())); - path.line_to(x + w, y + h); - path.line_to(x, y + h); + let fill_from_n = match self.fill_from { + FillFrom::Top => 0.0, + FillFrom::Bottom => 1.0, + FillFrom::Value(val) => { + 1.0 - ValueScaling::Linear.value_to_normalized( + val, + self.range.0, + self.range.1, + ) + } + }; + + path.line_to(x + w, y + h * fill_from_n); + path.line_to(x, y + h * fill_from_n); path.close(); canvas.fill_path(&path, &vg::Paint::color(cx.background_color().into())); @@ -111,8 +126,16 @@ where outline.close(); canvas.fill_path(&outline, &vg::Paint::color(cx.font_color().into())); - path.line_to(x, y + h); - path.line_to(x, y); + let fill_from_n = match self.fill_from { + FillFrom::Top => 1.0, + FillFrom::Bottom => 0.0, + FillFrom::Value(val) => { + ValueScaling::Linear.value_to_normalized(val, self.range.0, self.range.1) + } + }; + + path.line_to(x + w * fill_from_n, y + h); + path.line_to(x + w * fill_from_n, y); path.close(); canvas.fill_path(&path, &vg::Paint::color(cx.background_color().into())); @@ -126,6 +149,55 @@ where } } +impl<'a, L, I> FillModifiers for Handle<'a, Meter> +where + L: Lens>>, + I: VisualizerBuffer + 'static, +{ + /// Allows for the meter to be filled from the maximum instead of the minimum value. + /// + /// This is useful for certain meters like gain reduction meters. + /// + /// # Example + /// + /// Here's a gain reduction meter, which you could overlay on top of a peak meter. + /// + /// Here, `gain_mult` could be a [`MinimaBuffer`](crate::utils::MinimaBuffer). + /// + /// ``` + /// Meter::new(cx, Data::gain_mult, (-32.0, 8.0), ValueScaling::Decibels, Orientation::Vertical) + /// .fill_from_max() + /// .color(Color::rgba(255, 0, 0, 160)) + /// .background_color(Color::rgba(255, 0, 0, 60)); + /// ``` + fn fill_from_max(self) -> Self { + self.modify(|meter| { + meter.fill_from = FillFrom::Top; + }) + } + /// Allows for the meter to be filled from any desired level. + /// + /// This is useful for certain meters like gain reduction meters. + /// + /// # Example + /// + /// Here's a gain reduction meter, which you could overlay on top of a peak meter. + /// + /// Here, `gain_mult` could be a [`MinimaBuffer`](crate::utils::MinimaBuffer). + /// + /// ``` + /// Meter::new(cx, Data::gain_mult, (-32.0, 6.0), ValueScaling::Decibels, Orientation::Vertical) + /// .fill_from(0.0) // Fills the meter from 0.0dB downwards + /// .color(Color::rgba(255, 0, 0, 160)) + /// .background_color(Color::rgba(255, 0, 0, 60)); + /// ``` + fn fill_from_value(self, level: f32) -> Self { + self.modify(|meter| { + meter.fill_from = FillFrom::Value(level); + }) + } +} + impl<'a, L, I> RangeModifiers for Handle<'a, Meter> where L: Lens>>, @@ -135,7 +207,7 @@ where let e = self.entity(); range.set_or_bind(self.context(), e, move |cx, r| { - (*cx).emit_to(e, MeterEvents::UpdateRange(r.clone())); + (*cx).emit_to(e, MeterEvents::UpdateRange(r)); }); self From d2676a2faab637b2fe98d0e93a803895c0f5e83c Mon Sep 17 00:00:00 2001 From: luna <223230@proton.me> Date: Mon, 6 May 2024 20:11:17 +0200 Subject: [PATCH 8/9] Import `FillModifiers` from super --- src/visualizers/meter.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/visualizers/meter.rs b/src/visualizers/meter.rs index 11b3061..ed2d1fb 100644 --- a/src/visualizers/meter.rs +++ b/src/visualizers/meter.rs @@ -1,9 +1,8 @@ use std::sync::{Arc, Mutex}; -use crate::prelude::FillModifiers; use nih_plug_vizia::vizia::{prelude::*, vg}; -use super::{FillFrom, RangeModifiers}; +use super::{FillFrom, FillModifiers, RangeModifiers}; use crate::utils::ValueScaling; use crate::utils::VisualizerBuffer; From 7acebd229956de0fb7c374f05d452a3be9ac7b35 Mon Sep 17 00:00:00 2001 From: luna <223230@proton.me> Date: Mon, 6 May 2024 22:07:59 +0200 Subject: [PATCH 9/9] Add scaling to `RangeModifiers` to allow binding the scaling --- src/visualizers/graph.rs | 29 ++++++++++++++++++----------- src/visualizers/grid.rs | 18 +++++++++++++----- src/visualizers/meter.rs | 12 ++++++++++++ src/visualizers/mod.rs | 8 ++++++++ src/visualizers/oscilloscope.rs | 14 +++++++++++++- 5 files changed, 64 insertions(+), 17 deletions(-) diff --git a/src/visualizers/graph.rs b/src/visualizers/graph.rs index 7b337c9..4ac2d32 100644 --- a/src/visualizers/graph.rs +++ b/src/visualizers/graph.rs @@ -35,6 +35,7 @@ where enum GraphEvents { UpdateRange((f32, f32)), + UpdateScaling(ValueScaling), } impl Graph @@ -48,15 +49,15 @@ where range: impl Res<(f32, f32)> + Clone, scaling: impl Res + Clone, ) -> Handle { - let r = range.get_val(cx); Self { buffer, - range: r, + range: range.get_val(cx), scaling: scaling.get_val(cx), fill_from: FillFrom::Bottom, } .build(cx, |_| {}) .range(range) + .scaling(scaling) } } @@ -68,6 +69,12 @@ where fn element(&self) -> Option<&'static str> { Some("graph") } + fn event(&mut self, _cx: &mut EventContext, event: &mut Event) { + event.map(|e, _| match e { + GraphEvents::UpdateRange(v) => self.range = *v, + GraphEvents::UpdateScaling(s) => self.scaling = *s, + }); + } fn draw(&self, cx: &mut DrawContext, canvas: &mut Canvas) { let bounds = cx.bounds(); @@ -121,15 +128,6 @@ where &vg::Paint::color(cx.font_color().into()).with_line_width(line_width), ); } - fn event( - &mut self, - _cx: &mut nih_plug_vizia::vizia::context::EventContext, - event: &mut nih_plug_vizia::vizia::events::Event, - ) { - event.map(|e, _| match e { - GraphEvents::UpdateRange(v) => self.range = *v, - }); - } } impl<'a, L, I> FillModifiers for Handle<'a, Graph> @@ -193,6 +191,15 @@ where (*cx).emit_to(e, GraphEvents::UpdateRange(r.clone())); }); + self + } + fn scaling(mut self, scaling: impl Res) -> Self { + let e = self.entity(); + + scaling.set_or_bind(self.context(), e, move |cx, s| { + (*cx).emit_to(e, GraphEvents::UpdateScaling(s.clone())) + }); + self } } diff --git a/src/visualizers/grid.rs b/src/visualizers/grid.rs index c9b4340..3b27cf7 100644 --- a/src/visualizers/grid.rs +++ b/src/visualizers/grid.rs @@ -41,6 +41,7 @@ pub struct Grid { enum GridEvents { UpdateRange((f32, f32)), + UpdateScaling(ValueScaling), } impl Grid { @@ -59,6 +60,7 @@ impl Grid { } .build(cx, |_| {}) .range(range) + .scaling(scaling) } } @@ -116,13 +118,10 @@ impl View for Grid { &vg::Paint::color(cx.font_color().into()).with_line_width(line_width), ); } - fn event( - &mut self, - _cx: &mut nih_plug_vizia::vizia::context::EventContext, - event: &mut nih_plug_vizia::vizia::events::Event, - ) { + fn event(&mut self, _cx: &mut EventContext, event: &mut Event) { event.map(|e, _| match e { GridEvents::UpdateRange(v) => self.range = *v, + GridEvents::UpdateScaling(v) => self.scaling = *v, }); } } @@ -135,6 +134,15 @@ impl<'a> RangeModifiers for Handle<'a, Grid> { (*cx).emit_to(e, GridEvents::UpdateRange(r.clone())); }); + self + } + fn scaling(mut self, scaling: impl Res) -> Self { + let e = self.entity(); + + scaling.set_or_bind(self.context(), e, move |cx, s| { + (*cx).emit_to(e, GridEvents::UpdateScaling(s)); + }); + self } } diff --git a/src/visualizers/meter.rs b/src/visualizers/meter.rs index ed2d1fb..1f27636 100644 --- a/src/visualizers/meter.rs +++ b/src/visualizers/meter.rs @@ -57,11 +57,13 @@ where } .build(cx, |_| {}) .range(range) + .scaling(scaling) } } enum MeterEvents { UpdateRange((f32, f32)), + UpdateScaling(ValueScaling), } impl View for Meter @@ -144,6 +146,7 @@ where fn event(&mut self, _cx: &mut EventContext, event: &mut Event) { event.map(|e, _| match e { MeterEvents::UpdateRange(v) => self.range = *v, + MeterEvents::UpdateScaling(v) => self.scaling = *v, }); } } @@ -209,6 +212,15 @@ where (*cx).emit_to(e, MeterEvents::UpdateRange(r)); }); + self + } + fn scaling(mut self, scaling: impl Res) -> Self { + let e = self.entity(); + + scaling.set_or_bind(self.context(), e, move |cx, s| { + (*cx).emit_to(e, MeterEvents::UpdateScaling(s)); + }); + self } } diff --git a/src/visualizers/mod.rs b/src/visualizers/mod.rs index 872525a..58af505 100644 --- a/src/visualizers/mod.rs +++ b/src/visualizers/mod.rs @@ -18,10 +18,18 @@ pub use spectrum_analyzer::*; pub use unit_ruler::*; pub use waveform::*; +use super::utils::ValueScaling; use nih_plug_vizia::vizia::binding::Res; pub trait RangeModifiers { + /// Sets the minimum and maximum values that can be displayed by the view + /// + /// The values are relative to the scaling - e.g. for peak volume information, + /// `(-48., 6.)` would be -48 to +6 dB when the scaling is set to + /// [`ValueScaling::Decibels`] fn range(self, range: impl Res<(f32, f32)>) -> Self; + /// Specifies what scaling the view should use + fn scaling(self, scaling: impl Res) -> Self; } pub(crate) enum FillFrom { diff --git a/src/visualizers/oscilloscope.rs b/src/visualizers/oscilloscope.rs index 1a2f477..1462962 100644 --- a/src/visualizers/oscilloscope.rs +++ b/src/visualizers/oscilloscope.rs @@ -36,6 +36,7 @@ where enum OscilloscopeEvents { UpdateRange((f32, f32)), + UpdateScaling(ValueScaling), } impl Oscilloscope @@ -61,6 +62,7 @@ where } .build(cx, |_| {}) .range(range) + .scaling(scaling) } } @@ -130,6 +132,7 @@ where fn event(&mut self, cx: &mut EventContext, event: &mut Event) { event.map(|e, _| match e { OscilloscopeEvents::UpdateRange(v) => self.range = *v, + OscilloscopeEvents::UpdateScaling(v) => self.scaling = *v, }); } } @@ -142,7 +145,16 @@ where let e = self.entity(); range.set_or_bind(self.context(), e, move |cx, r| { - (*cx).emit_to(e, OscilloscopeEvents::UpdateRange(r.clone())); + (*cx).emit_to(e, OscilloscopeEvents::UpdateRange(r)); + }); + + self + } + fn scaling(mut self, scaling: impl Res) -> Self { + let e = self.entity(); + + scaling.set_or_bind(self.context(), e, move |cx, s| { + (*cx).emit_to(e, OscilloscopeEvents::UpdateScaling(s)); }); self