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:
parent
c8ed795524
commit
b7849f9a7a
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
45
src/midi.rs
45
src/midi.rs
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
) {
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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>,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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_ =
|
||||
|
|
Loading…
Reference in a new issue