From 366deda83c9656c96af0f1bb459517d57a1e5a1b Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 4 Feb 2022 02:37:40 +0100 Subject: [PATCH] Add MIDI support to the sine example --- plugins/examples/sine/src/lib.rs | 73 +++++++++++++++++++++++++++----- src/plugin.rs | 12 +++++- src/util.rs | 6 +++ 3 files changed, 80 insertions(+), 11 deletions(-) diff --git a/plugins/examples/sine/src/lib.rs b/plugins/examples/sine/src/lib.rs index 039084fd..6511c538 100644 --- a/plugins/examples/sine/src/lib.rs +++ b/plugins/examples/sine/src/lib.rs @@ -21,7 +21,7 @@ use nih_plug::{ formatters, util, Buffer, BufferConfig, BusConfig, Plugin, ProcessContext, ProcessStatus, Vst3Plugin, }; -use nih_plug::{FloatParam, Param, Params, Range, Smoother, SmoothingStyle}; +use nih_plug::{BoolParam, FloatParam, Param, Params, Range, Smoother, SmoothingStyle}; use std::f32::consts; use std::pin::Pin; @@ -32,7 +32,10 @@ struct Sine { params: Pin>, sample_rate: f32, + /// The current phase of the sine wave, always kept between in `[0, 1]`. phase: f32, + /// The active frequency, if triggered by MIDI. + active_note_freq: Option, } #[derive(Params)] @@ -42,6 +45,9 @@ struct SineParams { #[id = "freq"] pub frequency: FloatParam, + + #[id = "usemid"] + pub use_midi: BoolParam, } impl Default for Sine { @@ -51,6 +57,7 @@ impl Default for Sine { sample_rate: 1.0, phase: 0.0, + active_note_freq: None, } } } @@ -83,10 +90,29 @@ impl Default for SineParams { value_to_string: formatters::f32_rounded(0), ..Default::default() }, + use_midi: BoolParam { + value: false, + name: "Use MIDI", + ..Default::default() + }, } } } +impl Sine { + fn calculate_sine(&mut self, frequency: f32) -> f32 { + let phase_delta = frequency / self.sample_rate; + let sine = (self.phase * consts::TAU).sin(); + + self.phase += phase_delta; + if self.phase >= 1.0 { + self.phase -= 1.0; + } + + sine + } +} + impl Plugin for Sine { const NAME: &'static str = "Sine Test Tone"; const VENDOR: &'static str = "Moist Plugins GmbH"; @@ -120,19 +146,46 @@ impl Plugin for Sine { true } - fn process(&mut self, buffer: &mut Buffer, _context: &dyn ProcessContext) -> ProcessStatus { - for samples in buffer.iter_mut() { + fn process(&mut self, buffer: &mut Buffer, context: &dyn ProcessContext) -> ProcessStatus { + let mut next_event = context.next_midi_event(); + for (sample_id, samples) in buffer.iter_mut().enumerate() { // Smoothing is optionally built into the parameters themselves let gain = self.params.gain.smoothed.next(); - let frequency = self.params.frequency.smoothed.next(); - let phase_delta = frequency / self.sample_rate; - let sine = (self.phase * consts::TAU).sin(); + // This plugin can be either triggered by MIDI or controleld by a parameter + let sine = if self.params.use_midi.value { + // Act on the next MIDI event + 'midi_events: loop { + match next_event { + Some(event) if event.timing() == sample_id as u32 => match event { + nih_plug::NoteEvent::NoteOn { note, .. } => { + // Reset the phase if this is a new note + if self.active_note_freq.is_none() { + self.phase = 0.0; + } - self.phase += phase_delta; - if self.phase >= 1.0 { - self.phase -= 1.0; - } + self.active_note_freq = Some(util::midi_note_to_freq(note)); + } + nih_plug::NoteEvent::NoteOff { note, .. } => { + if self.active_note_freq == Some(util::midi_note_to_freq(note)) { + self.active_note_freq = None; + } + } + }, + _ => break 'midi_events, + } + + next_event = context.next_midi_event(); + } + + match self.active_note_freq { + Some(frequency) => self.calculate_sine(frequency), + None => 0.0, + } + } else { + let frequency = self.params.frequency.smoothed.next(); + self.calculate_sine(frequency) + }; for sample in samples { *sample = sine * util::db_to_gain(gain); diff --git a/src/plugin.rs b/src/plugin.rs index d9e0ce86..30ed1584 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -155,7 +155,7 @@ pub enum ProcessStatus { } /// Event for (incoming) notes. Right now this only supports a very small subset of the MIDI -/// specification. +/// specification. See the util module for convenient conversion functions. /// /// All of the timings are sample offsets withing the current buffer. /// @@ -175,3 +175,13 @@ pub enum NoteEvent { velocity: u8, }, } + +impl NoteEvent { + /// Return the sample within the current buffer this event belongs to. + pub fn timing(&self) -> u32 { + match &self { + NoteEvent::NoteOn { timing, .. } => *timing, + NoteEvent::NoteOff { timing, .. } => *timing, + } + } +} diff --git a/src/util.rs b/src/util.rs index 3d6ae5ae..96fbed74 100644 --- a/src/util.rs +++ b/src/util.rs @@ -35,6 +35,12 @@ pub fn gain_to_db(gain: f32) -> f32 { } } +/// Convert a MIDI note ID to a frequency at A4 = 440 Hz equal temperament and middle C = note 60 = +/// C4. +pub fn midi_note_to_freq(pitch: u8) -> f32 { + 2.0f32.powf((pitch as f32 - 69.0) / 12.0) * 440.0 +} + /// A version of [std::thread::Builder::spawn_unchecked] that works on the stable compiler. /// Implementation courtesy of Yandros on the Rust Discord. pub(crate) trait ThreadSpawnUnchecked {