Add a start of a gain reduction display
This needs to be drawn as a single mesh instead, and it should also use the target curve or it looks odd.
This commit is contained in:
parent
48d8ff9275
commit
6e3d2379b0
|
@ -34,10 +34,10 @@ pub struct AnalyzerData {
|
|||
/// 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 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.
|
||||
pub gain_reduction_db: [f32; crate::MAX_WINDOW_SIZE / 2 + 1],
|
||||
/// The gain different applied to each band, in decibels. Alternatively, the negative gain
|
||||
/// reduction. 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.
|
||||
pub gain_difference_db: [f32; crate::MAX_WINDOW_SIZE / 2 + 1],
|
||||
// TODO: Include the threshold curve. Decide on whether to only visualizer the 'global'
|
||||
// threshold curve or to also show the individual upwards/downwards thresholds. Or omit
|
||||
// this and implement it in a nicer way for the premium Spectral Compressor.
|
||||
|
@ -48,7 +48,7 @@ impl Default for AnalyzerData {
|
|||
Self {
|
||||
num_bins: 0,
|
||||
envelope_followers: [0.0; crate::MAX_WINDOW_SIZE / 2 + 1],
|
||||
gain_reduction_db: [0.0; crate::MAX_WINDOW_SIZE / 2 + 1],
|
||||
gain_difference_db: [0.0; crate::MAX_WINDOW_SIZE / 2 + 1],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -560,9 +560,9 @@ impl CompressorBank {
|
|||
) {
|
||||
nih_debug_assert_eq!(buffer.len(), self.log2_freqs.len());
|
||||
|
||||
// The gain reduction amounts are accumulated in `self.analyzer_input_data`. When processing
|
||||
// the last channel, this data is divided by the channel count, the envelope follower data
|
||||
// is added, and the data is then sent to the editor so it can be displayed.
|
||||
// The gain difference/reduction amounts are accumulated in `self.analyzer_input_data`. When
|
||||
// processing the last channel, this data is divided by the channel count, the envelope
|
||||
// follower data is added, and the data is then sent to the editor so it can be displayed.
|
||||
// `analyzer_input_data` contains excess capacity so it can handle any supported window
|
||||
// size, so all operations on it are limited to the actual number of used bins.
|
||||
let num_bins = buffer.len();
|
||||
|
@ -573,7 +573,7 @@ impl CompressorBank {
|
|||
// just been opened. If this doesn't look too obvious or too jarring this is
|
||||
// probably worth letting it be like this.
|
||||
let analyzer_input_data = self.analyzer_input_data.input_buffer();
|
||||
analyzer_input_data.gain_reduction_db[..num_bins].fill(0.0);
|
||||
analyzer_input_data.gain_difference_db[..num_bins].fill(0.0);
|
||||
}
|
||||
|
||||
self.update_if_needed(params);
|
||||
|
@ -605,8 +605,8 @@ impl CompressorBank {
|
|||
|
||||
// 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] {
|
||||
*gain_reduction_db *= channel_multiplier;
|
||||
for gain_difference_db in &mut analyzer_input_data.gain_difference_db[..num_bins] {
|
||||
*gain_difference_db *= channel_multiplier;
|
||||
}
|
||||
|
||||
// The spectrum analyzer data has not yet been added
|
||||
|
@ -784,7 +784,7 @@ impl CompressorBank {
|
|||
let downwards_knee_width_db = params.compressors.downwards.knee_width_db.value();
|
||||
let upwards_knee_width_db = params.compressors.upwards.knee_width_db.value();
|
||||
|
||||
assert!(analyzer_input_data.gain_reduction_db.len() >= buffer.len());
|
||||
assert!(analyzer_input_data.gain_difference_db.len() >= buffer.len());
|
||||
assert!(self.downwards_thresholds_db.len() == buffer.len());
|
||||
assert!(self.downwards_ratios.len() == buffer.len());
|
||||
assert!(self.downwards_knee_parabola_scale.len() == buffer.len());
|
||||
|
@ -849,14 +849,15 @@ impl CompressorBank {
|
|||
|
||||
// If the comprssed output is -10 dBFS and the envelope follower was at -6 dBFS, then we
|
||||
// want to apply -4 dB of gain to the bin
|
||||
let gain_reduction_db = downwards_compressed + upwards_compressed - (envelope_db * 2.0);
|
||||
let gain_difference_db =
|
||||
downwards_compressed + upwards_compressed - (envelope_db * 2.0);
|
||||
unsafe {
|
||||
*analyzer_input_data
|
||||
.gain_reduction_db
|
||||
.get_unchecked_mut(bin_idx) += gain_reduction_db;
|
||||
.gain_difference_db
|
||||
.get_unchecked_mut(bin_idx) += gain_difference_db;
|
||||
}
|
||||
|
||||
*bin *= util::db_to_gain_fast(gain_reduction_db);
|
||||
*bin *= util::db_to_gain_fast(gain_difference_db);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -885,7 +886,7 @@ impl CompressorBank {
|
|||
let other_channels_t = params.threshold.sc_channel_link.value() / num_channels;
|
||||
let this_channel_t = 1.0 - (other_channels_t * (num_channels - 1.0));
|
||||
|
||||
assert!(analyzer_input_data.gain_reduction_db.len() >= buffer.len());
|
||||
assert!(analyzer_input_data.gain_difference_db.len() >= buffer.len());
|
||||
assert!(self.sidechain_spectrum_magnitudes[channel_idx].len() == buffer.len());
|
||||
assert!(self.downwards_thresholds_db.len() == buffer.len());
|
||||
assert!(self.downwards_ratios.len() == buffer.len());
|
||||
|
@ -966,14 +967,15 @@ impl CompressorBank {
|
|||
|
||||
// If the comprssed output is -10 dBFS and the envelope follower was at -6 dBFS, then we
|
||||
// want to apply -4 dB of gain to the bin
|
||||
let gain_reduction_db = downwards_compressed + upwards_compressed - (envelope_db * 2.0);
|
||||
let gain_difference_db =
|
||||
downwards_compressed + upwards_compressed - (envelope_db * 2.0);
|
||||
unsafe {
|
||||
*analyzer_input_data
|
||||
.gain_reduction_db
|
||||
.get_unchecked_mut(bin_idx) += gain_reduction_db;
|
||||
.gain_difference_db
|
||||
.get_unchecked_mut(bin_idx) += gain_difference_db;
|
||||
}
|
||||
|
||||
*bin *= util::db_to_gain_fast(gain_reduction_db);
|
||||
*bin *= util::db_to_gain_fast(gain_difference_db);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -64,29 +64,35 @@ impl View for Analyzer {
|
|||
return;
|
||||
}
|
||||
|
||||
// This only covers the style rules we're actually setting. Right now this doesn't support
|
||||
// backgrounds.
|
||||
let opacity = cx.opacity();
|
||||
// This only covers the style rules we're actually using
|
||||
let border_width = match cx.border_width().unwrap_or_default() {
|
||||
Units::Pixels(val) => val,
|
||||
Units::Percentage(val) => bounds.w.min(bounds.h) * (val / 100.0),
|
||||
_ => 0.0,
|
||||
};
|
||||
let mut border_color: vg::Color = cx.border_color().cloned().unwrap_or_default().into();
|
||||
border_color.set_alphaf(border_color.a * opacity);
|
||||
let border_color: vg::Color = cx.border_color().cloned().unwrap_or_default().into();
|
||||
|
||||
// Used for the spectrum analyzer lines
|
||||
let line_width = cx.style.dpi_factor as f32 * 1.5;
|
||||
let text_color: vg::Color = cx.font_color().cloned().unwrap_or_default().into();
|
||||
let spectrum_paint = vg::Paint::color(text_color).with_line_width(line_width);
|
||||
// Used for the gain reduction bars. Lighter and semitransparent to make it stand out
|
||||
// against the spectrum analyzer
|
||||
let bar_paint_color = vg::Color::rgbaf(0.7, 0.9, 1.0, 0.7);
|
||||
let bar_paint = vg::Paint::color(bar_paint_color);
|
||||
|
||||
// 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 bin_frequency = |bin_idx: f32| (bin_idx / analyzer_data.num_bins as f32) * nyquist;
|
||||
|
||||
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
|
||||
// TODO: Draw individual bars until the difference between the next two bars becomes less
|
||||
// than one pixel. At that point draw it as a single mesh to get rid of aliasing.
|
||||
for (bin_idx, (magnetude, gain_difference_db)) in analyzer_data
|
||||
.envelope_followers
|
||||
.iter()
|
||||
.zip(analyzer_data.gain_reduction_db.iter())
|
||||
.zip(analyzer_data.gain_difference_db.iter())
|
||||
.enumerate()
|
||||
{
|
||||
// We'll show the bins from 30 Hz (to your chest) to 22 kHz, scaled logarithmically
|
||||
|
@ -94,30 +100,59 @@ impl View for Analyzer {
|
|||
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;
|
||||
{
|
||||
let ln_frequency = bin_frequency(bin_idx as f32).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, &spectrum_paint);
|
||||
}
|
||||
|
||||
// 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: Draw this as a single mesh instead, this doesn't work.
|
||||
// Avoid drawing tiny slivers for low gain reduction values
|
||||
if gain_difference_db.abs() > 0.2 {
|
||||
// The gain reduction bars are drawn width the width of the bin, centered on the
|
||||
// bin's center frequency
|
||||
let gr_start_ln_frequency = bin_frequency(bin_idx as f32 - 0.5).ln();
|
||||
let gr_end_ln_frequency = bin_frequency(bin_idx as f32 + 0.5).ln();
|
||||
|
||||
let t_start = ((gr_start_ln_frequency - LN_40_HZ) / LN_FREQ_RANGE).max(0.0);
|
||||
let t_end = ((gr_end_ln_frequency - LN_40_HZ) / LN_FREQ_RANGE).min(1.0);
|
||||
|
||||
// For the bar's height we'll draw 0 dB of gain reduction as a flat line (except we
|
||||
// don't actually draw 0 dBs of GR because it looks glitchy, but that's besides the
|
||||
// point). 40 dB of gain reduction causes the bar to be drawn from the center all
|
||||
// the way to the bottom of the spectrum analyzer. 40 dB of additional gain causes
|
||||
// the bar to be drawn from the center all the way to the top of the graph.
|
||||
// NOTE: Y-coordinates go from top to bottom, hence the minus
|
||||
// TODO: The y-position should be relative to the target curve
|
||||
let t_y = ((-gain_difference_db + 40.0) / 80.0).clamp(0.0, 1.0);
|
||||
|
||||
let mut path = vg::Path::new();
|
||||
path.move_to(bounds.x + (bounds.w * t_start), bounds.y + (bounds.h * 0.5));
|
||||
path.line_to(bounds.x + (bounds.w * t_end), bounds.y + (bounds.h * 0.5));
|
||||
path.line_to(bounds.x + (bounds.w * t_end), bounds.y + (bounds.h * t_y));
|
||||
path.line_to(bounds.x + (bounds.w * t_start), bounds.y + (bounds.h * t_y));
|
||||
path.close();
|
||||
canvas.fill_path(&mut path, &bar_paint);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Display the frequency range below the graph
|
||||
|
@ -133,7 +168,6 @@ impl View for Analyzer {
|
|||
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();
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue