Add a way to output note events
This supports all note events supported by NIH-plug, and both CLAP and VST3.
This commit is contained in:
parent
1f8db9d9c2
commit
65d87f87ed
|
@ -94,8 +94,9 @@ for download links.
|
|||
- A simple and safe API for state saving and restoring from the editor is
|
||||
provided by the framework if you want to do your own internal preset
|
||||
management.
|
||||
- Full support for both modern polyphonic note expressions as well as MIDI CCs,
|
||||
channel pressure, and pitch bend for CLAP and VST3.
|
||||
- Full support for receiving and outputting both modern polyphonic note
|
||||
expression events as well as MIDI CCs, channel pressure, and pitch bend for
|
||||
CLAP and VST3.
|
||||
- A plugin bundler accessible through the
|
||||
`cargo xtask bundle <package> <build_arguments>` command that automatically
|
||||
detects which plugin targets your plugin exposes and creates the correct
|
||||
|
|
|
@ -53,6 +53,11 @@ pub trait ProcessContext {
|
|||
/// ```
|
||||
fn next_event(&mut self) -> Option<NoteEvent>;
|
||||
|
||||
/// 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);
|
||||
|
||||
/// Update the current latency of the plugin. If the plugin is currently processing audio, then
|
||||
/// this may cause audio playback to be restarted.
|
||||
fn set_latency_samples(&self, samples: u32);
|
||||
|
|
|
@ -40,9 +40,14 @@ pub trait Plugin: Default + Send + Sync + 'static {
|
|||
/// instead of setting up the busses properly.
|
||||
const DEFAULT_NUM_OUTPUTS: u32 = 2;
|
||||
|
||||
/// Whether the plugin accepts note events, and which level of . If this is set to
|
||||
/// [`MidiConfig::None`], then the plugin won't receive any note events.
|
||||
/// Whether the plugin accepts note events, and what which events it wants to receive. If this
|
||||
/// is set to [`MidiConfig::None`], then the plugin won't receive any note events.
|
||||
const MIDI_INPUT: MidiConfig = MidiConfig::None;
|
||||
/// Whether the plugin can output note events. If this is set to [`MidiConfig::None`], then the
|
||||
/// plugin won't have a note output port. When this is set to another value, then in most hsots
|
||||
/// the plugin will consume all note and MIDI CC input. If you don't want that, then you will
|
||||
/// need to forward those events yourself.
|
||||
const MIDI_OUTPUT: MidiConfig = MidiConfig::None;
|
||||
/// If enabled, the audio processing cycle may be split up into multiple smaller chunks if
|
||||
/// parameter values change occur in the middle of the buffer. Depending on the host these
|
||||
/// blocks may be as small as a single sample. Bitwig Studio sends at most one parameter change
|
||||
|
|
|
@ -23,6 +23,7 @@ pub(crate) struct WrapperGuiContext<P: ClapPlugin> {
|
|||
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) transport: Transport,
|
||||
}
|
||||
|
||||
|
@ -99,6 +100,10 @@ impl<P: ClapPlugin> ProcessContext for WrapperProcessContext<'_, P> {
|
|||
self.input_events_guard.pop_front()
|
||||
}
|
||||
|
||||
fn send_event(&mut self, event: NoteEvent) {
|
||||
self.output_events_guard.push_back(event);
|
||||
}
|
||||
|
||||
fn set_latency_samples(&self, samples: u32) {
|
||||
// Only make a callback if it's actually needed
|
||||
// XXX: For CLAP we could move this handling to the Plugin struct, but it may be worthwhile
|
||||
|
|
|
@ -32,6 +32,7 @@ use clap_sys::ext::gui::{
|
|||
use clap_sys::ext::latency::{clap_host_latency, clap_plugin_latency, CLAP_EXT_LATENCY};
|
||||
use clap_sys::ext::note_ports::{
|
||||
clap_note_port_info, clap_plugin_note_ports, CLAP_EXT_NOTE_PORTS, CLAP_NOTE_DIALECT_CLAP,
|
||||
CLAP_NOTE_DIALECT_MIDI,
|
||||
};
|
||||
use clap_sys::ext::params::{
|
||||
clap_host_params, clap_param_info, clap_plugin_params, CLAP_EXT_PARAMS,
|
||||
|
@ -128,6 +129,9 @@ 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>>,
|
||||
/// Stores any events the plugin has output during the current processing cycle, analogous to
|
||||
/// `input_events`.
|
||||
output_events: AtomicRefCell<VecDeque<NoteEvent>>,
|
||||
/// 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
|
||||
|
@ -478,6 +482,7 @@ impl<P: ClapPlugin> Wrapper<P> {
|
|||
}),
|
||||
current_buffer_config: AtomicCell::new(None),
|
||||
input_events: AtomicRefCell::new(VecDeque::with_capacity(512)),
|
||||
output_events: AtomicRefCell::new(VecDeque::with_capacity(512)),
|
||||
last_process_status: AtomicCell::new(ProcessStatus::Normal),
|
||||
current_latency: AtomicU32::new(0),
|
||||
output_buffer: AtomicRefCell::new(Buffer::default()),
|
||||
|
@ -580,6 +585,7 @@ impl<P: ClapPlugin> Wrapper<P> {
|
|||
WrapperProcessContext {
|
||||
wrapper: self,
|
||||
input_events_guard: self.input_events.borrow_mut(),
|
||||
output_events_guard: self.output_events.borrow_mut(),
|
||||
transport,
|
||||
}
|
||||
}
|
||||
|
@ -831,6 +837,301 @@ impl<P: ClapPlugin> Wrapper<P> {
|
|||
if parameter_values_changed {
|
||||
self.notify_param_values_changed();
|
||||
}
|
||||
|
||||
// Also send all note events generated by the plugin
|
||||
let mut output_events = self.output_events.borrow_mut();
|
||||
while let Some(event) = output_events.pop_front() {
|
||||
let time = event.timing() + current_sample_idx as u32;
|
||||
|
||||
let push_successful = match event {
|
||||
NoteEvent::NoteOn {
|
||||
timing: _,
|
||||
channel,
|
||||
note,
|
||||
velocity,
|
||||
} if P::MIDI_OUTPUT >= MidiConfig::Basic => {
|
||||
let event = clap_event_note {
|
||||
header: clap_event_header {
|
||||
size: mem::size_of::<clap_event_param_gesture>() as u32,
|
||||
time,
|
||||
space_id: CLAP_CORE_EVENT_SPACE_ID,
|
||||
type_: CLAP_EVENT_NOTE_ON,
|
||||
// We don't have a way to denote live events
|
||||
flags: 0,
|
||||
},
|
||||
port_index: 0,
|
||||
key: note as i16,
|
||||
channel: channel as i16,
|
||||
velocity: velocity as f64,
|
||||
};
|
||||
|
||||
(out.try_push)(out, &event.header)
|
||||
}
|
||||
NoteEvent::NoteOff {
|
||||
timing: _,
|
||||
channel,
|
||||
note,
|
||||
velocity,
|
||||
} if P::MIDI_OUTPUT >= MidiConfig::Basic => {
|
||||
let event = clap_event_note {
|
||||
header: clap_event_header {
|
||||
size: mem::size_of::<clap_event_param_gesture>() as u32,
|
||||
time,
|
||||
space_id: CLAP_CORE_EVENT_SPACE_ID,
|
||||
type_: CLAP_EVENT_NOTE_OFF,
|
||||
flags: 0,
|
||||
},
|
||||
port_index: 0,
|
||||
key: note as i16,
|
||||
channel: channel as i16,
|
||||
velocity: velocity as f64,
|
||||
};
|
||||
|
||||
(out.try_push)(out, &event.header)
|
||||
}
|
||||
NoteEvent::PolyPressure {
|
||||
timing: _,
|
||||
channel,
|
||||
note,
|
||||
pressure,
|
||||
} if P::MIDI_OUTPUT >= MidiConfig::Basic => {
|
||||
let event = clap_event_note_expression {
|
||||
header: clap_event_header {
|
||||
size: mem::size_of::<clap_event_param_gesture>() as u32,
|
||||
time,
|
||||
space_id: CLAP_CORE_EVENT_SPACE_ID,
|
||||
type_: CLAP_EVENT_NOTE_EXPRESSION,
|
||||
flags: 0,
|
||||
},
|
||||
expression_id: CLAP_NOTE_EXPRESSION_PRESSURE,
|
||||
port_index: 0,
|
||||
key: note as i16,
|
||||
channel: channel as i16,
|
||||
value: pressure as f64,
|
||||
};
|
||||
|
||||
(out.try_push)(out, &event.header)
|
||||
}
|
||||
NoteEvent::PolyVolume {
|
||||
timing: _,
|
||||
channel,
|
||||
note,
|
||||
gain,
|
||||
} if P::MIDI_OUTPUT >= MidiConfig::Basic => {
|
||||
let event = clap_event_note_expression {
|
||||
header: clap_event_header {
|
||||
size: mem::size_of::<clap_event_param_gesture>() as u32,
|
||||
time,
|
||||
space_id: CLAP_CORE_EVENT_SPACE_ID,
|
||||
type_: CLAP_EVENT_NOTE_EXPRESSION,
|
||||
flags: 0,
|
||||
},
|
||||
expression_id: CLAP_NOTE_EXPRESSION_VOLUME,
|
||||
port_index: 0,
|
||||
key: note as i16,
|
||||
channel: channel as i16,
|
||||
value: gain as f64,
|
||||
};
|
||||
|
||||
(out.try_push)(out, &event.header)
|
||||
}
|
||||
NoteEvent::PolyPan {
|
||||
timing: _,
|
||||
channel,
|
||||
note,
|
||||
pan,
|
||||
} if P::MIDI_OUTPUT >= MidiConfig::Basic => {
|
||||
let event = clap_event_note_expression {
|
||||
header: clap_event_header {
|
||||
size: mem::size_of::<clap_event_param_gesture>() as u32,
|
||||
time,
|
||||
space_id: CLAP_CORE_EVENT_SPACE_ID,
|
||||
type_: CLAP_EVENT_NOTE_EXPRESSION,
|
||||
flags: 0,
|
||||
},
|
||||
expression_id: CLAP_NOTE_EXPRESSION_PAN,
|
||||
port_index: 0,
|
||||
key: note as i16,
|
||||
channel: channel as i16,
|
||||
value: (pan as f64 + 1.0) / 2.0,
|
||||
};
|
||||
|
||||
(out.try_push)(out, &event.header)
|
||||
}
|
||||
NoteEvent::PolyTuning {
|
||||
timing: _,
|
||||
channel,
|
||||
note,
|
||||
tuning,
|
||||
} if P::MIDI_OUTPUT >= MidiConfig::Basic => {
|
||||
let event = clap_event_note_expression {
|
||||
header: clap_event_header {
|
||||
size: mem::size_of::<clap_event_param_gesture>() as u32,
|
||||
time,
|
||||
space_id: CLAP_CORE_EVENT_SPACE_ID,
|
||||
type_: CLAP_EVENT_NOTE_EXPRESSION,
|
||||
flags: 0,
|
||||
},
|
||||
expression_id: CLAP_NOTE_EXPRESSION_TUNING,
|
||||
port_index: 0,
|
||||
key: note as i16,
|
||||
channel: channel as i16,
|
||||
value: tuning as f64,
|
||||
};
|
||||
|
||||
(out.try_push)(out, &event.header)
|
||||
}
|
||||
NoteEvent::PolyVibrato {
|
||||
timing: _,
|
||||
channel,
|
||||
note,
|
||||
vibrato,
|
||||
} if P::MIDI_OUTPUT >= MidiConfig::Basic => {
|
||||
let event = clap_event_note_expression {
|
||||
header: clap_event_header {
|
||||
size: mem::size_of::<clap_event_param_gesture>() as u32,
|
||||
time,
|
||||
space_id: CLAP_CORE_EVENT_SPACE_ID,
|
||||
type_: CLAP_EVENT_NOTE_EXPRESSION,
|
||||
flags: 0,
|
||||
},
|
||||
expression_id: CLAP_NOTE_EXPRESSION_VIBRATO,
|
||||
port_index: 0,
|
||||
key: note as i16,
|
||||
channel: channel as i16,
|
||||
value: vibrato as f64,
|
||||
};
|
||||
|
||||
(out.try_push)(out, &event.header)
|
||||
}
|
||||
NoteEvent::PolyExpression {
|
||||
timing: _,
|
||||
channel,
|
||||
note,
|
||||
expression,
|
||||
} if P::MIDI_OUTPUT >= MidiConfig::Basic => {
|
||||
let event = clap_event_note_expression {
|
||||
header: clap_event_header {
|
||||
size: mem::size_of::<clap_event_param_gesture>() as u32,
|
||||
time,
|
||||
space_id: CLAP_CORE_EVENT_SPACE_ID,
|
||||
type_: CLAP_EVENT_NOTE_EXPRESSION,
|
||||
flags: 0,
|
||||
},
|
||||
expression_id: CLAP_NOTE_EXPRESSION_EXPRESSION,
|
||||
port_index: 0,
|
||||
key: note as i16,
|
||||
channel: channel as i16,
|
||||
value: expression as f64,
|
||||
};
|
||||
|
||||
(out.try_push)(out, &event.header)
|
||||
}
|
||||
NoteEvent::PolyBrightness {
|
||||
timing: _,
|
||||
channel,
|
||||
note,
|
||||
brightness,
|
||||
} if P::MIDI_OUTPUT >= MidiConfig::Basic => {
|
||||
let event = clap_event_note_expression {
|
||||
header: clap_event_header {
|
||||
size: mem::size_of::<clap_event_param_gesture>() as u32,
|
||||
time,
|
||||
space_id: CLAP_CORE_EVENT_SPACE_ID,
|
||||
type_: CLAP_EVENT_NOTE_EXPRESSION,
|
||||
flags: 0,
|
||||
},
|
||||
expression_id: CLAP_NOTE_EXPRESSION_BRIGHTNESS,
|
||||
port_index: 0,
|
||||
key: note as i16,
|
||||
channel: channel as i16,
|
||||
value: brightness as f64,
|
||||
};
|
||||
|
||||
(out.try_push)(out, &event.header)
|
||||
}
|
||||
NoteEvent::MidiChannelPressure {
|
||||
timing: _,
|
||||
channel,
|
||||
pressure,
|
||||
} if P::MIDI_OUTPUT >= MidiConfig::MidiCCs => {
|
||||
let event = clap_event_midi {
|
||||
header: clap_event_header {
|
||||
size: mem::size_of::<clap_event_param_gesture>() as u32,
|
||||
time,
|
||||
space_id: CLAP_CORE_EVENT_SPACE_ID,
|
||||
type_: CLAP_EVENT_MIDI,
|
||||
flags: 0,
|
||||
},
|
||||
port_index: 0,
|
||||
data: [
|
||||
midi::CHANNEL_KEY_PRESSURE | channel as u8,
|
||||
(pressure * 127.0).round() as u8,
|
||||
0,
|
||||
],
|
||||
};
|
||||
|
||||
(out.try_push)(out, &event.header)
|
||||
}
|
||||
NoteEvent::MidiPitchBend {
|
||||
timing: _,
|
||||
channel,
|
||||
value,
|
||||
} if P::MIDI_OUTPUT >= MidiConfig::MidiCCs => {
|
||||
let scaled = (value * ((1 << 14) - 1) as f32).round() as i32;
|
||||
|
||||
let event = clap_event_midi {
|
||||
header: clap_event_header {
|
||||
size: mem::size_of::<clap_event_param_gesture>() as u32,
|
||||
time,
|
||||
space_id: CLAP_CORE_EVENT_SPACE_ID,
|
||||
type_: CLAP_EVENT_MIDI,
|
||||
flags: 0,
|
||||
},
|
||||
port_index: 0,
|
||||
data: [
|
||||
midi::PITCH_BEND_CHANGE | channel as u8,
|
||||
(scaled & 0b01111111) as u8,
|
||||
((scaled >> 7) & 0b01111111) as u8,
|
||||
],
|
||||
};
|
||||
|
||||
(out.try_push)(out, &event.header)
|
||||
}
|
||||
NoteEvent::MidiCC {
|
||||
timing: _,
|
||||
channel,
|
||||
cc,
|
||||
value,
|
||||
} if P::MIDI_OUTPUT >= MidiConfig::MidiCCs => {
|
||||
let event = clap_event_midi {
|
||||
header: clap_event_header {
|
||||
size: mem::size_of::<clap_event_param_gesture>() as u32,
|
||||
time,
|
||||
space_id: CLAP_CORE_EVENT_SPACE_ID,
|
||||
type_: CLAP_EVENT_MIDI,
|
||||
flags: 0,
|
||||
},
|
||||
port_index: 0,
|
||||
data: [
|
||||
midi::CONTROL_CHANGE | channel as u8,
|
||||
cc,
|
||||
(value * 127.0).round() as u8,
|
||||
],
|
||||
};
|
||||
|
||||
(out.try_push)(out, &event.header)
|
||||
}
|
||||
_ => {
|
||||
nih_debug_assert_failure!(
|
||||
"Invalid output event for the current MIDI_OUTPUT setting"
|
||||
);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
nih_debug_assert!(push_successful, "Could not send note event");
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle an incoming CLAP event. The sample index is provided to support block splitting for
|
||||
|
@ -1462,7 +1763,8 @@ impl<P: ClapPlugin> Wrapper<P> {
|
|||
ProcessStatus::KeepAlive => CLAP_PROCESS_CONTINUE,
|
||||
};
|
||||
|
||||
// After processing audio, send all spooled events to the host
|
||||
// After processing audio, send all spooled events to the host. This include note
|
||||
// events.
|
||||
if !process.out_events.is_null() {
|
||||
wrapper.handle_out_events(&*process.out_events, block_start);
|
||||
}
|
||||
|
@ -2031,6 +2333,7 @@ impl<P: ClapPlugin> Wrapper<P> {
|
|||
// TODO: Outputting notes
|
||||
match is_input {
|
||||
true if P::MIDI_INPUT >= MidiConfig::Basic => 1,
|
||||
false if P::MIDI_OUTPUT >= MidiConfig::Basic => 1,
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
@ -2047,14 +2350,30 @@ impl<P: ClapPlugin> Wrapper<P> {
|
|||
|
||||
let info = &mut *info;
|
||||
info.id = 0;
|
||||
// TODO: Implement MIDI CC handling
|
||||
// TODO: Implement MPE and MIDI2
|
||||
// TODO: Implement MPE (would just be a toggle for the plugin to expose it) and MIDI2
|
||||
info.supported_dialects = CLAP_NOTE_DIALECT_CLAP;
|
||||
if P::MIDI_INPUT >= MidiConfig::MidiCCs {
|
||||
info.supported_dialects |= CLAP_NOTE_DIALECT_MIDI;
|
||||
}
|
||||
info.preferred_dialect = CLAP_NOTE_DIALECT_CLAP;
|
||||
strlcpy(&mut info.name, "Note Input");
|
||||
|
||||
true
|
||||
}
|
||||
(0, false) if P::MIDI_OUTPUT >= MidiConfig::Basic => {
|
||||
*info = std::mem::zeroed();
|
||||
|
||||
let info = &mut *info;
|
||||
info.id = 0;
|
||||
info.supported_dialects = CLAP_NOTE_DIALECT_CLAP;
|
||||
if P::MIDI_OUTPUT >= MidiConfig::MidiCCs {
|
||||
info.supported_dialects |= CLAP_NOTE_DIALECT_MIDI;
|
||||
}
|
||||
info.preferred_dialect = CLAP_NOTE_DIALECT_CLAP;
|
||||
strlcpy(&mut info.name, "Note Output");
|
||||
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ pub(crate) struct WrapperGuiContext<P: Vst3Plugin> {
|
|||
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) transport: Transport,
|
||||
}
|
||||
|
||||
|
@ -108,6 +109,10 @@ impl<P: Vst3Plugin> ProcessContext for WrapperProcessContext<'_, P> {
|
|||
self.input_events_guard.pop_front()
|
||||
}
|
||||
|
||||
fn send_event(&mut self, event: NoteEvent) {
|
||||
self.output_events_guard.push_back(event);
|
||||
}
|
||||
|
||||
fn set_latency_samples(&self, samples: u32) {
|
||||
// Only trigger a restart if it's actually needed
|
||||
let old_latency = self.inner.current_latency.swap(samples, Ordering::SeqCst);
|
||||
|
|
|
@ -82,6 +82,9 @@ pub(crate) struct WrapperInner<P: Vst3Plugin> {
|
|||
/// interleave parameter changes and note events, this queue has to be sorted when
|
||||
/// creating the process context
|
||||
pub input_events: AtomicRefCell<VecDeque<NoteEvent>>,
|
||||
/// Stores any events the plugin has output during the current processing cycle, analogous to
|
||||
/// `input_events`.
|
||||
pub output_events: AtomicRefCell<VecDeque<NoteEvent>>,
|
||||
/// 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 msot recent VST3 note IDs we've seen, and then map those back to MIDI note IDs and
|
||||
|
@ -273,6 +276,7 @@ impl<P: Vst3Plugin> WrapperInner<P> {
|
|||
current_latency: AtomicU32::new(0),
|
||||
output_buffer: AtomicRefCell::new(Buffer::default()),
|
||||
input_events: AtomicRefCell::new(VecDeque::with_capacity(1024)),
|
||||
output_events: AtomicRefCell::new(VecDeque::with_capacity(1024)),
|
||||
note_expression_controller: AtomicRefCell::new(NoteExpressionController::default()),
|
||||
process_events: AtomicRefCell::new(Vec::with_capacity(4096)),
|
||||
updated_state_sender,
|
||||
|
@ -303,6 +307,7 @@ impl<P: Vst3Plugin> WrapperInner<P> {
|
|||
WrapperProcessContext {
|
||||
inner: self,
|
||||
input_events_guard: self.input_events.borrow_mut(),
|
||||
output_events_guard: self.output_events.borrow_mut(),
|
||||
transport,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -98,4 +98,46 @@ impl NoteExpressionController {
|
|||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Translate a NIH-plug note expression event a VST3 `NoteExpressionValueEvent`. Will return
|
||||
/// `None` if the event is not a polyphonic expression event, i.e. one of the events handled by
|
||||
/// `translate_event()`.
|
||||
pub fn translate_event_reverse(
|
||||
note_id: i32,
|
||||
event: &NoteEvent,
|
||||
) -> Option<NoteExpressionValueEvent> {
|
||||
match &event {
|
||||
NoteEvent::PolyVolume { gain, .. } => Some(NoteExpressionValueEvent {
|
||||
type_id: 0, // kVolumeTypeID
|
||||
note_id,
|
||||
value: *gain as f64 / 4.0,
|
||||
}),
|
||||
NoteEvent::PolyPan { pan, .. } => Some(NoteExpressionValueEvent {
|
||||
type_id: 1, // kPanTypeId
|
||||
note_id,
|
||||
value: (*pan as f64 + 1.0) / 2.0,
|
||||
}),
|
||||
NoteEvent::PolyTuning { tuning, .. } => Some(NoteExpressionValueEvent {
|
||||
type_id: 2, // kTuningTypeID
|
||||
note_id,
|
||||
value: (*tuning as f64 / 240.0) + 0.5,
|
||||
}),
|
||||
NoteEvent::PolyVibrato { vibrato, .. } => Some(NoteExpressionValueEvent {
|
||||
type_id: 3, // kVibratoTypeID
|
||||
note_id,
|
||||
value: *vibrato as f64,
|
||||
}),
|
||||
NoteEvent::PolyExpression { expression, .. } => Some(NoteExpressionValueEvent {
|
||||
type_id: 4, // kExpressionTypeID
|
||||
note_id,
|
||||
value: *expression as f64,
|
||||
}),
|
||||
NoteEvent::PolyBrightness { brightness, .. } => Some(NoteExpressionValueEvent {
|
||||
type_id: 5, // kBrightnessTypeID
|
||||
note_id,
|
||||
value: *brightness as f64,
|
||||
}),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,9 +8,9 @@ use vst3_sys::base::{kInvalidArgument, kNoInterface, kResultFalse, kResultOk, tr
|
|||
use vst3_sys::base::{IBStream, IPluginBase};
|
||||
use vst3_sys::utils::SharedVstPtr;
|
||||
use vst3_sys::vst::{
|
||||
kNoProgramListId, kRootUnitId, EventTypes, IAudioProcessor, IComponent, IEditController,
|
||||
IEventList, IMidiMapping, IParamValueQueue, IParameterChanges, IUnitInfo, ParameterFlags,
|
||||
ProgramListInfo, TChar, UnitInfo,
|
||||
kNoProgramListId, kRootUnitId, Event, EventTypes, IAudioProcessor, IComponent, IEditController,
|
||||
IEventList, IMidiMapping, IParamValueQueue, IParameterChanges, IUnitInfo, LegacyMidiCCOutEvent,
|
||||
NoteOffEvent, NoteOnEvent, ParameterFlags, PolyPressureEvent, ProgramListInfo, TChar, UnitInfo,
|
||||
};
|
||||
use vst3_sys::VST3;
|
||||
use widestring::U16CStr;
|
||||
|
@ -26,6 +26,7 @@ use crate::util::permit_alloc;
|
|||
use crate::wrapper::state;
|
||||
use crate::wrapper::util::{process_wrapper, u16strlcpy};
|
||||
use crate::wrapper::vst3::inner::ProcessEvent;
|
||||
use crate::wrapper::vst3::note_expressions::NoteExpressionController;
|
||||
use crate::wrapper::vst3::util::{VST3_MIDI_CHANNELS, VST3_MIDI_PARAMS_END};
|
||||
|
||||
// Alias needed for the VST3 attribute macro
|
||||
|
@ -78,6 +79,12 @@ impl<P: Vst3Plugin> IComponent for Wrapper<P> {
|
|||
{
|
||||
1
|
||||
}
|
||||
x if x == vst3_sys::vst::MediaTypes::kEvent as i32
|
||||
&& dir == vst3_sys::vst::BusDirections::kOutput as i32
|
||||
&& P::MIDI_OUTPUT >= MidiConfig::Basic =>
|
||||
{
|
||||
1
|
||||
}
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
@ -130,7 +137,23 @@ impl<P: Vst3Plugin> IComponent for Wrapper<P> {
|
|||
info.media_type = vst3_sys::vst::MediaTypes::kEvent as i32;
|
||||
info.direction = vst3_sys::vst::BusDirections::kInput as i32;
|
||||
info.channel_count = 16;
|
||||
u16strlcpy(&mut info.name, "MIDI");
|
||||
u16strlcpy(&mut info.name, "Note Input");
|
||||
info.bus_type = vst3_sys::vst::BusTypes::kMain as i32;
|
||||
info.flags = vst3_sys::vst::BusFlags::kDefaultActive as u32;
|
||||
kResultOk
|
||||
}
|
||||
(t, d, 0)
|
||||
if t == vst3_sys::vst::MediaTypes::kEvent as i32
|
||||
&& d == vst3_sys::vst::BusDirections::kOutput as i32
|
||||
&& P::MIDI_OUTPUT >= MidiConfig::Basic =>
|
||||
{
|
||||
*info = mem::zeroed();
|
||||
|
||||
let info = &mut *info;
|
||||
info.media_type = vst3_sys::vst::MediaTypes::kEvent as i32;
|
||||
info.direction = vst3_sys::vst::BusDirections::kOutput as i32;
|
||||
info.channel_count = 16;
|
||||
u16strlcpy(&mut info.name, "Note Output");
|
||||
info.bus_type = vst3_sys::vst::BusTypes::kMain as i32;
|
||||
info.flags = vst3_sys::vst::BusFlags::kDefaultActive as u32;
|
||||
kResultOk
|
||||
|
@ -158,6 +181,17 @@ impl<P: Vst3Plugin> IComponent for Wrapper<P> {
|
|||
|
||||
kResultOk
|
||||
}
|
||||
(t, 0)
|
||||
if t == vst3_sys::vst::MediaTypes::kEvent as i32
|
||||
&& P::MIDI_INPUT >= MidiConfig::Basic
|
||||
&& P::MIDI_OUTPUT >= MidiConfig::Basic =>
|
||||
{
|
||||
out_info.media_type = vst3_sys::vst::MediaTypes::kEvent as i32;
|
||||
out_info.bus_index = in_info.bus_index;
|
||||
out_info.channel = in_info.channel;
|
||||
|
||||
kResultOk
|
||||
}
|
||||
_ => kInvalidArgument,
|
||||
}
|
||||
}
|
||||
|
@ -179,6 +213,13 @@ impl<P: Vst3Plugin> IComponent for Wrapper<P> {
|
|||
{
|
||||
kResultOk
|
||||
}
|
||||
(t, d, 0)
|
||||
if t == vst3_sys::vst::MediaTypes::kEvent as i32
|
||||
&& d == vst3_sys::vst::BusDirections::kOutput as i32
|
||||
&& P::MIDI_OUTPUT >= MidiConfig::Basic =>
|
||||
{
|
||||
kResultOk
|
||||
}
|
||||
_ => kInvalidArgument,
|
||||
}
|
||||
}
|
||||
|
@ -1029,11 +1070,152 @@ impl<P: Vst3Plugin> IAudioProcessor for Wrapper<P> {
|
|||
}
|
||||
}
|
||||
|
||||
let mut plugin = self.inner.plugin.write();
|
||||
let mut context = self.inner.make_process_context(transport);
|
||||
|
||||
let result = plugin.process(&mut output_buffer, &mut context);
|
||||
let result = {
|
||||
let mut plugin = self.inner.plugin.write();
|
||||
let mut context = self.inner.make_process_context(transport);
|
||||
plugin.process(&mut output_buffer, &mut context)
|
||||
};
|
||||
self.inner.last_process_status.store(result);
|
||||
|
||||
// Send any events output by the plugin during the process cycle
|
||||
if let Some(events) = data.output_events.upgrade() {
|
||||
let mut output_events = self.inner.output_events.borrow_mut();
|
||||
while let Some(event) = output_events.pop_front() {
|
||||
// We'll set the correct variant on this struct, or skip to the next
|
||||
// loop iteration if we don't handle the event type
|
||||
let mut vst3_event: Event = mem::zeroed();
|
||||
vst3_event.bus_index = 0;
|
||||
// There's also a ppqPos field, but uh how about no
|
||||
vst3_event.sample_offset = event.timing() as i32 + block_start as i32;
|
||||
|
||||
match event {
|
||||
NoteEvent::NoteOn {
|
||||
timing: _,
|
||||
channel,
|
||||
note,
|
||||
velocity,
|
||||
} if P::MIDI_OUTPUT >= MidiConfig::Basic => {
|
||||
vst3_event.type_ = EventTypes::kNoteOnEvent as u16;
|
||||
vst3_event.event.note_on = NoteOnEvent {
|
||||
channel: channel as i16,
|
||||
pitch: note as i16,
|
||||
tuning: 0.0,
|
||||
velocity,
|
||||
length: 0, // What?
|
||||
// We'll use this for our note IDs, that way we don't have
|
||||
// to do anything complicated here
|
||||
note_id: ((channel as i32) << 8) | note as i32,
|
||||
};
|
||||
}
|
||||
NoteEvent::NoteOff {
|
||||
timing: _,
|
||||
channel,
|
||||
note,
|
||||
velocity,
|
||||
} if P::MIDI_OUTPUT >= MidiConfig::Basic => {
|
||||
vst3_event.type_ = EventTypes::kNoteOffEvent as u16;
|
||||
vst3_event.event.note_off = NoteOffEvent {
|
||||
channel: channel as i16,
|
||||
pitch: note as i16,
|
||||
velocity,
|
||||
note_id: ((channel as i32) << 8) | note as i32,
|
||||
tuning: 0.0,
|
||||
};
|
||||
}
|
||||
NoteEvent::PolyPressure {
|
||||
timing: _,
|
||||
channel,
|
||||
note,
|
||||
pressure,
|
||||
} if P::MIDI_OUTPUT >= MidiConfig::Basic => {
|
||||
vst3_event.type_ = EventTypes::kPolyPressureEvent as u16;
|
||||
vst3_event.event.poly_pressure = PolyPressureEvent {
|
||||
channel: channel as i16,
|
||||
pitch: note as i16,
|
||||
note_id: ((channel as i32) << 8) | note as i32,
|
||||
pressure,
|
||||
};
|
||||
}
|
||||
event @ (NoteEvent::PolyVolume { channel, note, .. }
|
||||
| NoteEvent::PolyPan { channel, note, .. }
|
||||
| NoteEvent::PolyTuning { channel, note, .. }
|
||||
| NoteEvent::PolyVibrato { channel, note, .. }
|
||||
| NoteEvent::PolyExpression { channel, note, .. }
|
||||
| NoteEvent::PolyBrightness { channel, note, .. })
|
||||
if P::MIDI_OUTPUT >= MidiConfig::Basic =>
|
||||
{
|
||||
match NoteExpressionController::translate_event_reverse(
|
||||
((channel as i32) << 8) | note as i32,
|
||||
&event,
|
||||
) {
|
||||
Some(translated_event) => {
|
||||
vst3_event.type_ =
|
||||
EventTypes::kNoteExpressionValueEvent as u16;
|
||||
vst3_event.event.note_expression_value =
|
||||
translated_event;
|
||||
}
|
||||
None => {
|
||||
nih_debug_assert_failure!(
|
||||
"Mishandled note expression value event"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
NoteEvent::MidiChannelPressure {
|
||||
timing: _,
|
||||
channel,
|
||||
pressure,
|
||||
} if P::MIDI_OUTPUT >= MidiConfig::MidiCCs => {
|
||||
vst3_event.type_ = EventTypes::kLegacyMIDICCOutEvent as u16;
|
||||
vst3_event.event.legacy_midi_cc_out = LegacyMidiCCOutEvent {
|
||||
control_number: 128, // kAfterTouch
|
||||
channel: channel as i8,
|
||||
value: (pressure * 127.0).round() as i8,
|
||||
value2: 0,
|
||||
};
|
||||
}
|
||||
NoteEvent::MidiPitchBend {
|
||||
timing: _,
|
||||
channel,
|
||||
value,
|
||||
} if P::MIDI_OUTPUT >= MidiConfig::MidiCCs => {
|
||||
let scaled = (value * ((1 << 14) - 1) as f32).round() as i32;
|
||||
|
||||
vst3_event.type_ = EventTypes::kLegacyMIDICCOutEvent as u16;
|
||||
vst3_event.event.legacy_midi_cc_out = LegacyMidiCCOutEvent {
|
||||
control_number: 129, // kPitchBend
|
||||
channel: channel as i8,
|
||||
value: (scaled & 0b01111111) as i8,
|
||||
value2: ((scaled >> 7) & 0b01111111) as i8,
|
||||
};
|
||||
}
|
||||
NoteEvent::MidiCC {
|
||||
timing: _,
|
||||
channel,
|
||||
cc,
|
||||
value,
|
||||
} if P::MIDI_OUTPUT >= MidiConfig::MidiCCs => {
|
||||
vst3_event.type_ = EventTypes::kLegacyMIDICCOutEvent as u16;
|
||||
vst3_event.event.legacy_midi_cc_out = LegacyMidiCCOutEvent {
|
||||
control_number: cc,
|
||||
channel: channel as i8,
|
||||
value: (value * 127.0).round() as i8,
|
||||
value2: 0,
|
||||
};
|
||||
}
|
||||
_ => {
|
||||
nih_debug_assert_failure!(
|
||||
"Invalid output event for the current MIDI_OUTPUT setting"
|
||||
);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let result = events.add_event(&mut vst3_event);
|
||||
nih_debug_assert_eq!(result, kResultOk);
|
||||
}
|
||||
}
|
||||
|
||||
match result {
|
||||
ProcessStatus::Error(err) => {
|
||||
nih_debug_assert_failure!("Process error: {}", err);
|
||||
|
|
Loading…
Reference in a new issue