1
0
Fork 0

Visualize the envelope followers in SC

This commit is contained in:
Robbert van der Helm 2023-03-20 15:51:13 +01:00
parent 7c4ae32ba4
commit 3600951159
3 changed files with 63 additions and 9 deletions

View file

@ -25,12 +25,15 @@
/// current window size is 2048, then only the first `2048 / 2 + 1` elements in the arrays are used.
#[derive(Debug)]
pub struct AnalyzerData {
/// The number of used bins. This is part of the `AnalyzerData` since recomputing it in the
/// editor could result in a race condition.
pub num_bins: usize,
/// The amplitudes of all frequency bins in a windowed FFT of Spectral Compressor's output. Also
/// includes the DC offset bin which we don't draw, just to make this a bit less confusing.
///
/// This data is taken directly from the envelope followers, so it has the same rise and fall
/// time as what is used by the compressors.
pub spectrum: [f32; crate::MAX_WINDOW_SIZE / 2 + 1],
pub envelope_followers: [f32; crate::MAX_WINDOW_SIZE / 2 + 1],
/// The gain reduction applied to each band, in decibels. Positive values mean that a band
/// becomes louder, and negative values mean a band got attenuated. Does not (and should not)
/// factor in the output gain.
@ -43,7 +46,8 @@ pub struct AnalyzerData {
impl Default for AnalyzerData {
fn default() -> Self {
Self {
spectrum: [0.0; crate::MAX_WINDOW_SIZE / 2 + 1],
num_bins: 0,
envelope_followers: [0.0; crate::MAX_WINDOW_SIZE / 2 + 1],
gain_reduction_db: [0.0; crate::MAX_WINDOW_SIZE / 2 + 1],
}
}

View file

@ -600,6 +600,9 @@ impl CompressorBank {
if should_update_analyzer_data && channel_idx == num_channels - 1 {
let analyzer_input_data = self.analyzer_input_data.input_buffer();
// The editor needs to know about this too so it can draw the spectra correctly
analyzer_input_data.num_bins = num_bins;
// The gain reduction data needs to be averaged, see above
let channel_multiplier = (num_channels as f32).recip();
for gain_reduction_db in &mut analyzer_input_data.gain_reduction_db[..num_bins] {
@ -609,7 +612,7 @@ impl CompressorBank {
// The spectrum analyzer data has not yet been added
assert!(self.envelopes.len() == num_channels);
assert!(self.envelopes[0].len() >= num_bins);
for (bin_idx, spectrum_data) in analyzer_input_data.spectrum[..num_bins]
for (bin_idx, spectrum_data) in analyzer_input_data.envelope_followers[..num_bins]
.iter_mut()
.enumerate()
{

View file

@ -15,13 +15,16 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use atomic_float::AtomicF32;
use nih_plug::nih_debug_assert;
use nih_plug_vizia::vizia::prelude::*;
use nih_plug_vizia::vizia::vg;
use std::sync::atomic::Ordering;
use std::sync::{Arc, Mutex};
use crate::analyzer::AnalyzerData;
/// A very spectrum analyzer with an overlay for the gain reduction.
/// A very analyzer showing the envelope followers as a magnitude spectrum with an overlay for the
/// gain reduction.
pub struct Analyzer {
analyzer_data: Arc<Mutex<triple_buffer::Output<AnalyzerData>>>,
sample_rate: Arc<AtomicF32>,
@ -57,12 +60,12 @@ impl View for Analyzer {
fn draw(&self, cx: &mut DrawContext, canvas: &mut Canvas) {
let bounds = cx.bounds();
dbg!(bounds);
if bounds.w == 0.0 || bounds.h == 0.0 {
return;
}
// This only covers the style rules we're actually setting
// This only covers the style rules we're actually setting. Right now this doesn't support
// backgrounds.
let opacity = cx.opacity();
let border_width = match cx.border_width().unwrap_or_default() {
Units::Pixels(val) => val,
@ -72,7 +75,52 @@ impl View for Analyzer {
let mut border_color: vg::Color = cx.border_color().cloned().unwrap_or_default().into();
border_color.set_alphaf(border_color.a * opacity);
// TODO: Draw the spectrum analyzer
// The analyzer data is pulled directly from the spectral `CompressorBank`
let mut analyzer_data = self.analyzer_data.lock().unwrap();
let analyzer_data = analyzer_data.read();
let nyquist = self.sample_rate.load(Ordering::Relaxed) / 2.0;
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, gain_reduction_db)) in analyzer_data
.envelope_followers
.iter()
.zip(analyzer_data.gain_reduction_db.iter())
.enumerate()
{
// We'll show the bins from 30 Hz (to your chest) to 22 kHz, scaled logarithmically
const LN_40_HZ: f32 = 3.4011974; // 30.0f32.ln();
const LN_22_KHZ: f32 = 9.998797; // 22000.0f32.ln();
const LN_FREQ_RANGE: f32 = LN_22_KHZ - LN_40_HZ;
let frequency = (bin_idx as f32 / analyzer_data.num_bins as f32) * nyquist;
let ln_frequency = frequency.ln();
let t = (ln_frequency - LN_40_HZ) / LN_FREQ_RANGE;
if t <= 0.0 || t >= 1.0 {
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. This is the same scaling used in Diopser's
// spectrum analyzer.
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);
// TODO: Visualize the gain reduction
// TODO: Visualize the target curve
}
// TODO: Display the frequency range below the graph
// Draw the border last
let mut path = vg::Path::new();
@ -89,8 +137,7 @@ impl View for Analyzer {
path.close();
}
let mut paint = vg::Paint::color(border_color);
paint.set_line_width(border_width);
let paint = vg::Paint::color(border_color).with_line_width(border_width);
canvas.stroke_path(&mut path, &paint);
}
}