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 = ();
```
- 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]
- 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.
use super::PluginApi;
use crate::midi::NoteEvent;
use crate::midi::PluginNoteEvent;
use crate::plugin::Plugin;
/// 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
/// ```
fn next_event(&mut self) -> Option<NoteEvent>;
fn next_event(&mut self) -> Option<PluginNoteEvent<P>>;
/// Send an event to the host. Only available when
/// [`Plugin::MIDI_OUTPUT`][crate::prelude::Plugin::MIDI_INPUT] is set. Will not do anything
/// 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
/// this may cause audio playback to be restarted.

View file

@ -2,10 +2,22 @@
use midi_consts::channel_event as midi;
use self::sysex::SysExMessage;
use crate::plugin::Plugin;
pub mod sysex;
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.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum MidiConfig {
@ -31,7 +43,7 @@ pub enum MidiConfig {
/// numbers are zero-indexed.
#[derive(Debug, Clone, Copy, PartialEq)]
#[non_exhaustive]
pub enum NoteEvent {
pub enum NoteEvent<S: SysExMessage> {
/// A note on event, available on [`MidiConfig::Basic`] and up.
NoteOn {
timing: u32,
@ -299,9 +311,13 @@ pub enum NoteEvent {
/// The program number, in `0..128`.
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.
pub fn timing(&self) -> u32 {
match self {
@ -322,6 +338,7 @@ impl NoteEvent {
NoteEvent::MidiPitchBend { timing, .. } => *timing,
NoteEvent::MidiCC { timing, .. } => *timing,
NoteEvent::MidiProgramChange { timing, .. } => *timing,
NoteEvent::MidiSysEx { timing, .. } => *timing,
}
}
@ -345,9 +362,13 @@ impl NoteEvent {
NoteEvent::MidiPitchBend { .. } => None,
NoteEvent::MidiCC { .. } => 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.
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
@ -408,6 +429,7 @@ impl NoteEvent {
channel,
program: midi_data[1],
}),
// TODO: SysEx
n => Err(n),
}
}
@ -500,6 +522,8 @@ impl NoteEvent {
| NoteEvent::PolyVibrato { .. }
| NoteEvent::PolyExpression { .. }
| 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::MidiCC { timing, .. } => *timing -= samples,
NoteEvent::MidiProgramChange { timing, .. } => *timing -= samples,
NoteEvent::MidiSysEx { timing, .. } => *timing -= samples,
}
}
}
@ -536,7 +561,7 @@ mod tests {
#[test]
fn test_note_on_midi_conversion() {
let event = NoteEvent::NoteOn {
let event = NoteEvent::<()>::NoteOn {
timing: TIMING,
voice_id: None,
channel: 1,
@ -553,7 +578,7 @@ mod tests {
#[test]
fn test_note_off_midi_conversion() {
let event = NoteEvent::NoteOff {
let event = NoteEvent::<()>::NoteOff {
timing: TIMING,
voice_id: None,
channel: 1,
@ -569,7 +594,7 @@ mod tests {
#[test]
fn test_poly_pressure_midi_conversion() {
let event = NoteEvent::PolyPressure {
let event = NoteEvent::<()>::PolyPressure {
timing: TIMING,
voice_id: None,
channel: 1,
@ -585,7 +610,7 @@ mod tests {
#[test]
fn test_channel_pressure_midi_conversion() {
let event = NoteEvent::MidiChannelPressure {
let event = NoteEvent::<()>::MidiChannelPressure {
timing: TIMING,
channel: 1,
pressure: 0.6929134,
@ -599,7 +624,7 @@ mod tests {
#[test]
fn test_pitch_bend_midi_conversion() {
let event = NoteEvent::MidiPitchBend {
let event = NoteEvent::<()>::MidiPitchBend {
timing: TIMING,
channel: 1,
value: 0.6929134,
@ -613,7 +638,7 @@ mod tests {
#[test]
fn test_cc_midi_conversion() {
let event = NoteEvent::MidiCC {
let event = NoteEvent::<()>::MidiCC {
timing: TIMING,
channel: 1,
cc: 2,
@ -628,7 +653,7 @@ mod tests {
#[test]
fn test_program_change_midi_conversion() {
let event = NoteEvent::MidiProgramChange {
let event = NoteEvent::<()>::MidiProgramChange {
timing: TIMING,
channel: 1,
program: 42,
@ -639,4 +664,6 @@ mod tests {
event
);
}
// TODO: SysEx conversion
}

View file

@ -17,7 +17,7 @@ pub use crate::context::process::ProcessContext;
// This also includes the derive macro
pub use crate::editor::{Editor, ParentWindowHandle};
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::internals::ParamPtr;
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::PluginApi;
use crate::event_loop::EventLoop;
use crate::midi::NoteEvent;
use crate::midi::PluginNoteEvent;
use crate::params::internals::ParamPtr;
use crate::plugin::ClapPlugin;
@ -37,8 +37,8 @@ pub(crate) struct PendingInitContextRequests {
/// unnecessary atomic operations to lock the uncontested RwLocks.
pub(crate) struct WrapperProcessContext<'a, P: ClapPlugin> {
pub(super) wrapper: &'a Wrapper<P>,
pub(super) input_events_guard: AtomicRefMut<'a, VecDeque<NoteEvent>>,
pub(super) output_events_guard: AtomicRefMut<'a, VecDeque<NoteEvent>>,
pub(super) input_events_guard: AtomicRefMut<'a, VecDeque<PluginNoteEvent<P>>>,
pub(super) output_events_guard: AtomicRefMut<'a, VecDeque<PluginNoteEvent<P>>>,
pub(super) transport: Transport,
}
@ -96,11 +96,11 @@ impl<P: ClapPlugin> ProcessContext<P> for WrapperProcessContext<'_, P> {
&self.transport
}
fn next_event(&mut self) -> Option<NoteEvent> {
fn next_event(&mut self) -> Option<PluginNoteEvent<P>> {
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);
}

View file

@ -81,7 +81,7 @@ use crate::context::gui::AsyncExecutor;
use crate::context::process::Transport;
use crate::editor::{Editor, ParentWindowHandle};
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::{ParamFlags, Params};
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
/// 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
/// `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.
last_process_status: AtomicCell<ProcessStatus>,
/// 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(
&self,
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>,
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) {
Ok(wrapper) => wrapper,
Err(err) => {

View file

@ -1,5 +1,5 @@
use crate::context::process::Transport;
use crate::midi::NoteEvent;
use crate::midi::PluginNoteEvent;
mod cpal;
mod dummy;
@ -9,9 +9,10 @@ pub use self::cpal::Cpal;
pub use self::dummy::Dummy;
pub use self::jack::Jack;
pub use crate::buffer::Buffer;
pub use crate::plugin::Plugin;
/// 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
/// 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
@ -20,7 +21,12 @@ pub trait Backend: 'static + Send + Sync {
/// TODO: Auxiliary inputs and outputs
fn run(
&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
+ Send,
);

View file

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

View file

@ -4,7 +4,7 @@ use super::super::config::WrapperConfig;
use super::Backend;
use crate::buffer::Buffer;
use crate::context::process::Transport;
use crate::midi::NoteEvent;
use crate::midi::PluginNoteEvent;
use crate::plugin::{AuxiliaryIOConfig, BusConfig, Plugin};
/// 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,
}
impl Backend for Dummy {
impl<P: Plugin> Backend<P> for Dummy {
fn run(
&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
+ Send,
) {

View file

@ -12,7 +12,7 @@ use super::super::config::WrapperConfig;
use super::Backend;
use crate::buffer::Buffer;
use crate::context::process::Transport;
use crate::midi::{MidiConfig, NoteEvent};
use crate::midi::{MidiConfig, NoteEvent, PluginNoteEvent};
use crate::plugin::Plugin;
/// Uses JACK audio and MIDI.
@ -28,10 +28,15 @@ pub struct Jack {
midi_output: Option<Arc<Mutex<Port<MidiOut>>>>,
}
impl Backend for Jack {
impl<P: Plugin> Backend<P> for Jack {
fn run(
&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
+ Send,
) {
@ -45,8 +50,8 @@ impl Backend for Jack {
})
}
let mut input_events = Vec::with_capacity(2048);
let mut output_events = Vec::with_capacity(2048);
let mut input_events: Vec<PluginNoteEvent<P>> = 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
let parker = Parker::new();

View file

@ -7,33 +7,33 @@ use crate::context::gui::GuiContext;
use crate::context::init::InitContext;
use crate::context::process::{ProcessContext, Transport};
use crate::context::PluginApi;
use crate::midi::NoteEvent;
use crate::midi::PluginNoteEvent;
use crate::params::internals::ParamPtr;
use crate::plugin::Plugin;
/// 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>,
}
/// 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
/// 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)]
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
// here to keep the standalone backend implementation a bit more flexible
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,
}
/// 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
/// 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>>,
/// 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>,
}
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 {
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 {
PluginApi::Standalone
}
@ -79,10 +79,10 @@ impl<P: Plugin, B: Backend> ProcessContext<P> for WrapperProcessContext<'_, P, B
&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
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;
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);
}
@ -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 {
PluginApi::Standalone
}

View file

@ -17,7 +17,7 @@ use crate::context::gui::AsyncExecutor;
use crate::context::process::Transport;
use crate::editor::{Editor, ParentWindowHandle};
use crate::event_loop::{EventLoop, MainThreadExecutor, OsEventLoop};
use crate::midi::NoteEvent;
use crate::midi::PluginNoteEvent;
use crate::params::internals::ParamPtr;
use crate::params::{ParamFlags, Params};
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.
const EVENT_QUEUE_CAPACITY: usize = 2048;
pub struct Wrapper<P: Plugin, B: Backend> {
pub struct Wrapper<P: Plugin, B: Backend<P>> {
backend: AtomicRefCell<B>,
/// 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) {
match 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
/// not accept the IO configuration from the wrapper config.
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>(
&'a self,
transport: Transport,
input_events: &'a [NoteEvent],
output_events: &'a mut Vec<NoteEvent>,
input_events: &'a [PluginNoteEvent<P>],
output_events: &'a mut Vec<PluginNoteEvent<P>>,
) -> WrapperProcessContext<'a, P, B> {
WrapperProcessContext {
wrapper: self,

View file

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

View file

@ -19,7 +19,7 @@ use crate::context::gui::AsyncExecutor;
use crate::context::process::Transport;
use crate::editor::Editor;
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::{ParamFlags, Params};
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
/// interleave parameter changes and note events, this queue has to be sorted when
/// 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
/// `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
/// 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
@ -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
/// 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.
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
/// 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
@ -177,7 +177,7 @@ pub enum Task<P: Plugin> {
/// 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.
#[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
/// automation has been enabled, and the parameters are only updated when we process this
/// spooled event at the start of a block.
@ -198,7 +198,7 @@ pub enum ProcessEvent {
timing: u32,
/// The actual note event, make sure to subtract the block start index with
/// [`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 crate::midi::sysex::SysExMessage;
use crate::midi::NoteEvent;
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
/// expression type from the note expression value event. The timing is provided here because we
/// may be splitting buffers on inter-buffer parameter changes.
pub fn translate_event(
pub fn translate_event<S: SysExMessage>(
&self,
timing: u32,
event: &NoteExpressionValueEvent,
) -> Option<NoteEvent> {
) -> Option<NoteEvent<S>> {
// We're calling it a voice ID, VST3 (and CLAP) calls it a note ID
let (note_id, note, channel) = *self
.note_ids
@ -168,7 +169,7 @@ impl NoteExpressionController {
/// `translate_event()`.
pub fn translate_event_reverse(
note_id: i32,
event: &NoteEvent,
event: &NoteEvent<impl SysExMessage>,
) -> Option<NoteExpressionValueEvent> {
match &event {
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;
for event_idx in event_start_idx..process_events.len() {
match process_events[event_idx] {
match &process_events[event_idx] {
ProcessEvent::ParameterChange {
timing,
hash,
@ -1280,24 +1280,22 @@ impl<P: Vst3Plugin> IAudioProcessor for Wrapper<P> {
// 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've processed this block
if timing != block_start as u32 {
if *timing != block_start as u32 {
event_start_idx = event_idx;
block_end = timing as usize;
block_end = *timing as usize;
break;
}
self.inner.set_normalized_value_by_hash(
hash,
normalized_value,
*hash,
*normalized_value,
Some(sample_rate),
);
}
ProcessEvent::NoteEvent {
timing: _,
mut event,
} => {
ProcessEvent::NoteEvent { timing: _, event } => {
// We need to make sure to compensate the event for any block splitting,
// since we had to create the event object beforehand
let mut event = event.clone();
event.subtract_timing(block_start as u32);
input_events.push_back(event);
}
@ -1626,7 +1624,7 @@ impl<P: Vst3Plugin> IAudioProcessor for Wrapper<P> {
pressure,
};
}
event @ (NoteEvent::PolyVolume {
ref event @ (NoteEvent::PolyVolume {
voice_id,
channel,
note,
@ -1665,7 +1663,7 @@ impl<P: Vst3Plugin> IAudioProcessor for Wrapper<P> {
match NoteExpressionController::translate_event_reverse(
voice_id
.unwrap_or_else(|| ((channel as i32) << 8) | note as i32),
&event,
event,
) {
Some(translated_event) => {
vst3_event.type_ =