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