diff --git a/src/editor.rs b/src/editor.rs index 3e4304a..d2e2a86 100644 --- a/src/editor.rs +++ b/src/editor.rs @@ -4,29 +4,33 @@ use nih_plug_vizia::vizia::prelude::*; use nih_plug_vizia::vizia::vg; use nih_plug_vizia::widgets::*; use nih_plug_vizia::{assets, create_vizia_editor, ViziaState, ViziaTheming}; -use std::sync::atomic::Ordering; +use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::{Arc, Mutex}; +use crate::AtomicByteArray; use crate::Del2Params; use crate::DelayData; use crate::DelayDataOutput; #[derive(Lens, Clone)] pub(crate) struct Data { - pub(crate) params: Arc, - pub(crate) delay_data: Arc>, + pub params: Arc, + pub delay_data: Arc>, pub input_meter: Arc, pub output_meter: Arc, + pub is_learning: Arc, + pub learning_index: Arc, + pub learned_notes: Arc, } impl Model for Data {} // Makes sense to also define this here, makes it a bit easier to keep track of -pub(crate) fn default_state() -> Arc { - ViziaState::new(|| (1200, 600)) +pub fn default_state() -> Arc { + ViziaState::new(|| (1200, 800)) } -pub(crate) fn create(editor_data: Data, editor_state: Arc) -> Option> { +pub fn create(editor_data: Data, editor_state: Arc) -> Option> { create_vizia_editor(editor_state, ViziaTheming::Custom, move |cx, _| { assets::register_noto_sans_light(cx); assets::register_noto_sans_thin(cx); @@ -79,6 +83,13 @@ pub(crate) fn create(editor_data: Data, editor_state: Arc) -> Option }); }); }); + ActionTrigger::new( + cx, + Data::is_learning, + Data::learning_index, + 0, + Data::learned_notes, + ); HStack::new(cx, |cx| { HStack::new(cx, |cx| { @@ -215,20 +226,6 @@ pub struct DelayGraph { delay_data: Arc>, } -impl DelayGraph { - pub fn new(cx: &mut Context, delay_data: DelayDataL) -> Handle - where - DelayDataL: Lens>>, - { - Self { - delay_data: delay_data.get(cx), - } - .build(cx, |_cx| { - // put other widgets here - }) - } -} - // TODO: add grid to show bars & beats impl View for DelayGraph { // For CSS: @@ -240,7 +237,7 @@ impl View for DelayGraph { let mut locked_delay_data = self.delay_data.lock().unwrap(); let delay_data = locked_delay_data.read(); - let bounding_box = draw_context.bounds(); + let bounds = draw_context.bounds(); let background_color: vg::Color = draw_context.background_color().into(); let border_color: vg::Color = draw_context.border_color().into(); @@ -250,19 +247,15 @@ impl View for DelayGraph { let path_line_width = draw_context.outline_width(); // Compute the time scaling factor - let time_scaling_factor = self.compute_time_scaling_factor( - &delay_data, - bounding_box.w, - border_width, - path_line_width, - ); + let time_scaling_factor = + self.compute_time_scaling_factor(&delay_data, bounds.w, border_width, path_line_width); // Draw components - self.draw_background(canvas, bounding_box, background_color, border_width); + self.draw_background(canvas, bounds, background_color, border_width); self.draw_delay_times_as_lines( canvas, &delay_data, - bounding_box, + bounds, border_color, 1.0, time_scaling_factor, @@ -270,7 +263,7 @@ impl View for DelayGraph { self.draw_time_line( canvas, &delay_data, - bounding_box, + bounds, selection_color, path_line_width, time_scaling_factor, @@ -279,7 +272,7 @@ impl View for DelayGraph { self.draw_tap_velocities( canvas, &delay_data, - bounding_box, + bounds, outline_color, path_line_width, time_scaling_factor, @@ -288,19 +281,30 @@ impl View for DelayGraph { self.draw_tap_notes_as_diamonds( canvas, &delay_data, - bounding_box, + bounds, selection_color, path_line_width, time_scaling_factor, border_width, true, ); - self.draw_bounding_outline(canvas, bounding_box, border_color, border_width); + self.draw_bounding_outline(canvas, bounds, border_color, border_width); } } -// Functional Breakdown impl DelayGraph { + pub fn new(cx: &mut Context, delay_data: DelayDataL) -> Handle + where + DelayDataL: Lens>>, + { + Self { + delay_data: delay_data.get(cx), + } + .build(cx, |_cx| { + // put other widgets here + }) + } + fn compute_time_scaling_factor( &self, delay_data: &DelayData, @@ -516,3 +520,90 @@ fn make_column(cx: &mut Context, title: &str, contents: impl FnOnce(&mut Context }) .class("column"); } + +/////////////////////////////////////////////////////////////////////////////// +// ActionTrigger // +/////////////////////////////////////////////////////////////////////////////// + +pub struct ActionTrigger { + is_learning: Arc, + learning_index: Arc, + own_index: usize, + learned_notes: Arc, +} +impl ActionTrigger { + pub fn new( + cx: &mut Context, + + is_learning: IsLearningL, + learning_index: LearningIndexL, + own_index: usize, + learned_notes: LearnedNoteL, + ) -> Handle + where + IsLearningL: Lens>, + LearningIndexL: Lens>, + LearnedNoteL: Lens>, + { + Self { + is_learning: is_learning.get(cx), + learning_index: learning_index.get(cx), + own_index, + learned_notes: learned_notes.get(cx), + // delay_data: delay_data.get(cx), + } + .build(cx, |_cx| { + // put other widgets here + }) + } + + pub fn start_learning(&self) { + self.is_learning.store(true, Ordering::SeqCst); + self.learning_index.store(self.own_index, Ordering::SeqCst); + } + + pub fn stop_learning(&self) { + self.is_learning.store(false, Ordering::SeqCst); + } + + pub fn set_learning_index(&self, index: usize) { + self.learning_index.store(index, Ordering::SeqCst); + } + + pub fn get_learning_index(&self) -> usize { + self.learning_index.load(Ordering::SeqCst) + } + + // Checks if learning is active for this trigger + pub fn is_learning(&self) -> bool { + self.is_learning.load(Ordering::SeqCst) && self.get_learning_index() == self.own_index + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////// + // for drawing + ///////////////////////////////////////////////////////////////////////////////////////////////////////////// + fn draw_background(&self, canvas: &mut Canvas, bounds: BoundingBox, color: vg::Color) { + let mut path = vg::Path::new(); + path.rect(bounds.x, bounds.y, bounds.w, bounds.h); + path.close(); + + let paint = vg::Paint::color(color); + canvas.fill_path(&path, &paint); + } +} + +impl View for ActionTrigger { + // For CSS: + fn element(&self) -> Option<&'static str> { + Some("action-trigger") + } + + fn draw(&self, draw_context: &mut DrawContext, canvas: &mut Canvas) { + let bounds = draw_context.bounds(); + let background_color: vg::Color = draw_context.background_color().into(); + + self.draw_background(canvas, bounds, background_color); + // Example of using internal state in a simplistic way + if self.is_learning() {} + } +} diff --git a/src/lib.rs b/src/lib.rs index deb06a1..91ace89 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,7 +28,7 @@ use nih_plug::prelude::*; use nih_plug_vizia::vizia::prelude::*; use nih_plug_vizia::ViziaState; use std::simd::f32x4; -use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; +use std::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering}; use std::sync::{Arc, Mutex}; use synfx_dsp::fh_va::{FilterParams, LadderFilter, LadderMode}; use triple_buffer::TripleBuffer; @@ -47,8 +47,6 @@ const VELOCITY_HIGH_NAME_PREFIX: &str = "high velocity"; const MAX_BLOCK_LEN: usize = 32768; const PEAK_METER_DECAY_MS: f64 = 150.0; -// TODO: make an enum of which controls we have -const MAX_NR_LEARNED_NOTES: usize = 4; struct Del2 { params: Arc, @@ -69,7 +67,6 @@ struct Del2 { amp_envelopes: [Smoother; MAX_NR_TAPS], envelope_block: Vec, releasings: [bool; MAX_NR_TAPS], - learned_notes: [u8; MAX_NR_LEARNED_NOTES], sample_rate: f32, peak_meter_decay_weight: f32, input_meter: Arc, @@ -78,6 +75,7 @@ struct Del2 { // for which control are we learning? is_learning: Arc, learning_index: Arc, + learned_notes: Arc, samples_since_last_event: u32, timing_last_event: u32, min_tap_samples: u32, @@ -428,6 +426,7 @@ impl Default for Del2 { let amp_envelopes = array_init::array_init(|_| Smoother::none()); let is_learning = Arc::new(AtomicBool::new(false)); let learning_index = Arc::new(AtomicUsize::new(0)); + let learned_notes = Arc::new(AtomicByteArray::new()); Self { params: Arc::new(Del2Params::new( should_update_filter.clone(), @@ -450,7 +449,6 @@ impl Default for Del2 { envelope_block: vec![0.0; MAX_BLOCK_LEN], releasings: [false; MAX_NR_TAPS], //TODO: make Option - learned_notes: [0; MAX_NR_LEARNED_NOTES], sample_rate: 1.0, peak_meter_decay_weight: 1.0, input_meter: Arc::new(AtomicF32::new(util::MINUS_INFINITY_DB)), @@ -458,6 +456,7 @@ impl Default for Del2 { delay_write_index: 0, is_learning, learning_index, + learned_notes, samples_since_last_event: 0, timing_last_event: 0, min_tap_samples: 0, @@ -543,6 +542,9 @@ impl Plugin for Del2 { input_meter: self.input_meter.clone(), output_meter: self.output_meter.clone(), delay_data: self.delay_data_output.clone(), + is_learning: self.is_learning.clone(), + learning_index: self.learning_index.clone(), + learned_notes: self.learned_notes.clone(), }, self.params.editor_state.clone(), ) @@ -679,7 +681,11 @@ impl Del2 { CountingState::MidiLearn => { let is_learning = self.is_learning.clone(); is_learning.store(false, Ordering::Release); - self.learned_notes[self.learning_index.load(Ordering::SeqCst)] = note; + self.learned_notes.store( + self.learning_index.load(Ordering::SeqCst), + note, + Ordering::Release, + ); self.counting_state = CountingState::TimeOut; self.timing_last_event = 0; self.samples_since_last_event = 0; @@ -1112,6 +1118,31 @@ impl Enum for MyLadderMode { }) } } +// #[derive(Debug)] +struct AtomicByteArray { + data: AtomicU64, +} + +impl AtomicByteArray { + fn new() -> Self { + Self { + data: AtomicU64::new(0), // Initialize with zero + } + } + + fn load(&self, index: usize, ordering: Ordering) -> u8 { + assert!(index < 8, "Index out of bounds"); + let value = self.data.load(ordering); + ((value >> (index * 8)) & 0xFF) as u8 + } + + fn store(&self, index: usize, byte: u8, ordering: Ordering) { + assert!(index < 8, "Index out of bounds"); + let mask = !(0xFFu64 << (index * 8)); + let new_value = (self.data.load(ordering) & mask) | ((byte as u64) << (index * 8)); + self.data.store(new_value, ordering); + } +} nih_export_clap!(Del2); nih_export_vst3!(Del2); diff --git a/src/style.css b/src/style.css index ddd1b0f..9a0990a 100644 --- a/src/style.css +++ b/src/style.css @@ -114,6 +114,11 @@ delay-graph { selection-color: #fabdf0; } +action-trigger { + top: 100px; + background-color: #4e4e4e; +} + generic-ui .row { col-between: 6px; height: auto;