diff --git a/plugins/diopser/src/editor.rs b/plugins/diopser/src/editor.rs
index a99792f1..ff15f9e8 100644
--- a/plugins/diopser/src/editor.rs
+++ b/plugins/diopser/src/editor.rs
@@ -27,6 +27,7 @@ use self::button::SafeModeButton;
use crate::spectrum::SpectrumOutput;
use crate::{Diopser, DiopserParams};
+mod analyzer;
mod button;
mod xy_pad;
@@ -137,7 +138,9 @@ fn spectrum_analyzer(cx: &mut Context) {
VStack::new(cx, |cx| {
ZStack::new(cx, |cx| {
- Label::new(cx, "When I grow up, I want to be a spectrum analyzer!");
+ analyzer::SpectrumAnalyzer::new(cx, Data::spectrum, Data::sample_rate)
+ .width(Percentage(100.0))
+ .height(Percentage(100.0));
xy_pad::XyPad::new(
cx,
@@ -148,7 +151,6 @@ fn spectrum_analyzer(cx: &mut Context) {
.width(Percentage(100.0))
.height(Percentage(100.0));
})
- .child_space(Stretch(1.0))
.width(Percentage(100.0))
.background_color(DARK_GRAY)
.height(Pixels(SPECTRUM_ANALYZER_HEIGHT));
diff --git a/plugins/diopser/src/editor/analyzer.rs b/plugins/diopser/src/editor/analyzer.rs
new file mode 100644
index 00000000..e654fcdf
--- /dev/null
+++ b/plugins/diopser/src/editor/analyzer.rs
@@ -0,0 +1,109 @@
+// Diopser: a phase rotation plugin
+// Copyright (C) 2021-2022 Robbert van der Helm
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+use atomic_float::AtomicF32;
+use nih_plug::nih_debug_assert;
+use nih_plug::prelude::FloatRange;
+use nih_plug_vizia::vizia::prelude::*;
+use nih_plug_vizia::vizia::vg;
+use std::sync::atomic::Ordering;
+use std::sync::{Arc, Mutex};
+
+use crate::spectrum::SpectrumOutput;
+
+/// A very abstract spectrum analyzer. This draws the magnitude spectrum's bins as vertical lines
+/// with the same distirubtion as the filter frequency parmaeter..
+pub struct SpectrumAnalyzer {
+ spectrum: Arc>,
+ sample_rate: Arc,
+
+ /// The same range as that used by the filter frequency parameter. We'll use this to make sure
+ /// we draw the spectrum analyzer's ticks at locations that match the frequency parameter linked
+ /// to the X-Y pad's X-axis.
+ frequency_range: FloatRange,
+}
+
+impl SpectrumAnalyzer {
+ /// Creates a new [`SpectrumAnalyzer`]. The uses custom drawing.
+ pub fn new(
+ cx: &mut Context,
+ spectrum: LSpectrum,
+ sample_rate: LRate,
+ ) -> Handle
+ where
+ LSpectrum: Lens>>,
+ LRate: Lens>,
+ {
+ Self {
+ spectrum: spectrum.get(cx),
+ sample_rate: sample_rate.get(cx),
+
+ frequency_range: crate::filter_frequency_range(),
+ }
+ .build(
+ cx,
+ // This is an otherwise empty element only used for custom drawing
+ |_cx| (),
+ )
+ }
+}
+
+impl View for SpectrumAnalyzer {
+ fn element(&self) -> Option<&'static str> {
+ Some("spectrum-analyzer")
+ }
+
+ fn draw(&self, cx: &mut DrawContext, canvas: &mut Canvas) {
+ let bounds = cx.bounds();
+ if bounds.w == 0.0 || bounds.h == 0.0 {
+ return;
+ }
+
+ // This spectrum buffer is written to at the end of the process function when the editor is
+ // open
+ let mut spectrum = self.spectrum.lock().unwrap();
+ let spectrum = spectrum.read();
+ let nyquist = self.sample_rate.load(Ordering::Relaxed) / 2.0;
+
+ // This skips background and border drawing
+ let line_width = cx.style.dpi_factor as f32 * 1.5;
+ let paint = vg::Paint::color(cx.font_color().cloned().unwrap_or_default().into())
+ .with_line_width(line_width);
+ for (bin_idx, magnetude) in spectrum.iter().enumerate() {
+ // We'll match up the bin's x-coordinate with the filter frequency parameter
+ let frequency = (bin_idx as f32 / spectrum.len() as f32) * nyquist;
+ let t = self.frequency_range.normalize(frequency);
+ if !(0.0..=1.0).contains(&t) {
+ continue;
+ }
+
+ // Scale this so that 1.0/0 dBFS magnetude is at 80% of the height, the bars begin at
+ // -80 dBFS, and that the scaling is linear
+ nih_debug_assert!(*magnetude >= 0.0);
+ let magnetude_db = nih_plug::util::gain_to_db(*magnetude);
+ let height = ((magnetude_db + 80.0) / 100.0).clamp(0.0, 1.0);
+
+ let mut path = vg::Path::new();
+ path.move_to(
+ bounds.x + (bounds.w * t),
+ bounds.y + (bounds.h * (1.0 - height)),
+ );
+ path.line_to(bounds.x + (bounds.w * t), bounds.y + bounds.h);
+
+ canvas.stroke_path(&mut path, &paint);
+ }
+ }
+}
diff --git a/plugins/diopser/src/editor/theme.css b/plugins/diopser/src/editor/theme.css
index 437d9bff..7ed44f53 100644
--- a/plugins/diopser/src/editor/theme.css
+++ b/plugins/diopser/src/editor/theme.css
@@ -32,6 +32,10 @@ param-button.safe-mode:checked {
width: 170px;
}
+spectrum-analyzer {
+ color: #0a0a0a;
+}
+
xy-pad {
overflow: hidden;
}