#![allow(incomplete_features)] #![feature(generic_associated_types)] #![feature(drain_filter)] use baseplug::{event::Data, Event, Plugin, ProcessContext}; use rand::{thread_rng, Rng}; use serde::{Deserialize, Serialize}; use utils::logs::*; mod maths; use maths::*; baseplug::model! { #[derive(Debug, Serialize, Deserialize)] struct MultidimModel { /// Speed at which the ball moves /// Around 1 should cause aprox. one bounce per beat #[model(min = 0.0, max = 10.0)] #[parameter(name = "speed")] speed: f32, /// Root note /// Notes will be generated from `root` to `root + DIM` #[model(min = 0.0, max = 111.0)] #[parameter(name = "root note")] root: f32, /// How many beats each note lasts #[model(min = 0.0, max = 4.0)] #[parameter(name = "note length")] note_length: f32, /// Enable if over 0.5 #[model(min = 0.0, max = 1.0)] #[parameter(name = "enable")] enable: f32, } } impl Default for MultidimModel { fn default() -> Self { Self { speed: 1.0, root: 64.0, note_length: 0.25, enable: 0.0, } } } const DIM: usize = 16; struct Multidim { position: [f32; DIM], velocity: [f32; DIM], frame: usize, pending_note_offs: Vec<(u8, usize)>, } impl Plugin for Multidim { const NAME: &'static str = "multidim"; const PRODUCT: &'static str = "multidim"; const VENDOR: &'static str = "unnieversal"; const INPUT_CHANNELS: usize = 2; const OUTPUT_CHANNELS: usize = 2; type Model = MultidimModel; #[inline] fn new(_sample_rate: f32, _model: &MultidimModel) -> Self { setup_logging("multidim.log"); // Randomize direction, normalize let mut velocity = [0.0; DIM]; let mut rng = thread_rng(); for v in &mut velocity { *v = rng.gen_range(-1.0..1.0); } velocity = normalize(velocity); log::info!("finished init"); Self { position: [0.0; DIM], velocity, frame: 0, pending_note_offs: Vec::with_capacity(100), } } #[inline] fn process(&mut self, model: &MultidimModelProcess, ctx: &mut ProcessContext) { let output = &mut ctx.outputs[0].buffers; let enqueue_midi = &mut ctx.enqueue_event; let curr_bpm = ctx.musical_time.bpm as f32; let beat_in_seconds = 60.0 / curr_bpm; let samples_per_beat = beat_in_seconds * ctx.sample_rate as f32; // Check for pending note offs let n = ctx.nframes; let f = self.frame; for (note, end) in self.pending_note_offs.drain_filter(|(_, end)| *end < f + n) { // Send note off let note_on = Event:: { frame: end - self.frame, data: Data::Midi([0x80, note, 120]), }; enqueue_midi(note_on); } self.frame += ctx.nframes; for i in 0..ctx.nframes { // write silence output[0][i] = 0.0; output[1][i] = 0.0; // If we're not playing, we skip advancing the ball // We don't want to 1) waste cpu 2) send note ons if model.enable[i] < 0.5 { continue; } // Advance ball and get list of collisions let collisions = advance( model.speed[i] / samples_per_beat, &mut self.position, &mut self.velocity, ); let root = model.root[i].floor() as u8; // Go through the list of possible collisions, and if any happened, send midi for (idx, collision) in collisions.iter().enumerate() { if !collision { continue; } let note = idx as u8 + root; // Send note on let note_on = Event:: { frame: i, data: Data::Midi([0x90, note, 120]), }; enqueue_midi(note_on); // Add note off let end = self.frame + (samples_per_beat * model.note_length[i]) as usize; self.pending_note_offs.push((note, end)) } } } } baseplug::vst2!(Multidim, b"mdim");