Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 131 additions & 2 deletions plinth-plugin/src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use crate::parameters::ParameterId;
#[non_exhaustive]
pub enum Event {
// Note events

NoteOn {
sample_offset: usize,
channel: i16,
Expand All @@ -25,15 +26,132 @@ pub enum Event {
velocity: f64,
},

PitchBend {
// Per-note expression events (VST3/CLAP note expression)

/// Per-note volume. `gain` is linear in [0, 4], where 1 is 0dB and 0 -INFdB.
/// Requires [`crate::NoteExpressions::with_volume`].
PolyVolume {
sample_offset: usize,
channel: i16,
key: i16,
note: i32,
gain: f64,
},

/// Polyphonic key pressure (poly aftertouch) (VST3 `kPolyPressureEvent` or CLAP's `CLAP_NOTE_EXPRESSION_PRESSURE`).
/// `value` is in [0, 1]. The same value delivered as a raw MIDI byte message arrives as [`Event::MidiPolyPressure`].
/// Requires [`crate::NoteExpressions::with_pressure`].
PolyPressure {
sample_offset: usize,
channel: i16,
key: i16,
note: i32,
value: f64,
},

/// Per-note panning. `pan` is in [-1, 1] (left..right).
/// Requires [`crate::NoteExpressions::with_pan`].
PolyPan {
sample_offset: usize,
channel: i16,
key: i16,
note: i32,
pan: f64,
},

/// Per-note tuning offset in semitones. `semitones` is in [-120, +120].
/// Requires [`crate::NoteExpressions::with_tuning`].
PolyTuning {
sample_offset: usize,
channel: i16,
key: i16,
note: i32,
semitones: f64,
},

/// Per-note vibrato. `amount` is in [0, 1].
/// Rarely (if at all) used by hosts, but part of the CLAP specs.
/// Requires [`crate::NoteExpressions::with_vibrato`].
PolyVibrato {
sample_offset: usize,
channel: i16,
key: i16,
note: i32,
amount: f64,
},

/// Per-note expression (MIDI MPE "slide" / CC 74 equivalent). `amount` is in [0, 1].
/// Rarely (if at all) used by hosts, but part of the CLAP specs. Use `PolyBrightness` instead.
/// Requires [`crate::NoteExpressions::with_expression`].
PolyExpression {
sample_offset: usize,
channel: i16,
key: i16,
note: i32,
amount: f64,
},

/// Per-note brightness a.k.a. timbre (CLAP brightness / VST3 brightness). `amount` is in [0, 1].
/// Requires [`crate::NoteExpressions::with_brightness`].
PolyBrightness {
sample_offset: usize,
channel: i16,
key: i16,
note: i32,
amount: f64,
},

// MIDI channel based events

/// Channel-wide MIDI pitch bend.
/// `semitones` is the current bend in semitones, using the standard +-2 semitone range.
/// Per-note pitch offset (VST3/CLAP note expression) arrives as [`Event::PolyTuning`].
/// Requires [`MidiCapabilities::with_pitch_bend`].
MidiPitchBend {
sample_offset: usize,
channel: i16,
semitones: f64,
},

/// Channel-wide pressure (mono aftertouch). `value` is in [0, 1].
/// Requires [`MidiCapabilities::with_channel_pressure`]. See also [`Event::MidiPolyPressure`].
MidiChannelPressure {
sample_offset: usize,
channel: i16,
value: f64,
},

/// Polyphonic key pressure (poly aftertouch), delivered as a raw MIDI byte message.
/// `value` is in [0, 1]. Same value delivered via a native per-note mechanism (VST3 `kPolyPressureEvent`
/// or CLAP note expression) instead arrives as [`Event::PolyPressure`].
/// Requires [`MidiCapabilities::with_poly_pressure`]. See also [`Event::MidiChannelPressure`].
MidiPolyPressure {
sample_offset: usize,
channel: i16,
key: i16,
note: i32,
value: f64,
},

/// MIDI Program Change. `program` is the program number (0..=127).
/// Requires [`MidiCapabilities::with_program_change`].
MidiProgramChange {
sample_offset: usize,
channel: i16,
program: u8,
},

/// MIDI Control Change. `controller` is the CC number (0..=127), `value` is in [0, 1].
/// Requires the corresponding CC to be enabled via [`MidiCapabilities::with_control_change`].
MidiControlChange {
sample_offset: usize,
channel: i16,
controller: u8,
value: f64,
},

// Parameter events

StartParameterChange {
id: ParameterId,
},
Expand Down Expand Up @@ -68,7 +186,18 @@ impl Event {
match self {
Event::NoteOn { sample_offset, .. } => *sample_offset,
Event::NoteOff { sample_offset, .. } => *sample_offset,
Event::PitchBend { sample_offset, .. } => *sample_offset,
Event::PolyVolume { sample_offset, .. } => *sample_offset,
Event::PolyPan { sample_offset, .. } => *sample_offset,
Event::PolyTuning { sample_offset, .. } => *sample_offset,
Event::PolyVibrato { sample_offset, .. } => *sample_offset,
Event::PolyExpression { sample_offset, .. } => *sample_offset,
Event::PolyBrightness { sample_offset, .. } => *sample_offset,
Event::PolyPressure { sample_offset, .. } => *sample_offset,
Event::MidiPitchBend { sample_offset, .. } => *sample_offset,
Event::MidiChannelPressure { sample_offset, .. } => *sample_offset,
Event::MidiPolyPressure { sample_offset, .. } => *sample_offset,
Event::MidiProgramChange { sample_offset, .. } => *sample_offset,
Event::MidiControlChange { sample_offset, .. } => *sample_offset,
Event::ParameterValue { sample_offset, .. } => *sample_offset,
Event::ParameterModulation { sample_offset, .. } => *sample_offset,

Expand Down
1 change: 1 addition & 0 deletions plinth-plugin/src/formats.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::fmt::Display;
#[cfg(target_os="macos")]
pub mod auv3;
pub mod clap;
pub mod midi;
#[cfg(feature = "standalone")]
pub mod standalone;
pub mod vst3;
Expand Down
150 changes: 122 additions & 28 deletions plinth-plugin/src/formats/clap/event.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,25 @@
use std::collections::BTreeMap;
use std::ffi::c_void;

use clap_sys::events::{clap_event_note, clap_event_note_expression, clap_event_param_mod, clap_event_param_value, clap_input_events, CLAP_CORE_EVENT_SPACE_ID, CLAP_EVENT_NOTE_EXPRESSION, CLAP_EVENT_NOTE_OFF, CLAP_EVENT_NOTE_ON, CLAP_EVENT_PARAM_MOD, CLAP_EVENT_PARAM_VALUE, CLAP_NOTE_EXPRESSION_TUNING};
use clap_sys::events::{clap_event_note, clap_event_note_expression, clap_event_param_mod, clap_event_param_value, clap_event_midi, clap_input_events, CLAP_CORE_EVENT_SPACE_ID, CLAP_EVENT_NOTE_EXPRESSION, CLAP_EVENT_NOTE_OFF, CLAP_EVENT_NOTE_ON, CLAP_EVENT_PARAM_MOD, CLAP_EVENT_PARAM_VALUE, CLAP_NOTE_EXPRESSION_TUNING, CLAP_EVENT_MIDI, CLAP_NOTE_EXPRESSION_BRIGHTNESS, CLAP_NOTE_EXPRESSION_EXPRESSION, CLAP_NOTE_EXPRESSION_PAN, CLAP_NOTE_EXPRESSION_PRESSURE, CLAP_NOTE_EXPRESSION_VIBRATO, CLAP_NOTE_EXPRESSION_VOLUME};

use crate::{parameters::info::ParameterInfo, Event, ParameterId};
use crate::{formats::midi::parse_midi_event, parameters::info::ParameterInfo, Event, MidiCapabilities, NoteExpressions, ParameterId};

use super::parameters::map_parameter_value_from_clap;

pub struct EventIterator<'a> {
note_expressions: NoteExpressions,
midi_capabilities: MidiCapabilities,
parameter_info: &'a BTreeMap<ParameterId, ParameterInfo>,
events: &'a clap_input_events,
index: u32,
}

impl<'a> EventIterator<'a> {
pub fn new(parameter_info: &'a BTreeMap<ParameterId, ParameterInfo>, events: &'a clap_input_events) -> Self {
pub fn new(parameter_info: &'a BTreeMap<ParameterId, ParameterInfo>, events: &'a clap_input_events, midi_capabilities: MidiCapabilities, note_expressions: NoteExpressions) -> Self {
Self {
midi_capabilities,
note_expressions,
parameter_info,
events,
index: 0,
Expand All @@ -41,52 +45,139 @@ impl Iterator for EventIterator<'_> {
if self.index >= events_size {
return None;
}

let header = unsafe { (self.events.get.unwrap())(self.events, self.index) };
self.index += 1;

if unsafe { *header }.space_id != CLAP_CORE_EVENT_SPACE_ID {
continue;
}

let event = match (unsafe { *header }).type_ {
let event: Option<Event> = match (unsafe { *header }).type_ {
CLAP_EVENT_NOTE_ON => {
let event = unsafe { &*(header as *const clap_event_note) };

Event::NoteOn {
Some(Event::NoteOn {
sample_offset: event.header.time as _,
channel: event.channel,
key: event.key,
note: event.note_id,
velocity: event.velocity,
}
})
}

CLAP_EVENT_NOTE_OFF => {
let event = unsafe { &*(header as *const clap_event_note) };

Event::NoteOff {
Some(Event::NoteOff {
sample_offset: event.header.time as _,
channel: event.channel,
key: event.key,
note: event.note_id,
velocity: event.velocity,
}
})
}

CLAP_EVENT_NOTE_EXPRESSION => {
let event = unsafe { &*(header as *const clap_event_note_expression) };
if event.expression_id != CLAP_NOTE_EXPRESSION_TUNING {
continue;
let note_expressions = self.note_expressions;
let channel = event.channel;
let key = event.key;
let note = event.note_id;
let value = event.value;
let sample_offset = event.header.time as usize;

match event.expression_id {
CLAP_NOTE_EXPRESSION_TUNING if note_expressions.tuning() => {
Some(Event::PolyTuning {
sample_offset,
channel,
key,
note,
// fractional semitones, -120 to +120
semitones: value,
})
}

CLAP_NOTE_EXPRESSION_PRESSURE if note_expressions.pressure() => {
Some(Event::PolyPressure {
sample_offset,
channel,
key,
note,
// pass value in [0..1] as it is
value,
})
}

CLAP_NOTE_EXPRESSION_VOLUME if note_expressions.volume() => {
Some(Event::PolyVolume {
sample_offset,
channel,
key,
note,
// pass value in [0..4] as it is
gain: value,
})
}

CLAP_NOTE_EXPRESSION_PAN if note_expressions.pan() => {
Some(Event::PolyPan {
sample_offset,
channel,
key,
note,
// CLAP pan: 0=left, 0.5=center, 1=right -> map to [-1, +1]
pan: value * 2.0 - 1.0,
})
}

CLAP_NOTE_EXPRESSION_VIBRATO if note_expressions.vibrato() => {
Some(Event::PolyVibrato {
sample_offset,
channel,
key,
note,
// pass value in [0..1] as it is
amount: value,
})
}

CLAP_NOTE_EXPRESSION_EXPRESSION if note_expressions.expression() => {
Some(Event::PolyExpression {
sample_offset,
channel,
key,
note,
// pass value in [0..1] as it is
amount: value,
})
}

CLAP_NOTE_EXPRESSION_BRIGHTNESS if note_expressions.brightness() => {
Some(Event::PolyBrightness {
sample_offset,
channel,
key,
note,
// pass value in [0..1] as it is
amount: value,
})
}

// Unknown or unsupported expression ID
_ => None,
}
}

Event::PitchBend {
sample_offset: event.header.time as _,
channel: event.channel,
key: event.key,
note: event.note_id,
semitones: event.value,
}
// Covert raw MIDI bytes to CC / channel pressure / pitch bend / poly pressure events.
CLAP_EVENT_MIDI => {
let event = unsafe { &*(header as *const clap_event_midi) };
parse_midi_event(
&event.data,
event.header.time as usize,
self.midi_capabilities,
)
}

CLAP_EVENT_PARAM_VALUE => {
Expand All @@ -95,32 +186,35 @@ impl Iterator for EventIterator<'_> {

let value = map_parameter_value_from_clap(parameter_info, event.value);

Event::ParameterValue {
Some(Event::ParameterValue {
sample_offset: event.header.time as _,
id: event.param_id,
value,
}
})
},

CLAP_EVENT_PARAM_MOD => {
let event = unsafe { &*(header as *const clap_event_param_mod) };
let parameter_info = self.parameter_info(event.param_id, event.cookie);

let amount = map_parameter_value_from_clap(parameter_info, event.amount);

Event::ParameterModulation {
Some(Event::ParameterModulation {
sample_offset: event.header.time as _,
id: event.param_id,
amount,
}
})
},

_ => {
continue;
}

// All other event types (MIDI2, sysex, etc.) are unsupported and skipped.
_ => None,
};

return Some(event);
if event.is_some() {
return event;
} else {
continue;
}
}
}
}
Loading
Loading