unnieversal/crates/multidim/src/lib.rs

156 lines
4.3 KiB
Rust

#![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<Self>) {
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::<Multidim> {
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::<Multidim> {
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");