From fba2c4761438b9e04bb6b5f3220dff373fdc652b Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Wed, 1 Feb 2023 14:52:01 +0100 Subject: [PATCH] Add a SysEx example plugin --- Cargo.lock | 7 ++ Cargo.toml | 1 + README.md | 2 + plugins/examples/midi_inverter/src/lib.rs | 6 -- plugins/examples/sysex/Cargo.toml | 12 +++ plugins/examples/sysex/src/lib.rs | 109 ++++++++++++++++++++++ 6 files changed, 131 insertions(+), 6 deletions(-) create mode 100644 plugins/examples/sysex/Cargo.toml create mode 100644 plugins/examples/sysex/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 2e8e9334..9923f2b1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4252,6 +4252,13 @@ dependencies = [ "winapi", ] +[[package]] +name = "sysex" +version = "0.1.0" +dependencies = [ + "nih_plug", +] + [[package]] name = "termcolor" version = "1.1.3" diff --git a/Cargo.toml b/Cargo.toml index 06534de0..99861066 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,7 @@ members = [ "plugins/examples/poly_mod_synth", "plugins/examples/sine", "plugins/examples/stft", + "plugins/examples/sysex", "plugins/buffr_glitch", "plugins/crisp", diff --git a/README.md b/README.md index 2134f0ef..a526a52a 100644 --- a/README.md +++ b/README.md @@ -207,6 +207,8 @@ examples. higher level helper features, such as an adapter to process audio with a short-term Fourier transform using the overlap-add method, all using the compositional `Buffer` interfaces. +- [**sysex**](plugins/examples/sysex) is a simple example of how to send and + receive SysEx messages by defining custom message types. ## Licensing diff --git a/plugins/examples/midi_inverter/src/lib.rs b/plugins/examples/midi_inverter/src/lib.rs index df781fda..d87bd0e0 100644 --- a/plugins/examples/midi_inverter/src/lib.rs +++ b/plugins/examples/midi_inverter/src/lib.rs @@ -40,12 +40,6 @@ impl Plugin for MidiInverter { self.params.clone() } - fn accepts_bus_config(&self, _config: &BusConfig) -> bool { - // This is a zero channel plugin, but the host can configure it to pass through audio as - // needed. Ableton Live for instance doesn't support note effects. - true - } - fn process( &mut self, _buffer: &mut Buffer, diff --git a/plugins/examples/sysex/Cargo.toml b/plugins/examples/sysex/Cargo.toml new file mode 100644 index 00000000..b9cebefb --- /dev/null +++ b/plugins/examples/sysex/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "sysex" +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/sysex/src/lib.rs b/plugins/examples/sysex/src/lib.rs new file mode 100644 index 00000000..ee7b7149 --- /dev/null +++ b/plugins/examples/sysex/src/lib.rs @@ -0,0 +1,109 @@ +use nih_plug::prelude::*; +use std::sync::Arc; + +#[derive(Default)] +struct SysEx { + params: Arc, +} + +#[derive(Default, Params)] +struct SysExParams {} + +// This is a struct or enum describing all MIDI SysEx messages this plugin will send or receive +#[derive(Debug, Clone, PartialEq)] +enum CoolSysExMessage { + Foo(f32), + Bar { x: u8, y: u8 }, +} + +// This trait is used to convert between `CoolSysExMessage` and the raw MIDI SysEx messages. That +// way the rest of the code doesn't need to bother with parsing or SysEx implementation details. +impl SysExMessage for CoolSysExMessage { + // This is a byte array that is large enough to write all of the messages to + type Buffer = [u8; 6]; + + fn from_buffer(buffer: &[u8]) -> Option { + // `buffer` contains the entire buffer, including headers and the 0xf7 End Of system + // eXclusive byte + match buffer { + [0xf0, 0x69, 0x01, n, 0xf7] => Some(CoolSysExMessage::Foo(*n as f32 / 127.0)), + [0xf0, 0x69, 0x02, x, y, 0xf7] => Some(CoolSysExMessage::Bar { x: *x, y: *y }), + _ => None, + } + } + + fn to_buffer(self) -> (Self::Buffer, usize) { + // `Self::Buffer` needs to have a fixed size, so the result needs to be padded, and we + // return the message's actual length in bytes alongside it so the caller can trim the + // excess padding + match self { + CoolSysExMessage::Foo(x) => ([0xf0, 0x69, 0x01, (x * 127.0).round() as u8, 0xf7, 0], 5), + CoolSysExMessage::Bar { x, y } => ([0xf0, 0x69, 0x02, x, y, 0xf7], 6), + } + } +} + +impl Plugin for SysEx { + const NAME: &'static str = "SysEx Example"; + 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 = env!("CARGO_PKG_VERSION"); + + const DEFAULT_INPUT_CHANNELS: u32 = 0; + const DEFAULT_OUTPUT_CHANNELS: u32 = 0; + + const SAMPLE_ACCURATE_AUTOMATION: bool = true; + + type SysExMessage = CoolSysExMessage; + type BackgroundTask = (); + + fn params(&self) -> Arc { + self.params.clone() + } + + fn process( + &mut self, + _buffer: &mut Buffer, + _aux: &mut AuxiliaryBuffers, + context: &mut impl ProcessContext, + ) -> ProcessStatus { + // This example converts one of the two messages into the other + while let Some(event) = context.next_event() { + if let NoteEvent::MidiSysEx { timing, message } = event { + let new_message = match message { + CoolSysExMessage::Foo(x) => CoolSysExMessage::Bar { + x: (x * 127.0).round() as u8, + y: 69, + }, + CoolSysExMessage::Bar { x, y: _ } => CoolSysExMessage::Foo(x as f32 / 127.0), + }; + + context.send_event(NoteEvent::MidiSysEx { + timing, + message: new_message, + }); + } + } + + ProcessStatus::Normal + } +} + +impl ClapPlugin for SysEx { + const CLAP_ID: &'static str = "com.moist-plugins-gmbh.sysex"; + const CLAP_DESCRIPTION: Option<&'static str> = + Some("An example plugin to demonstrate sending and receiving SysEx"); + const CLAP_MANUAL_URL: Option<&'static str> = Some(Self::URL); + const CLAP_SUPPORT_URL: Option<&'static str> = None; + const CLAP_FEATURES: &'static [ClapFeature] = &[ClapFeature::NoteEffect, ClapFeature::Utility]; +} + +impl Vst3Plugin for SysEx { + const VST3_CLASS_ID: [u8; 16] = *b"SysExCoolPluginn"; + const VST3_CATEGORIES: &'static str = "Fx|Instrument|Tools"; +} + +nih_export_clap!(SysEx); +nih_export_vst3!(SysEx);