diff --git a/Cargo.lock b/Cargo.lock index a7060b65..c4ed59c3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4384,6 +4384,14 @@ dependencies = [ "nih_plug", ] +[[package]] +name = "task" +version = "0.1.0" +dependencies = [ + "crossbeam", + "nih_plug", +] + [[package]] name = "tempfile" version = "3.9.0" diff --git a/Cargo.toml b/Cargo.toml index 3181b255..55fc2801 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,7 @@ members = [ "plugins/examples/sine", "plugins/examples/stft", "plugins/examples/sysex", + "plugins/examples/task", "plugins/soft_vacuum", "plugins/buffr_glitch", diff --git a/plugins/examples/task/Cargo.toml b/plugins/examples/task/Cargo.toml new file mode 100644 index 00000000..275587dd --- /dev/null +++ b/plugins/examples/task/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "task" +version = "0.1.0" +edition = "2021" +authors = ["Brian Edwards "] +homepage = "https://github.com/robbert-vdh/nih-plug" +license = "GPL-3.0-or-later" +description = "Background task example" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +nih_plug = { path = "../../../", features = ["assert_process_allocs"] } +crossbeam = "0.8" diff --git a/plugins/examples/task/README.md b/plugins/examples/task/README.md new file mode 100644 index 00000000..2f63619f --- /dev/null +++ b/plugins/examples/task/README.md @@ -0,0 +1,38 @@ +# Background task execution example + +## Build and bundle the VST and CLAP plugins + +```shell +$ cargo xtask bundle task --release +``` + +## One option is to run in Bitwig Studio on Mac + +``` +$ NIH_LOG=/tmp/nih.log open /Applications/Bitwig\ Studio.app +$ tail -f /tmp/nih.log +``` + +### in Bitwig Studio + +* Show Browser Panel -> File Browser -> Add Plug-in Location +* Settings -> Plug-ins -> Per Plug-in Overrides +* add the "nih-plug task example" plugin to a track + +### tail output + +``` +11:54:31 [INFO] initialize: initializing the plugin +11:54:33 [INFO] task: task run from initialize method +11:54:33 [INFO] initialize: sent from task +``` + +* press play on the transport in the host/DAW + +``` +11:54:45 [INFO] process: processing first buffer after play pressed +11:54:47 [INFO] task: task run from process method +11:54:47 [INFO] process: sent from task +11:54:49 [INFO] task: task run from process method +11:54:49 [INFO] process: sent from task +``` diff --git a/plugins/examples/task/src/lib.rs b/plugins/examples/task/src/lib.rs new file mode 100644 index 00000000..85d87520 --- /dev/null +++ b/plugins/examples/task/src/lib.rs @@ -0,0 +1,132 @@ +use crossbeam::channel::{unbounded, Receiver, Sender}; +use nih_plug::prelude::*; +use std::{ + sync::{Arc, Mutex}, + thread::sleep, + time::Duration, +}; + +// Name the plugin's tasks +enum Task { + HelloWorld, +} + +struct MyPlugin { + params: Arc, + greeting: Arc>, + channel: (Sender, Receiver), +} + +impl Default for MyPlugin { + fn default() -> Self { + Self { + params: Arc::new(MyParams::default()), + greeting: Arc::new(Mutex::new(String::from("hello world"))), + channel: unbounded(), + } + } +} + +impl Plugin for MyPlugin { + // Identify the enum as the type the plugin uses for task names + type BackgroundTask = Task; + + // Implement the plugin's task runner by switching on task name. + // - Called after the plugin instance is created + // - Send result back over a channel or triple buffer + fn task_executor(&mut self) -> TaskExecutor { + let greeting = self.greeting.clone(); + let s = self.channel.0.clone(); + Box::new(move |task| match task { + Task::HelloWorld => { + sleep(Duration::from_secs(2)); + nih_log!("task: {}", greeting.lock().unwrap()); + s.send(String::from("sent from task")).unwrap(); + } + }) + } + + fn initialize( + &mut self, + _audio_io_layout: &AudioIOLayout, + _buffer_config: &BufferConfig, + context: &mut impl InitContext, + ) -> bool { + nih_log!("initialize: initializing the plugin"); + *(self.greeting.lock().unwrap()) = String::from("task run from initialize method"); + + // Run synchronously + context.execute(Self::BackgroundTask::HelloWorld); + + // log messages from background task + while let Ok(message) = self.channel.1.try_recv() { + nih_log!("initialize: {message}"); + } + + true + } + + fn reset(&mut self) {} + + fn process( + &mut self, + _buffer: &mut Buffer, + _aux: &mut AuxiliaryBuffers, + context: &mut impl ProcessContext, + ) -> ProcessStatus { + *(self.greeting.lock().unwrap()) = String::from("task run from process method"); + let transport = context.transport(); + + if transport.playing && 0 == transport.pos_samples().unwrap_or_default() { + nih_log!("process: processing first buffer after play pressed"); + + // Run on a background thread + context.execute_background(Self::BackgroundTask::HelloWorld); + + // Waits for previous run of same task to complete + context.execute_background(Self::BackgroundTask::HelloWorld); + } + + // log messages from background task + while let Ok(message) = self.channel.1.try_recv() { + nih_log!("process: {message}"); + } + + ProcessStatus::Normal + } + + fn params(&self) -> Arc { + self.params.clone() + } + + type SysExMessage = (); + + const NAME: &'static str = "nih-plug task example"; + const VENDOR: &'static str = "Brian Edwards"; + const URL: &'static str = env!("CARGO_PKG_HOMEPAGE"); + const EMAIL: &'static str = "brian.edwards@jalopymusic.com"; + const VERSION: &'static str = env!("CARGO_PKG_VERSION"); + const AUDIO_IO_LAYOUTS: &'static [AudioIOLayout] = &[]; + const MIDI_INPUT: MidiConfig = MidiConfig::None; + const MIDI_OUTPUT: MidiConfig = MidiConfig::None; + const SAMPLE_ACCURATE_AUTOMATION: bool = true; +} + +#[derive(Default, Params)] +struct MyParams {} + +impl ClapPlugin for MyPlugin { + const CLAP_ID: &'static str = "com.jalopymusic.nih-plug-task"; + const CLAP_DESCRIPTION: Option<&'static str> = Some("nih-plug background task example"); + const CLAP_MANUAL_URL: Option<&'static str> = Some(Self::URL); + const CLAP_SUPPORT_URL: Option<&'static str> = Some(Self::URL); + const CLAP_FEATURES: &'static [ClapFeature] = &[]; +} + +impl Vst3Plugin for MyPlugin { + const VST3_CLASS_ID: [u8; 16] = *b"NihPlugTaskExamp"; + const VST3_SUBCATEGORIES: &'static [Vst3SubCategory] = &[]; +} + +nih_export_clap!(MyPlugin); +nih_export_vst3!(MyPlugin);