1
0
Fork 0

Add a NoteEvent::MidiSysEx variant

Right now this isn't used, and there is not yet any way to convert to
and from raw SysEx data.
This commit is contained in:
Robbert van der Helm 2023-01-31 18:29:43 +01:00
parent c8ed795524
commit b7849f9a7a
17 changed files with 141 additions and 85 deletions

View file

@ -18,6 +18,10 @@ code then it will not be listed here.
type SysExMessage = (); type SysExMessage = ();
``` ```
- As the result of the above change, `NoteEvent` is now parameterized by a
`SysExMessage` type. There is a new `PluginNoteEvent<P>` type synonym that can
be parameterized by a `Plugin` to make using this slightly less verbose.
## [2023-01-12] ## [2023-01-12]
- The Vizia dependency has been updated. This updated version uses a new text - The Vizia dependency has been updated. This updated version uses a new text

View file

@ -1,7 +1,7 @@
//! A context passed during the process function. //! A context passed during the process function.
use super::PluginApi; use super::PluginApi;
use crate::midi::NoteEvent; use crate::midi::PluginNoteEvent;
use crate::plugin::Plugin; use crate::plugin::Plugin;
/// Contains both context data and callbacks the plugin can use during processing. Most notably this /// Contains both context data and callbacks the plugin can use during processing. Most notably this
@ -73,12 +73,12 @@ pub trait ProcessContext<P: Plugin> {
/// ///
/// ProcessStatus::Normal /// ProcessStatus::Normal
/// ``` /// ```
fn next_event(&mut self) -> Option<NoteEvent>; fn next_event(&mut self) -> Option<PluginNoteEvent<P>>;
/// Send an event to the host. Only available when /// Send an event to the host. Only available when
/// [`Plugin::MIDI_OUTPUT`][crate::prelude::Plugin::MIDI_INPUT] is set. Will not do anything /// [`Plugin::MIDI_OUTPUT`][crate::prelude::Plugin::MIDI_INPUT] is set. Will not do anything
/// otherwise. /// otherwise.
fn send_event(&mut self, event: NoteEvent); fn send_event(&mut self, event: PluginNoteEvent<P>);
/// Update the current latency of the plugin. If the plugin is currently processing audio, then /// Update the current latency of the plugin. If the plugin is currently processing audio, then
/// this may cause audio playback to be restarted. /// this may cause audio playback to be restarted.

View file

@ -2,10 +2,22 @@
use midi_consts::channel_event as midi; use midi_consts::channel_event as midi;
use self::sysex::SysExMessage;
use crate::plugin::Plugin;
pub mod sysex; pub mod sysex;
pub use midi_consts::channel_event::control_change; pub use midi_consts::channel_event::control_change;
/// A plugin-specific note event type.
///
/// The reason why this is defined like this instead of parameterizing `NoteEvent` with `P`` is
/// because deriving trait bounds requires all of the plugin's generic parameters to implement those
/// traits. And we can't require `P` to implement things like `Clone`.
///
/// <https://github.com/rust-lang/rust/issues/26925>
pub type PluginNoteEvent<P> = NoteEvent<<P as Plugin>::SysExMessage>;
/// Determines which note events a plugin receives. /// Determines which note events a plugin receives.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum MidiConfig { pub enum MidiConfig {
@ -31,7 +43,7 @@ pub enum MidiConfig {
/// numbers are zero-indexed. /// numbers are zero-indexed.
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Debug, Clone, Copy, PartialEq)]
#[non_exhaustive] #[non_exhaustive]
pub enum NoteEvent { pub enum NoteEvent<S: SysExMessage> {
/// A note on event, available on [`MidiConfig::Basic`] and up. /// A note on event, available on [`MidiConfig::Basic`] and up.
NoteOn { NoteOn {
timing: u32, timing: u32,
@ -299,9 +311,13 @@ pub enum NoteEvent {
/// The program number, in `0..128`. /// The program number, in `0..128`.
program: u8, program: u8,
}, },
/// A MIDI SysEx message supported by the plugin's `SysExMessage` type. If the conversion from
/// the raw byte array fails (e.g. the plugin doesn't support this kind of message), then this
/// will be logged during debug builds of the plugin, and no event is emitted.
MidiSysEx { timing: u32, message: S },
} }
impl NoteEvent { impl<S: SysExMessage> NoteEvent<S> {
/// Returns the sample within the current buffer this event belongs to. /// Returns the sample within the current buffer this event belongs to.
pub fn timing(&self) -> u32 { pub fn timing(&self) -> u32 {
match self { match self {
@ -322,6 +338,7 @@ impl NoteEvent {
NoteEvent::MidiPitchBend { timing, .. } => *timing, NoteEvent::MidiPitchBend { timing, .. } => *timing,
NoteEvent::MidiCC { timing, .. } => *timing, NoteEvent::MidiCC { timing, .. } => *timing,
NoteEvent::MidiProgramChange { timing, .. } => *timing, NoteEvent::MidiProgramChange { timing, .. } => *timing,
NoteEvent::MidiSysEx { timing, .. } => *timing,
} }
} }
@ -345,9 +362,13 @@ impl NoteEvent {
NoteEvent::MidiPitchBend { .. } => None, NoteEvent::MidiPitchBend { .. } => None,
NoteEvent::MidiCC { .. } => None, NoteEvent::MidiCC { .. } => None,
NoteEvent::MidiProgramChange { .. } => None, NoteEvent::MidiProgramChange { .. } => None,
NoteEvent::MidiSysEx { .. } => None,
} }
} }
// TODO: `[u8; 3]` doesn't work anymore with SysEx. We can wrap this in an
// `enum MidiBuffer<P> { simple: [u8; 3], sysex: P::SysExMessage::Buffer }`.
/// Parse MIDI into a [`NoteEvent`]. Will return `Err(event_type)` if the parsing failed. /// Parse MIDI into a [`NoteEvent`]. Will return `Err(event_type)` if the parsing failed.
pub fn from_midi(timing: u32, midi_data: [u8; 3]) -> Result<Self, u8> { pub fn from_midi(timing: u32, midi_data: [u8; 3]) -> Result<Self, u8> {
// TODO: Maybe add special handling for 14-bit CCs and RPN messages at some // TODO: Maybe add special handling for 14-bit CCs and RPN messages at some
@ -408,6 +429,7 @@ impl NoteEvent {
channel, channel,
program: midi_data[1], program: midi_data[1],
}), }),
// TODO: SysEx
n => Err(n), n => Err(n),
} }
} }
@ -500,6 +522,8 @@ impl NoteEvent {
| NoteEvent::PolyVibrato { .. } | NoteEvent::PolyVibrato { .. }
| NoteEvent::PolyExpression { .. } | NoteEvent::PolyExpression { .. }
| NoteEvent::PolyBrightness { .. } => None, | NoteEvent::PolyBrightness { .. } => None,
// TODO: These functions need to handle both simple and longer messages as documented above
NoteEvent::MidiSysEx { .. } => None,
} }
} }
@ -524,6 +548,7 @@ impl NoteEvent {
NoteEvent::MidiPitchBend { timing, .. } => *timing -= samples, NoteEvent::MidiPitchBend { timing, .. } => *timing -= samples,
NoteEvent::MidiCC { timing, .. } => *timing -= samples, NoteEvent::MidiCC { timing, .. } => *timing -= samples,
NoteEvent::MidiProgramChange { timing, .. } => *timing -= samples, NoteEvent::MidiProgramChange { timing, .. } => *timing -= samples,
NoteEvent::MidiSysEx { timing, .. } => *timing -= samples,
} }
} }
} }
@ -536,7 +561,7 @@ mod tests {
#[test] #[test]
fn test_note_on_midi_conversion() { fn test_note_on_midi_conversion() {
let event = NoteEvent::NoteOn { let event = NoteEvent::<()>::NoteOn {
timing: TIMING, timing: TIMING,
voice_id: None, voice_id: None,
channel: 1, channel: 1,
@ -553,7 +578,7 @@ mod tests {
#[test] #[test]
fn test_note_off_midi_conversion() { fn test_note_off_midi_conversion() {
let event = NoteEvent::NoteOff { let event = NoteEvent::<()>::NoteOff {
timing: TIMING, timing: TIMING,
voice_id: None, voice_id: None,
channel: 1, channel: 1,
@ -569,7 +594,7 @@ mod tests {
#[test] #[test]
fn test_poly_pressure_midi_conversion() { fn test_poly_pressure_midi_conversion() {
let event = NoteEvent::PolyPressure { let event = NoteEvent::<()>::PolyPressure {
timing: TIMING, timing: TIMING,
voice_id: None, voice_id: None,
channel: 1, channel: 1,
@ -585,7 +610,7 @@ mod tests {
#[test] #[test]
fn test_channel_pressure_midi_conversion() { fn test_channel_pressure_midi_conversion() {
let event = NoteEvent::MidiChannelPressure { let event = NoteEvent::<()>::MidiChannelPressure {
timing: TIMING, timing: TIMING,
channel: 1, channel: 1,
pressure: 0.6929134, pressure: 0.6929134,
@ -599,7 +624,7 @@ mod tests {
#[test] #[test]
fn test_pitch_bend_midi_conversion() { fn test_pitch_bend_midi_conversion() {
let event = NoteEvent::MidiPitchBend { let event = NoteEvent::<()>::MidiPitchBend {
timing: TIMING, timing: TIMING,
channel: 1, channel: 1,
value: 0.6929134, value: 0.6929134,
@ -613,7 +638,7 @@ mod tests {
#[test] #[test]
fn test_cc_midi_conversion() { fn test_cc_midi_conversion() {
let event = NoteEvent::MidiCC { let event = NoteEvent::<()>::MidiCC {
timing: TIMING, timing: TIMING,
channel: 1, channel: 1,
cc: 2, cc: 2,
@ -628,7 +653,7 @@ mod tests {
#[test] #[test]
fn test_program_change_midi_conversion() { fn test_program_change_midi_conversion() {
let event = NoteEvent::MidiProgramChange { let event = NoteEvent::<()>::MidiProgramChange {
timing: TIMING, timing: TIMING,
channel: 1, channel: 1,
program: 42, program: 42,
@ -639,4 +664,6 @@ mod tests {
event event
); );
} }
// TODO: SysEx conversion
} }

View file

@ -17,7 +17,7 @@ pub use crate::context::process::ProcessContext;
// This also includes the derive macro // This also includes the derive macro
pub use crate::editor::{Editor, ParentWindowHandle}; pub use crate::editor::{Editor, ParentWindowHandle};
pub use crate::midi::sysex::SysExMessage; pub use crate::midi::sysex::SysExMessage;
pub use crate::midi::{control_change, MidiConfig, NoteEvent}; pub use crate::midi::{control_change, MidiConfig, NoteEvent, PluginNoteEvent};
pub use crate::params::enums::{Enum, EnumParam}; pub use crate::params::enums::{Enum, EnumParam};
pub use crate::params::internals::ParamPtr; pub use crate::params::internals::ParamPtr;
pub use crate::params::range::{FloatRange, IntRange}; pub use crate::params::range::{FloatRange, IntRange};

View file

@ -9,7 +9,7 @@ use crate::context::init::InitContext;
use crate::context::process::{ProcessContext, Transport}; use crate::context::process::{ProcessContext, Transport};
use crate::context::PluginApi; use crate::context::PluginApi;
use crate::event_loop::EventLoop; use crate::event_loop::EventLoop;
use crate::midi::NoteEvent; use crate::midi::PluginNoteEvent;
use crate::params::internals::ParamPtr; use crate::params::internals::ParamPtr;
use crate::plugin::ClapPlugin; use crate::plugin::ClapPlugin;
@ -37,8 +37,8 @@ pub(crate) struct PendingInitContextRequests {
/// unnecessary atomic operations to lock the uncontested RwLocks. /// unnecessary atomic operations to lock the uncontested RwLocks.
pub(crate) struct WrapperProcessContext<'a, P: ClapPlugin> { pub(crate) struct WrapperProcessContext<'a, P: ClapPlugin> {
pub(super) wrapper: &'a Wrapper<P>, pub(super) wrapper: &'a Wrapper<P>,
pub(super) input_events_guard: AtomicRefMut<'a, VecDeque<NoteEvent>>, pub(super) input_events_guard: AtomicRefMut<'a, VecDeque<PluginNoteEvent<P>>>,
pub(super) output_events_guard: AtomicRefMut<'a, VecDeque<NoteEvent>>, pub(super) output_events_guard: AtomicRefMut<'a, VecDeque<PluginNoteEvent<P>>>,
pub(super) transport: Transport, pub(super) transport: Transport,
} }
@ -96,11 +96,11 @@ impl<P: ClapPlugin> ProcessContext<P> for WrapperProcessContext<'_, P> {
&self.transport &self.transport
} }
fn next_event(&mut self) -> Option<NoteEvent> { fn next_event(&mut self) -> Option<PluginNoteEvent<P>> {
self.input_events_guard.pop_front() self.input_events_guard.pop_front()
} }
fn send_event(&mut self, event: NoteEvent) { fn send_event(&mut self, event: PluginNoteEvent<P>) {
self.output_events_guard.push_back(event); self.output_events_guard.push_back(event);
} }

View file

@ -81,7 +81,7 @@ use crate::context::gui::AsyncExecutor;
use crate::context::process::Transport; use crate::context::process::Transport;
use crate::editor::{Editor, ParentWindowHandle}; use crate::editor::{Editor, ParentWindowHandle};
use crate::event_loop::{BackgroundThread, EventLoop, MainThreadExecutor, TASK_QUEUE_CAPACITY}; use crate::event_loop::{BackgroundThread, EventLoop, MainThreadExecutor, TASK_QUEUE_CAPACITY};
use crate::midi::{MidiConfig, NoteEvent}; use crate::midi::{MidiConfig, NoteEvent, PluginNoteEvent};
use crate::params::internals::ParamPtr; use crate::params::internals::ParamPtr;
use crate::params::{ParamFlags, Params}; use crate::params::{ParamFlags, Params};
use crate::plugin::{ use crate::plugin::{
@ -136,10 +136,10 @@ pub struct Wrapper<P: ClapPlugin> {
/// ///
/// TODO: Maybe load these lazily at some point instead of needing to spool them all to this /// TODO: Maybe load these lazily at some point instead of needing to spool them all to this
/// queue first /// queue first
input_events: AtomicRefCell<VecDeque<NoteEvent>>, input_events: AtomicRefCell<VecDeque<PluginNoteEvent<P>>>,
/// Stores any events the plugin has output during the current processing cycle, analogous to /// Stores any events the plugin has output during the current processing cycle, analogous to
/// `input_events`. /// `input_events`.
output_events: AtomicRefCell<VecDeque<NoteEvent>>, output_events: AtomicRefCell<VecDeque<PluginNoteEvent<P>>>,
/// The last process status returned by the plugin. This is used for tail handling. /// The last process status returned by the plugin. This is used for tail handling.
last_process_status: AtomicCell<ProcessStatus>, last_process_status: AtomicCell<ProcessStatus>,
/// The current latency in samples, as set by the plugin through the [`ProcessContext`]. uses /// The current latency in samples, as set by the plugin through the [`ProcessContext`]. uses
@ -1356,7 +1356,7 @@ impl<P: ClapPlugin> Wrapper<P> {
pub unsafe fn handle_in_event( pub unsafe fn handle_in_event(
&self, &self,
event: *const clap_event_header, event: *const clap_event_header,
input_events: &mut AtomicRefMut<VecDeque<NoteEvent>>, input_events: &mut AtomicRefMut<VecDeque<PluginNoteEvent<P>>>,
transport_info: Option<&mut *const clap_event_transport>, transport_info: Option<&mut *const clap_event_transport>,
current_sample_idx: usize, current_sample_idx: usize,
) { ) {

View file

@ -173,7 +173,7 @@ pub fn nih_export_standalone_with_args<P: Plugin, Args: IntoIterator<Item = Stri
} }
} }
fn run_wrapper<P: Plugin, B: Backend>(backend: B, config: WrapperConfig) -> bool { fn run_wrapper<P: Plugin, B: Backend<P>>(backend: B, config: WrapperConfig) -> bool {
let wrapper = match Wrapper::<P, _>::new(backend, config) { let wrapper = match Wrapper::<P, _>::new(backend, config) {
Ok(wrapper) => wrapper, Ok(wrapper) => wrapper,
Err(err) => { Err(err) => {

View file

@ -1,5 +1,5 @@
use crate::context::process::Transport; use crate::context::process::Transport;
use crate::midi::NoteEvent; use crate::midi::PluginNoteEvent;
mod cpal; mod cpal;
mod dummy; mod dummy;
@ -9,9 +9,10 @@ pub use self::cpal::Cpal;
pub use self::dummy::Dummy; pub use self::dummy::Dummy;
pub use self::jack::Jack; pub use self::jack::Jack;
pub use crate::buffer::Buffer; pub use crate::buffer::Buffer;
pub use crate::plugin::Plugin;
/// An audio+MIDI backend for the standalone wrapper. /// An audio+MIDI backend for the standalone wrapper.
pub trait Backend: 'static + Send + Sync { pub trait Backend<P: Plugin>: 'static + Send + Sync {
/// Start processing audio and MIDI on this thread. The process callback will be called whenever /// Start processing audio and MIDI on this thread. The process callback will be called whenever
/// there's a new block of audio to be processed. The process callback receives the audio /// there's a new block of audio to be processed. The process callback receives the audio
/// buffers for the wrapped plugin's outputs. Any inputs will have already been copied to this /// buffers for the wrapped plugin's outputs. Any inputs will have already been copied to this
@ -20,7 +21,12 @@ pub trait Backend: 'static + Send + Sync {
/// TODO: Auxiliary inputs and outputs /// TODO: Auxiliary inputs and outputs
fn run( fn run(
&mut self, &mut self,
cb: impl FnMut(&mut Buffer, Transport, &[NoteEvent], &mut Vec<NoteEvent>) -> bool cb: impl FnMut(
&mut Buffer,
Transport,
&[PluginNoteEvent<P>],
&mut Vec<PluginNoteEvent<P>>,
) -> bool
+ 'static + 'static
+ Send, + Send,
); );

View file

@ -10,7 +10,7 @@ use super::super::config::WrapperConfig;
use super::Backend; use super::Backend;
use crate::buffer::Buffer; use crate::buffer::Buffer;
use crate::context::process::Transport; use crate::context::process::Transport;
use crate::midi::{MidiConfig, NoteEvent}; use crate::midi::{MidiConfig, PluginNoteEvent};
use crate::plugin::{AuxiliaryIOConfig, BusConfig, Plugin}; use crate::plugin::{AuxiliaryIOConfig, BusConfig, Plugin};
/// Uses CPAL for audio and midir for MIDI. /// Uses CPAL for audio and midir for MIDI.
@ -26,10 +26,15 @@ pub struct Cpal {
// TODO: MIDI // TODO: MIDI
} }
impl Backend for Cpal { impl<P: Plugin> Backend<P> for Cpal {
fn run( fn run(
&mut self, &mut self,
cb: impl FnMut(&mut Buffer, Transport, &[NoteEvent], &mut Vec<NoteEvent>) -> bool cb: impl FnMut(
&mut Buffer,
Transport,
&[PluginNoteEvent<P>],
&mut Vec<PluginNoteEvent<P>>,
) -> bool
+ 'static + 'static
+ Send, + Send,
) { ) {
@ -100,17 +105,17 @@ impl Backend for Cpal {
let output_stream = match self.output_sample_format { let output_stream = match self.output_sample_format {
SampleFormat::I16 => self.output_device.build_output_stream( SampleFormat::I16 => self.output_device.build_output_stream(
&self.output_config, &self.output_config,
self.build_output_data_callback::<i16>(unparker, input_rb_consumer, cb), self.build_output_data_callback::<P, i16>(unparker, input_rb_consumer, cb),
error_cb, error_cb,
), ),
SampleFormat::U16 => self.output_device.build_output_stream( SampleFormat::U16 => self.output_device.build_output_stream(
&self.output_config, &self.output_config,
self.build_output_data_callback::<u16>(unparker, input_rb_consumer, cb), self.build_output_data_callback::<P, u16>(unparker, input_rb_consumer, cb),
error_cb, error_cb,
), ),
SampleFormat::F32 => self.output_device.build_output_stream( SampleFormat::F32 => self.output_device.build_output_stream(
&self.output_config, &self.output_config,
self.build_output_data_callback::<f32>(unparker, input_rb_consumer, cb), self.build_output_data_callback::<P, f32>(unparker, input_rb_consumer, cb),
error_cb, error_cb,
), ),
} }
@ -306,11 +311,16 @@ impl Cpal {
} }
} }
fn build_output_data_callback<T: Sample>( fn build_output_data_callback<P: Plugin, T: Sample>(
&self, &self,
unparker: Unparker, unparker: Unparker,
mut input_rb_consumer: Option<rtrb::Consumer<f32>>, mut input_rb_consumer: Option<rtrb::Consumer<f32>>,
mut cb: impl FnMut(&mut Buffer, Transport, &[NoteEvent], &mut Vec<NoteEvent>) -> bool mut cb: impl FnMut(
&mut Buffer,
Transport,
&[PluginNoteEvent<P>],
&mut Vec<PluginNoteEvent<P>>,
) -> bool
+ 'static + 'static
+ Send, + Send,
) -> impl FnMut(&mut [T], &OutputCallbackInfo) + Send + 'static { ) -> impl FnMut(&mut [T], &OutputCallbackInfo) + Send + 'static {

View file

@ -4,7 +4,7 @@ use super::super::config::WrapperConfig;
use super::Backend; use super::Backend;
use crate::buffer::Buffer; use crate::buffer::Buffer;
use crate::context::process::Transport; use crate::context::process::Transport;
use crate::midi::NoteEvent; use crate::midi::PluginNoteEvent;
use crate::plugin::{AuxiliaryIOConfig, BusConfig, Plugin}; use crate::plugin::{AuxiliaryIOConfig, BusConfig, Plugin};
/// This backend doesn't input or output any audio or MIDI. It only exists so the standalone /// This backend doesn't input or output any audio or MIDI. It only exists so the standalone
@ -15,10 +15,15 @@ pub struct Dummy {
bus_config: BusConfig, bus_config: BusConfig,
} }
impl Backend for Dummy { impl<P: Plugin> Backend<P> for Dummy {
fn run( fn run(
&mut self, &mut self,
mut cb: impl FnMut(&mut Buffer, Transport, &[NoteEvent], &mut Vec<NoteEvent>) -> bool mut cb: impl FnMut(
&mut Buffer,
Transport,
&[PluginNoteEvent<P>],
&mut Vec<PluginNoteEvent<P>>,
) -> bool
+ 'static + 'static
+ Send, + Send,
) { ) {

View file

@ -12,7 +12,7 @@ use super::super::config::WrapperConfig;
use super::Backend; use super::Backend;
use crate::buffer::Buffer; use crate::buffer::Buffer;
use crate::context::process::Transport; use crate::context::process::Transport;
use crate::midi::{MidiConfig, NoteEvent}; use crate::midi::{MidiConfig, NoteEvent, PluginNoteEvent};
use crate::plugin::Plugin; use crate::plugin::Plugin;
/// Uses JACK audio and MIDI. /// Uses JACK audio and MIDI.
@ -28,10 +28,15 @@ pub struct Jack {
midi_output: Option<Arc<Mutex<Port<MidiOut>>>>, midi_output: Option<Arc<Mutex<Port<MidiOut>>>>,
} }
impl Backend for Jack { impl<P: Plugin> Backend<P> for Jack {
fn run( fn run(
&mut self, &mut self,
mut cb: impl FnMut(&mut Buffer, Transport, &[NoteEvent], &mut Vec<NoteEvent>) -> bool mut cb: impl FnMut(
&mut Buffer,
Transport,
&[PluginNoteEvent<P>],
&mut Vec<PluginNoteEvent<P>>,
) -> bool
+ 'static + 'static
+ Send, + Send,
) { ) {
@ -45,8 +50,8 @@ impl Backend for Jack {
}) })
} }
let mut input_events = Vec::with_capacity(2048); let mut input_events: Vec<PluginNoteEvent<P>> = Vec::with_capacity(2048);
let mut output_events = Vec::with_capacity(2048); let mut output_events: Vec<PluginNoteEvent<P>> = Vec::with_capacity(2048);
// This thread needs to be blocked until processing is finished // This thread needs to be blocked until processing is finished
let parker = Parker::new(); let parker = Parker::new();

View file

@ -7,33 +7,33 @@ use crate::context::gui::GuiContext;
use crate::context::init::InitContext; use crate::context::init::InitContext;
use crate::context::process::{ProcessContext, Transport}; use crate::context::process::{ProcessContext, Transport};
use crate::context::PluginApi; use crate::context::PluginApi;
use crate::midi::NoteEvent; use crate::midi::PluginNoteEvent;
use crate::params::internals::ParamPtr; use crate::params::internals::ParamPtr;
use crate::plugin::Plugin; use crate::plugin::Plugin;
/// An [`InitContext`] implementation for the standalone wrapper. /// An [`InitContext`] implementation for the standalone wrapper.
pub(crate) struct WrapperInitContext<'a, P: Plugin, B: Backend> { pub(crate) struct WrapperInitContext<'a, P: Plugin, B: Backend<P>> {
pub(super) wrapper: &'a Wrapper<P, B>, pub(super) wrapper: &'a Wrapper<P, B>,
} }
/// A [`ProcessContext`] implementation for the standalone wrapper. This is a separate object so it /// A [`ProcessContext`] implementation for the standalone wrapper. This is a separate object so it
/// can hold on to lock guards for event queues. Otherwise reading these events would require /// can hold on to lock guards for event queues. Otherwise reading these events would require
/// constant unnecessary atomic operations to lock the uncontested RwLocks. /// constant unnecessary atomic operations to lock the uncontested RwLocks.
pub(crate) struct WrapperProcessContext<'a, P: Plugin, B: Backend> { pub(crate) struct WrapperProcessContext<'a, P: Plugin, B: Backend<P>> {
#[allow(dead_code)] #[allow(dead_code)]
pub(super) wrapper: &'a Wrapper<P, B>, pub(super) wrapper: &'a Wrapper<P, B>,
pub(super) input_events: &'a [NoteEvent], pub(super) input_events: &'a [PluginNoteEvent<P>],
// The current index in `input_events`, since we're not actually popping anything from a queue // The current index in `input_events`, since we're not actually popping anything from a queue
// here to keep the standalone backend implementation a bit more flexible // here to keep the standalone backend implementation a bit more flexible
pub(super) input_events_idx: usize, pub(super) input_events_idx: usize,
pub(super) output_events: &'a mut Vec<NoteEvent>, pub(super) output_events: &'a mut Vec<PluginNoteEvent<P>>,
pub(super) transport: Transport, pub(super) transport: Transport,
} }
/// A [`GuiContext`] implementation for the wrapper. This is passed to the plugin in /// A [`GuiContext`] implementation for the wrapper. This is passed to the plugin in
/// [`Editor::spawn()`][crate::prelude::Editor::spawn()] so it can interact with the rest of the plugin and /// [`Editor::spawn()`][crate::prelude::Editor::spawn()] so it can interact with the rest of the plugin and
/// with the host for things like setting parameters. /// with the host for things like setting parameters.
pub(crate) struct WrapperGuiContext<P: Plugin, B: Backend> { pub(crate) struct WrapperGuiContext<P: Plugin, B: Backend<P>> {
pub(super) wrapper: Arc<Wrapper<P, B>>, pub(super) wrapper: Arc<Wrapper<P, B>>,
/// This allows us to send tasks to the parent view that will be handled at the start of its /// This allows us to send tasks to the parent view that will be handled at the start of its
@ -41,7 +41,7 @@ pub(crate) struct WrapperGuiContext<P: Plugin, B: Backend> {
pub(super) gui_task_sender: channel::Sender<GuiTask>, pub(super) gui_task_sender: channel::Sender<GuiTask>,
} }
impl<P: Plugin, B: Backend> InitContext<P> for WrapperInitContext<'_, P, B> { impl<P: Plugin, B: Backend<P>> InitContext<P> for WrapperInitContext<'_, P, B> {
fn plugin_api(&self) -> PluginApi { fn plugin_api(&self) -> PluginApi {
PluginApi::Standalone PluginApi::Standalone
} }
@ -59,7 +59,7 @@ impl<P: Plugin, B: Backend> InitContext<P> for WrapperInitContext<'_, P, B> {
} }
} }
impl<P: Plugin, B: Backend> ProcessContext<P> for WrapperProcessContext<'_, P, B> { impl<P: Plugin, B: Backend<P>> ProcessContext<P> for WrapperProcessContext<'_, P, B> {
fn plugin_api(&self) -> PluginApi { fn plugin_api(&self) -> PluginApi {
PluginApi::Standalone PluginApi::Standalone
} }
@ -79,10 +79,10 @@ impl<P: Plugin, B: Backend> ProcessContext<P> for WrapperProcessContext<'_, P, B
&self.transport &self.transport
} }
fn next_event(&mut self) -> Option<NoteEvent> { fn next_event(&mut self) -> Option<PluginNoteEvent<P>> {
// We'll pretend we're a queue, choo choo // We'll pretend we're a queue, choo choo
if self.input_events_idx < self.input_events.len() { if self.input_events_idx < self.input_events.len() {
let event = self.input_events[self.input_events_idx]; let event = self.input_events[self.input_events_idx].clone();
self.input_events_idx += 1; self.input_events_idx += 1;
Some(event) Some(event)
@ -91,7 +91,7 @@ impl<P: Plugin, B: Backend> ProcessContext<P> for WrapperProcessContext<'_, P, B
} }
} }
fn send_event(&mut self, event: NoteEvent) { fn send_event(&mut self, event: PluginNoteEvent<P>) {
self.output_events.push(event); self.output_events.push(event);
} }
@ -104,7 +104,7 @@ impl<P: Plugin, B: Backend> ProcessContext<P> for WrapperProcessContext<'_, P, B
} }
} }
impl<P: Plugin, B: Backend> GuiContext for WrapperGuiContext<P, B> { impl<P: Plugin, B: Backend<P>> GuiContext for WrapperGuiContext<P, B> {
fn plugin_api(&self) -> PluginApi { fn plugin_api(&self) -> PluginApi {
PluginApi::Standalone PluginApi::Standalone
} }

View file

@ -17,7 +17,7 @@ use crate::context::gui::AsyncExecutor;
use crate::context::process::Transport; use crate::context::process::Transport;
use crate::editor::{Editor, ParentWindowHandle}; use crate::editor::{Editor, ParentWindowHandle};
use crate::event_loop::{EventLoop, MainThreadExecutor, OsEventLoop}; use crate::event_loop::{EventLoop, MainThreadExecutor, OsEventLoop};
use crate::midi::NoteEvent; use crate::midi::PluginNoteEvent;
use crate::params::internals::ParamPtr; use crate::params::internals::ParamPtr;
use crate::params::{ParamFlags, Params}; use crate::params::{ParamFlags, Params};
use crate::plugin::{ use crate::plugin::{
@ -32,7 +32,7 @@ use crate::wrapper::util::process_wrapper;
/// than this many parameters at a time will cause changes to get lost. /// than this many parameters at a time will cause changes to get lost.
const EVENT_QUEUE_CAPACITY: usize = 2048; const EVENT_QUEUE_CAPACITY: usize = 2048;
pub struct Wrapper<P: Plugin, B: Backend> { pub struct Wrapper<P: Plugin, B: Backend<P>> {
backend: AtomicRefCell<B>, backend: AtomicRefCell<B>,
/// The wrapped plugin instance. /// The wrapped plugin instance.
@ -154,7 +154,7 @@ impl WindowHandler for WrapperWindowHandler {
} }
} }
impl<P: Plugin, B: Backend> MainThreadExecutor<Task<P>> for Wrapper<P, B> { impl<P: Plugin, B: Backend<P>> MainThreadExecutor<Task<P>> for Wrapper<P, B> {
fn execute(&self, task: Task<P>, _is_gui_thread: bool) { fn execute(&self, task: Task<P>, _is_gui_thread: bool) {
match task { match task {
Task::PluginTask(task) => (self.task_executor.lock())(task), Task::PluginTask(task) => (self.task_executor.lock())(task),
@ -175,7 +175,7 @@ impl<P: Plugin, B: Backend> MainThreadExecutor<Task<P>> for Wrapper<P, B> {
} }
} }
impl<P: Plugin, B: Backend> Wrapper<P, B> { impl<P: Plugin, B: Backend<P>> Wrapper<P, B> {
/// Instantiate a new instance of the standalone wrapper. Returns an error if the plugin does /// Instantiate a new instance of the standalone wrapper. Returns an error if the plugin does
/// not accept the IO configuration from the wrapper config. /// not accept the IO configuration from the wrapper config.
pub fn new(backend: B, config: WrapperConfig) -> Result<Arc<Self>, WrapperError> { pub fn new(backend: B, config: WrapperConfig) -> Result<Arc<Self>, WrapperError> {
@ -602,8 +602,8 @@ impl<P: Plugin, B: Backend> Wrapper<P, B> {
fn make_process_context<'a>( fn make_process_context<'a>(
&'a self, &'a self,
transport: Transport, transport: Transport,
input_events: &'a [NoteEvent], input_events: &'a [PluginNoteEvent<P>],
output_events: &'a mut Vec<NoteEvent>, output_events: &'a mut Vec<PluginNoteEvent<P>>,
) -> WrapperProcessContext<'a, P, B> { ) -> WrapperProcessContext<'a, P, B> {
WrapperProcessContext { WrapperProcessContext {
wrapper: self, wrapper: self,

View file

@ -10,7 +10,7 @@ use crate::context::gui::GuiContext;
use crate::context::init::InitContext; use crate::context::init::InitContext;
use crate::context::process::{ProcessContext, Transport}; use crate::context::process::{ProcessContext, Transport};
use crate::context::PluginApi; use crate::context::PluginApi;
use crate::midi::NoteEvent; use crate::midi::PluginNoteEvent;
use crate::params::internals::ParamPtr; use crate::params::internals::ParamPtr;
use crate::plugin::Vst3Plugin; use crate::plugin::Vst3Plugin;
use crate::wrapper::state::PluginState; use crate::wrapper::state::PluginState;
@ -42,8 +42,8 @@ pub(crate) struct PendingInitContextRequests {
/// unnecessary atomic operations to lock the uncontested locks. /// unnecessary atomic operations to lock the uncontested locks.
pub(crate) struct WrapperProcessContext<'a, P: Vst3Plugin> { pub(crate) struct WrapperProcessContext<'a, P: Vst3Plugin> {
pub(super) inner: &'a WrapperInner<P>, pub(super) inner: &'a WrapperInner<P>,
pub(super) input_events_guard: AtomicRefMut<'a, VecDeque<NoteEvent>>, pub(super) input_events_guard: AtomicRefMut<'a, VecDeque<PluginNoteEvent<P>>>,
pub(super) output_events_guard: AtomicRefMut<'a, VecDeque<NoteEvent>>, pub(super) output_events_guard: AtomicRefMut<'a, VecDeque<PluginNoteEvent<P>>>,
pub(super) transport: Transport, pub(super) transport: Transport,
} }
@ -101,11 +101,11 @@ impl<P: Vst3Plugin> ProcessContext<P> for WrapperProcessContext<'_, P> {
&self.transport &self.transport
} }
fn next_event(&mut self) -> Option<NoteEvent> { fn next_event(&mut self) -> Option<PluginNoteEvent<P>> {
self.input_events_guard.pop_front() self.input_events_guard.pop_front()
} }
fn send_event(&mut self, event: NoteEvent) { fn send_event(&mut self, event: PluginNoteEvent<P>) {
self.output_events_guard.push_back(event); self.output_events_guard.push_back(event);
} }

View file

@ -19,7 +19,7 @@ use crate::context::gui::AsyncExecutor;
use crate::context::process::Transport; use crate::context::process::Transport;
use crate::editor::Editor; use crate::editor::Editor;
use crate::event_loop::{EventLoop, MainThreadExecutor, OsEventLoop}; use crate::event_loop::{EventLoop, MainThreadExecutor, OsEventLoop};
use crate::midi::{MidiConfig, NoteEvent}; use crate::midi::{MidiConfig, PluginNoteEvent};
use crate::params::internals::ParamPtr; use crate::params::internals::ParamPtr;
use crate::params::{ParamFlags, Params}; use crate::params::{ParamFlags, Params};
use crate::plugin::{ use crate::plugin::{
@ -102,10 +102,10 @@ pub(crate) struct WrapperInner<P: Vst3Plugin> {
/// NOTE: Because with VST3 MIDI CC messages are sent as parameter changes and VST3 does not /// NOTE: Because with VST3 MIDI CC messages are sent as parameter changes and VST3 does not
/// interleave parameter changes and note events, this queue has to be sorted when /// interleave parameter changes and note events, this queue has to be sorted when
/// creating the process context /// creating the process context
pub input_events: AtomicRefCell<VecDeque<NoteEvent>>, pub input_events: AtomicRefCell<VecDeque<PluginNoteEvent<P>>>,
/// Stores any events the plugin has output during the current processing cycle, analogous to /// Stores any events the plugin has output during the current processing cycle, analogous to
/// `input_events`. /// `input_events`.
pub output_events: AtomicRefCell<VecDeque<NoteEvent>>, pub output_events: AtomicRefCell<VecDeque<PluginNoteEvent<P>>>,
/// VST3 has several useful predefined note expressions, but for some reason they are the only /// VST3 has several useful predefined note expressions, but for some reason they are the only
/// note event type that don't have MIDI note ID and channel fields. So we need to keep track of /// note event type that don't have MIDI note ID and channel fields. So we need to keep track of
/// the most recent VST3 note IDs we've seen, and then map those back to MIDI note IDs and /// the most recent VST3 note IDs we've seen, and then map those back to MIDI note IDs and
@ -120,7 +120,7 @@ pub(crate) struct WrapperInner<P: Vst3Plugin> {
/// parameter changes and events into a vector at the start of the process call, sort it, and /// parameter changes and events into a vector at the start of the process call, sort it, and
/// then do the block splitting based on that. Note events need to have their timing adjusted to /// then do the block splitting based on that. Note events need to have their timing adjusted to
/// match the block start, since they're all read upfront. /// match the block start, since they're all read upfront.
pub process_events: AtomicRefCell<Vec<ProcessEvent>>, pub process_events: AtomicRefCell<Vec<ProcessEvent<P>>>,
/// The plugin is able to restore state through a method on the `GuiContext`. To avoid changing /// The plugin is able to restore state through a method on the `GuiContext`. To avoid changing
/// parameters mid-processing and running into garbled data if the host also tries to load state /// parameters mid-processing and running into garbled data if the host also tries to load state
/// at the same time the restoring happens at the end of each processing call. If this zero /// at the same time the restoring happens at the end of each processing call. If this zero
@ -177,7 +177,7 @@ pub enum Task<P: Plugin> {
/// sample accurate automation and MIDI CC handling through parameters we need to put all parameter /// sample accurate automation and MIDI CC handling through parameters we need to put all parameter
/// changes and (translated) note events into a sorted array first. /// changes and (translated) note events into a sorted array first.
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub enum ProcessEvent { pub enum ProcessEvent<P: Plugin> {
/// An incoming parameter change sent by the host. This will only be used when sample accurate /// An incoming parameter change sent by the host. This will only be used when sample accurate
/// automation has been enabled, and the parameters are only updated when we process this /// automation has been enabled, and the parameters are only updated when we process this
/// spooled event at the start of a block. /// spooled event at the start of a block.
@ -198,7 +198,7 @@ pub enum ProcessEvent {
timing: u32, timing: u32,
/// The actual note event, make sure to subtract the block start index with /// The actual note event, make sure to subtract the block start index with
/// [`NoteEvent::subtract_timing()`] before putting this into the input event queue. /// [`NoteEvent::subtract_timing()`] before putting this into the input event queue.
event: NoteEvent, event: PluginNoteEvent<P>,
}, },
} }

View file

@ -3,6 +3,7 @@
use vst3_sys::vst::{NoteExpressionValueEvent, NoteOnEvent}; use vst3_sys::vst::{NoteExpressionValueEvent, NoteOnEvent};
use crate::midi::sysex::SysExMessage;
use crate::midi::NoteEvent; use crate::midi::NoteEvent;
type MidiNote = u8; type MidiNote = u8;
@ -100,11 +101,11 @@ impl NoteExpressionController {
/// Translate the note expression value event into an internal NIH-plug event, if we handle the /// Translate the note expression value event into an internal NIH-plug event, if we handle the
/// expression type from the note expression value event. The timing is provided here because we /// expression type from the note expression value event. The timing is provided here because we
/// may be splitting buffers on inter-buffer parameter changes. /// may be splitting buffers on inter-buffer parameter changes.
pub fn translate_event( pub fn translate_event<S: SysExMessage>(
&self, &self,
timing: u32, timing: u32,
event: &NoteExpressionValueEvent, event: &NoteExpressionValueEvent,
) -> Option<NoteEvent> { ) -> Option<NoteEvent<S>> {
// We're calling it a voice ID, VST3 (and CLAP) calls it a note ID // We're calling it a voice ID, VST3 (and CLAP) calls it a note ID
let (note_id, note, channel) = *self let (note_id, note, channel) = *self
.note_ids .note_ids
@ -168,7 +169,7 @@ impl NoteExpressionController {
/// `translate_event()`. /// `translate_event()`.
pub fn translate_event_reverse( pub fn translate_event_reverse(
note_id: i32, note_id: i32,
event: &NoteEvent, event: &NoteEvent<impl SysExMessage>,
) -> Option<NoteExpressionValueEvent> { ) -> Option<NoteExpressionValueEvent> {
match &event { match &event {
NoteEvent::PolyVolume { gain, .. } => Some(NoteExpressionValueEvent { NoteEvent::PolyVolume { gain, .. } => Some(NoteExpressionValueEvent {

View file

@ -1271,7 +1271,7 @@ impl<P: Vst3Plugin> IAudioProcessor for Wrapper<P> {
block_end = data.num_samples as usize; block_end = data.num_samples as usize;
for event_idx in event_start_idx..process_events.len() { for event_idx in event_start_idx..process_events.len() {
match process_events[event_idx] { match &process_events[event_idx] {
ProcessEvent::ParameterChange { ProcessEvent::ParameterChange {
timing, timing,
hash, hash,
@ -1280,24 +1280,22 @@ impl<P: Vst3Plugin> IAudioProcessor for Wrapper<P> {
// If this parameter change happens after the start of this block, then // If this parameter change happens after the start of this block, then
// we'll split the block here and handle this parameter change after // we'll split the block here and handle this parameter change after
// we've processed this block // we've processed this block
if timing != block_start as u32 { if *timing != block_start as u32 {
event_start_idx = event_idx; event_start_idx = event_idx;
block_end = timing as usize; block_end = *timing as usize;
break; break;
} }
self.inner.set_normalized_value_by_hash( self.inner.set_normalized_value_by_hash(
hash, *hash,
normalized_value, *normalized_value,
Some(sample_rate), Some(sample_rate),
); );
} }
ProcessEvent::NoteEvent { ProcessEvent::NoteEvent { timing: _, event } => {
timing: _,
mut event,
} => {
// We need to make sure to compensate the event for any block splitting, // We need to make sure to compensate the event for any block splitting,
// since we had to create the event object beforehand // since we had to create the event object beforehand
let mut event = event.clone();
event.subtract_timing(block_start as u32); event.subtract_timing(block_start as u32);
input_events.push_back(event); input_events.push_back(event);
} }
@ -1626,7 +1624,7 @@ impl<P: Vst3Plugin> IAudioProcessor for Wrapper<P> {
pressure, pressure,
}; };
} }
event @ (NoteEvent::PolyVolume { ref event @ (NoteEvent::PolyVolume {
voice_id, voice_id,
channel, channel,
note, note,
@ -1665,7 +1663,7 @@ impl<P: Vst3Plugin> IAudioProcessor for Wrapper<P> {
match NoteExpressionController::translate_event_reverse( match NoteExpressionController::translate_event_reverse(
voice_id voice_id
.unwrap_or_else(|| ((channel as i32) << 8) | note as i32), .unwrap_or_else(|| ((channel as i32) << 8) | note as i32),
&event, event,
) { ) {
Some(translated_event) => { Some(translated_event) => {
vst3_event.type_ = vst3_event.type_ =