From bf4ac6f19ce94c5d19ae217cda38f48e469be08b Mon Sep 17 00:00:00 2001 From: Bart Brouns Date: Fri, 15 Nov 2024 17:35:33 +0100 Subject: [PATCH] add tap meters --- src/editor.rs | 78 ++++++++++++++++++++++++++++++++++++++++----------- src/lib.rs | 40 ++++++++++++++++++++++---- 2 files changed, 95 insertions(+), 23 deletions(-) diff --git a/src/editor.rs b/src/editor.rs index f2b391d..aa6d54b 100644 --- a/src/editor.rs +++ b/src/editor.rs @@ -12,19 +12,26 @@ use nih_plug_vizia::{ }; use crate::{ - editor::dual_meter::DualMeter, util, AtomicBoolArray, AtomicByteArray, AtomicF32, Del2Params, - LastPlayedNotes, CLEAR_TAPS, LEARNING, LOCK_TAPS, MUTE_IN, MUTE_OUT, NO_LEARNED_NOTE, + editor::dual_meter::DualMeter, util, AtomicBoolArray, AtomicByteArray, AtomicF32, + AtomicF32Array, Del2Params, LastPlayedNotes, CLEAR_TAPS, LEARNING, LOCK_TAPS, MUTE_IN, + MUTE_OUT, NO_LEARNED_NOTE, }; mod dual_meter; const ZOOM_SMOOTH_POLE: f32 = 0.915; +/// The minimum decibel value that the meters display +const MIN_TICK: f32 = -60.0; +/// The maximum decibel value that the meters display +const MAX_TICK: f32 = 0.0; + #[derive(Lens, Clone)] pub struct Data { pub params: Arc, pub input_meter: Arc, pub output_meter: Arc, + pub tap_meters: Arc, pub is_learning: Arc, pub learning_index: Arc, pub learned_notes: Arc, @@ -335,7 +342,7 @@ pub fn create(editor_data: Data, editor_state: Arc) -> Option) -> Option, + tap_meters: Arc, } // TODO: add grid to show bars & beats @@ -395,6 +403,7 @@ impl View for DelayGraph { let selection_color: vg::Color = draw_context.selection_color().into(); let border_width = draw_context.border_width(); let outline_width = draw_context.outline_width(); + let tap_meters = self.tap_meters.clone(); // Compute the time scaling factor let target_time_scaling_factor = Self::compute_time_scaling_factor( @@ -436,8 +445,10 @@ impl View for DelayGraph { Self::draw_tap_velocities( canvas, params.clone(), + tap_meters.clone(), bounds, outline_color, + border_color, outline_width, time_scaling_factor, border_width, @@ -459,12 +470,18 @@ impl View for DelayGraph { } impl DelayGraph { - fn new(cx: &mut Context, params: ParamsL) -> Handle + fn new( + cx: &mut Context, + params: ParamsL, + tap_meters: TapMetersL, + ) -> Handle where ParamsL: Lens>, + TapMetersL: Lens>, { Self { params: params.get(cx), + tap_meters: tap_meters.get(cx), } .build(cx, |cx| { Label::new( @@ -574,31 +591,58 @@ impl DelayGraph { fn draw_tap_velocities( canvas: &mut Canvas, params: Arc, + tap_meters: Arc, bounds: BoundingBox, - color: vg::Color, + velocity_color: vg::Color, + meter_color: vg::Color, line_width: f32, scaling_factor: f32, border_width: f32, ) { - let mut path = vg::Path::new(); for i in 0..params.current_tap.load(std::sync::atomic::Ordering::SeqCst) { let x_offset = (params.delay_times[i].load(std::sync::atomic::Ordering::SeqCst) as f32) .mul_add(scaling_factor, border_width * 0.5); - let velocity_height = params.velocities[i] - .load(std::sync::atomic::Ordering::SeqCst) - .mul_add( - -border_width.mul_add(-0.5, bounds.h), - border_width.mul_add(-0.5, bounds.h), - ); + let velocity_value = params.velocities[i].load(std::sync::atomic::Ordering::SeqCst); + let velocity_height = velocity_value.mul_add(bounds.h, -border_width * 1.0); + + let meter_db = + util::gain_to_db(tap_meters[i].load(std::sync::atomic::Ordering::Relaxed)); + let meter_height = { + let tick_fraction = (meter_db - MIN_TICK) / (MAX_TICK - MIN_TICK); + (tick_fraction * bounds.h).max(0.0) // Scale using bounds height + }; + + let mut path = vg::Path::new(); path.move_to( - bounds.x + x_offset, - border_width.mul_add(-0.5, bounds.y + bounds.h), + bounds.x + x_offset - (line_width * 0.75), + bounds.y + bounds.h - velocity_height, + ); + path.line_to( + bounds.x + x_offset - (line_width * 0.75), + bounds.y + bounds.h, ); - path.line_to(bounds.x + x_offset, bounds.y + velocity_height); - } - canvas.stroke_path(&path, &vg::Paint::color(color).with_line_width(line_width)); + canvas.stroke_path( + &path, + &vg::Paint::color(velocity_color).with_line_width(line_width * 1.5), + ); + + path = vg::Path::new(); + path.move_to( + bounds.x + x_offset + (line_width * 0.75), + bounds.y + bounds.h - meter_height, + ); + path.line_to( + bounds.x + x_offset + (line_width * 0.75), + bounds.y + bounds.h, + ); + + canvas.stroke_path( + &path, + &vg::Paint::color(meter_color).with_line_width(line_width * 1.5), + ); + } } fn draw_tap_notes_and_pans( diff --git a/src/lib.rs b/src/lib.rs index 3055c67..8b74a04 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -103,6 +103,7 @@ struct Del2 { peak_meter_decay_weight: f32, input_meter: Arc, output_meter: Arc, + tap_meters: Arc, delay_write_index: isize, is_learning: Arc, // for which control are we learning? @@ -447,6 +448,11 @@ impl Default for Del2 { let ladders: [LadderFilter; NUM_TAPS] = array_init(|i| LadderFilter::new(filter_params[i].clone())); let amp_envelopes = array_init::array_init(|_| Smoother::none()); + + let tap_meters = AtomicF32Array(array_init::array_init(|_| { + Arc::new(AtomicF32::new(util::MINUS_INFINITY_DB)) + })) + .into(); // let delayed_audio_l = array_init(|_| f32::default_boxed_array::()); // let delayed_audio_r = array_init(|_| f32::default_boxed_array::()); let delayed_audio_l = array_init(|_| vec![0.0; MAX_BLOCK_SIZE].into_boxed_slice()); @@ -488,6 +494,7 @@ impl Default for Del2 { peak_meter_decay_weight: 1.0, input_meter: Arc::new(AtomicF32::new(util::MINUS_INFINITY_DB)), output_meter: Arc::new(AtomicF32::new(util::MINUS_INFINITY_DB)), + tap_meters, delay_write_index: 0, is_learning: Arc::new(AtomicBool::new(false)), learning_index: Arc::new(AtomicUsize::new(0)), @@ -600,6 +607,7 @@ impl Plugin for Del2 { params: self.params.clone(), input_meter: self.input_meter.clone(), output_meter: self.output_meter.clone(), + tap_meters: self.tap_meters.clone(), is_learning: self.is_learning.clone(), learning_index: self.learning_index.clone(), learned_notes: self.learned_notes.clone(), @@ -995,14 +1003,35 @@ impl Plugin for Del2 { } } + let mut amplitude = 0.0; for (value_idx, sample_idx) in (block_start..block_end).enumerate() { let post_filter_gain = self.dry_wet[value_idx] * self.output_gain[value_idx] / (drive * self.global_drive[value_idx]); + let left = self.delayed_audio_l[tap_index][sample_idx] * post_filter_gain; + let right = self.delayed_audio_r[tap_index][sample_idx] * post_filter_gain; + output[0][sample_idx] += left; + output[1][sample_idx] += right; + amplitude += left.abs(); // + right.abs(); + } - output[0][sample_idx] += - self.delayed_audio_l[tap_index][sample_idx] * post_filter_gain; - output[1][sample_idx] += - self.delayed_audio_r[tap_index][sample_idx] * post_filter_gain; + if self.params.editor_state.is_open() { + amplitude = (amplitude / block_len as f32).abs(); + let current_peak_meter = + self.tap_meters[tap_index].load(std::sync::atomic::Ordering::Relaxed); + let new_peak_meter = if amplitude > current_peak_meter { + amplitude + } else { + // println!("self.peak_meter_decay_weight: {}",self.peak_meter_decay_weight); + + current_peak_meter.mul_add(0.8, amplitude * (1.0 - 0.8)) + // current_peak_meter.mul_add( + // self.peak_meter_decay_weight, + // amplitude * (1.0 - self.peak_meter_decay_weight), + // ) + }; + + self.tap_meters[tap_index] + .store(new_peak_meter, std::sync::atomic::Ordering::Relaxed); } } @@ -1451,7 +1480,6 @@ impl Del2 { amplitude += *sample; } - // Example of condition dependent on editor or GUI state if self.params.editor_state.is_open() { amplitude = (amplitude / num_samples as f32).abs(); let current_peak_meter = peak_meter.load(std::sync::atomic::Ordering::Relaxed); @@ -1974,7 +2002,7 @@ impl PersistentField<'_, u8> for ArcAtomicBoolArray { } struct AtomicU8Array([Arc; NUM_TAPS]); struct AtomicU32Array([Arc; NUM_TAPS]); -struct AtomicF32Array([Arc; NUM_TAPS]); +pub struct AtomicF32Array([Arc; NUM_TAPS]); // Implement PersistentField for AtomicU8Array impl PersistentField<'_, [u8; 8]> for AtomicU8Array {