From 49f1a45b7639942043fc906abfafcbf2a5c35c63 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 11 Apr 2022 20:47:00 +0200 Subject: [PATCH] Add a MIDI effect that inverts all signals To demonstrates the API. The CLAP version does not seem to be able to output anything other than basic notes in Bitwig Studio 4.2.3. --- Cargo.lock | 7 + Cargo.toml | 1 + README.md | 3 + plugins/examples/midi-inverter/Cargo.toml | 12 ++ plugins/examples/midi-inverter/src/lib.rs | 201 ++++++++++++++++++++++ 5 files changed, 224 insertions(+) create mode 100644 plugins/examples/midi-inverter/Cargo.toml create mode 100644 plugins/examples/midi-inverter/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 650db78e..65474a4d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1940,6 +1940,13 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f2dd5c7f8aaf48a76e389068ab25ed80bdbc226b887f9013844c415698c9952" +[[package]] +name = "midi_inverter" +version = "0.1.0" +dependencies = [ + "nih_plug", +] + [[package]] name = "minimal-lexical" version = "0.2.1" diff --git a/Cargo.toml b/Cargo.toml index ae71793f..912b0d97 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ members = [ "plugins/examples/gain-gui-egui", "plugins/examples/gain-gui-iced", "plugins/examples/gain-gui-vizia", + "plugins/examples/midi-inverter", "plugins/examples/sine", "plugins/examples/stft", diff --git a/README.md b/README.md index ff3a859a..1a227b21 100644 --- a/README.md +++ b/README.md @@ -145,6 +145,9 @@ examples. [egui](plugins/examples/gain-gui-egui), [iced](plugins/examples/gain-gui-iced), and [VIZIA](plugins/examples/gain-gui-vizia). +- [**midi-inverter**](plugins/examples/midi-inverter) takes note/MIDI events and + flips around the note, channel, expression, pressure, and CC values. This + example demonstrates how to receive and output those events. - [**sine**](plugins/examples/sine) is a simple test tone generator plugin with frequency smoothing that can also make use of MIDI input instead of generating a static signal based on the plugin's parameters. diff --git a/plugins/examples/midi-inverter/Cargo.toml b/plugins/examples/midi-inverter/Cargo.toml new file mode 100644 index 00000000..894a87ff --- /dev/null +++ b/plugins/examples/midi-inverter/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "midi_inverter" +version = "0.1.0" +edition = "2021" +authors = ["Robbert van der Helm "] +license = "ISC" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +nih_plug = { path = "../../../", features = ["assert_process_allocs"] } diff --git a/plugins/examples/midi-inverter/src/lib.rs b/plugins/examples/midi-inverter/src/lib.rs new file mode 100644 index 00000000..4e185e2b --- /dev/null +++ b/plugins/examples/midi-inverter/src/lib.rs @@ -0,0 +1,201 @@ +use nih_plug::prelude::*; +use std::sync::Arc; + +/// A plugin that inverts all MIDI note numbers, channels, CCs, velocitires, pressures, and +/// everything else you don't want to be inverted. +struct MidiInverter { + params: Arc, +} + +#[derive(Default, Params)] +struct MidiInverterParams {} + +impl Default for MidiInverter { + fn default() -> Self { + Self { + params: Arc::new(MidiInverterParams::default()), + } + } +} + +impl Plugin for MidiInverter { + const NAME: &'static str = "MIDI Inverter"; + const VENDOR: &'static str = "Moist Plugins GmbH"; + const URL: &'static str = "https://youtu.be/dQw4w9WgXcQ"; + const EMAIL: &'static str = "info@example.com"; + + const VERSION: &'static str = "0.0.1"; + + const DEFAULT_NUM_INPUTS: u32 = 0; + const DEFAULT_NUM_OUTPUTS: u32 = 0; + + const MIDI_INPUT: MidiConfig = MidiConfig::MidiCCs; + const MIDI_OUTPUT: MidiConfig = MidiConfig::MidiCCs; + const SAMPLE_ACCURATE_AUTOMATION: bool = true; + + fn params(&self) -> Arc { + // The explicit cast is not needed, Rust Analyzer just doesn't get it otherwise + self.params.clone() as Arc + } + + fn process( + &mut self, + _buffer: &mut Buffer, + context: &mut impl ProcessContext, + ) -> ProcessStatus { + // Act on the next MIDI event + while let Some(event) = context.next_event() { + match event { + NoteEvent::NoteOn { + timing, + channel, + note, + velocity, + } => context.send_event(NoteEvent::NoteOn { + timing, + channel: 15 - channel, + note: 127 - note, + velocity: 1.0 - velocity, + }), + NoteEvent::NoteOff { + timing, + channel, + note, + velocity, + } => context.send_event(NoteEvent::NoteOff { + timing, + channel: 15 - channel, + note: 127 - note, + velocity: 1.0 - velocity, + }), + NoteEvent::PolyPressure { + timing, + channel, + note, + pressure, + } => context.send_event(NoteEvent::PolyPressure { + timing, + channel: 15 - channel, + note: 127 - note, + pressure: 1.0 - pressure, + }), + NoteEvent::PolyVolume { + timing, + channel, + note, + gain, + } => context.send_event(NoteEvent::PolyVolume { + timing, + channel: 15 - channel, + note: 127 - note, + gain: 1.0 - gain, + }), + NoteEvent::PolyPan { + timing, + channel, + note, + pan, + } => context.send_event(NoteEvent::PolyPan { + timing, + channel: 15 - channel, + note: 127 - note, + pan: 1.0 - pan, + }), + NoteEvent::PolyTuning { + timing, + channel, + note, + tuning, + } => context.send_event(NoteEvent::PolyTuning { + timing, + channel: 15 - channel, + note: 127 - note, + tuning: 1.0 - tuning, + }), + NoteEvent::PolyVibrato { + timing, + channel, + note, + vibrato, + } => context.send_event(NoteEvent::PolyVibrato { + timing, + channel: 15 - channel, + note: 127 - note, + vibrato: 1.0 - vibrato, + }), + NoteEvent::PolyExpression { + timing, + channel, + note, + expression, + } => context.send_event(NoteEvent::PolyExpression { + timing, + channel: 15 - channel, + note: 127 - note, + expression: 1.0 - expression, + }), + NoteEvent::PolyBrightness { + timing, + channel, + note, + brightness, + } => context.send_event(NoteEvent::PolyBrightness { + timing, + channel: 15 - channel, + note: 127 - note, + brightness: 1.0 - brightness, + }), + NoteEvent::MidiChannelPressure { + timing, + channel, + pressure, + } => context.send_event(NoteEvent::MidiChannelPressure { + timing, + channel: 15 - channel, + pressure: 1.0 - pressure, + }), + NoteEvent::MidiPitchBend { + timing, + channel, + value, + } => context.send_event(NoteEvent::MidiPitchBend { + timing, + channel: 15 - channel, + value: 1.0 - value, + }), + NoteEvent::MidiCC { + timing, + channel, + cc, + value, + } => context.send_event(NoteEvent::MidiCC { + timing, + channel: 15 - channel, + // The one thing we won't invert, because uuhhhh + cc, + value: 1.0 - value, + }), + _ => (), + } + } + + ProcessStatus::Normal + } +} + +impl ClapPlugin for MidiInverter { + const CLAP_ID: &'static str = "com.moist-plugins-gmbh.midi-inverter"; + const CLAP_DESCRIPTION: &'static str = + "Inverts all note and MIDI signals in ways you don't want to"; + const CLAP_FEATURES: &'static [&'static str] = &["note_effect", "utility"]; + const CLAP_MANUAL_URL: &'static str = Self::URL; + const CLAP_SUPPORT_URL: &'static str = Self::URL; +} + +impl Vst3Plugin for MidiInverter { + const VST3_CLASS_ID: [u8; 16] = *b"M1d1Inv3r70rzAaA"; + const VST3_CATEGORIES: &'static str = "Instrument|Tools"; +} + +nih_export_clap!(MidiInverter); +nih_export_vst3!(MidiInverter);