commit 93abcc869bb9e839d4c49db3585155af177d36c6
parent 314c814ce16fc44b420c546c184755c57b61b52f
Author: falkTX <falktx@falktx.com>
Date: Mon, 20 Jun 2022 23:47:17 +0100
Proper MIDI implementation for VST3, sort input events ourselves
Signed-off-by: falkTX <falktx@falktx.com>
Diffstat:
1 file changed, 320 insertions(+), 105 deletions(-)
diff --git a/distrho/src/DistrhoPluginVST3.cpp b/distrho/src/DistrhoPluginVST3.cpp
@@ -42,7 +42,6 @@
* - have parameter outputs host-provided UI working in at least 1 host
* - parameter groups via unit ids
* - test parameter changes from DSP (aka requestParameterValueChange)
- * - test receiving midi CC
* - implement getParameterNormalized/setParameterNormalized for MIDI CC params ?
* - fully implemented parameter stuff and verify
* - float to int safe casting
@@ -51,7 +50,6 @@
* - MIDI CC changes (need to store value to give to the host?)
* - MIDI program changes
* - MIDI sysex
- * - append MIDI input events in a sorted way
* == BUSES
* - bus arrangements
* - optional audio buses
@@ -249,6 +247,288 @@ class PluginVst3
numCV(0) {}
} inputBuses, outputBuses;
+ #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
+ /* handy class for storing and sorting events and MIDI CC parameters
+ * only stores events for which a MIDI conversion is possible.
+ */
+ struct InputEventsList {
+ enum Type {
+ NoteOn,
+ NoteOff,
+ SysexData,
+ PolyPressure,
+ CC_Normal,
+ CC_ChannelPressure,
+ CC_Pitchbend,
+ UI_MIDI // event from UI
+ };
+ struct InputEventStorage {
+ Type type;
+ union {
+ v3_event_note_on noteOn;
+ v3_event_note_off noteOff;
+ v3_event_data sysexData;
+ v3_event_poly_pressure polyPressure;
+ uint8_t midi[3];
+ };
+ } eventListStorage[kMaxMidiEvents];
+
+ struct InputEventTiming {
+ int32_t sampleOffset;
+ const InputEventStorage* storage;
+ InputEventTiming* next;
+ } eventList[kMaxMidiEvents];
+
+ uint16_t numUsed;
+ int32_t firstSampleOffset;
+ int32_t lastSampleOffset;
+ InputEventTiming* firstEvent;
+ InputEventTiming* lastEvent;
+
+ void init()
+ {
+ numUsed = 0;
+ firstSampleOffset = lastSampleOffset = 0;
+ firstEvent = nullptr;
+ }
+
+ uint32_t convert(MidiEvent midiEvents[kMaxMidiEvents]) const noexcept
+ {
+ uint32_t count = 0;
+
+ for (const InputEventTiming* event = firstEvent; event != nullptr; event = event->next)
+ {
+ MidiEvent& midiEvent(midiEvents[count++]);
+ midiEvent.frame = event->sampleOffset;
+
+ const InputEventStorage& eventStorage(*event->storage);
+
+ switch (eventStorage.type)
+ {
+ case NoteOn:
+ midiEvent.size = 3;
+ midiEvent.data[0] = 0x90 | (eventStorage.noteOn.channel & 0xf);
+ midiEvent.data[1] = eventStorage.noteOn.pitch;
+ midiEvent.data[2] = std::max(0, std::min(127, (int)(eventStorage.noteOn.velocity * 127)));
+ midiEvent.data[3] = 0;
+ break;
+ case NoteOff:
+ midiEvent.size = 3;
+ midiEvent.data[0] = 0x80 | (eventStorage.noteOff.channel & 0xf);
+ midiEvent.data[1] = eventStorage.noteOff.pitch;
+ midiEvent.data[2] = std::max(0, std::min(127, (int)(eventStorage.noteOff.velocity * 127)));
+ midiEvent.data[3] = 0;
+ break;
+ /* TODO
+ case SysexData:
+ break;
+ */
+ case PolyPressure:
+ midiEvent.size = 3;
+ midiEvent.data[0] = 0xA0 | (eventStorage.polyPressure.channel & 0xf);
+ midiEvent.data[1] = eventStorage.polyPressure.pitch;
+ midiEvent.data[2] = std::max(0, std::min(127, (int)(eventStorage.polyPressure.pressure * 127)));
+ midiEvent.data[3] = 0;
+ break;
+ case CC_Normal:
+ midiEvent.size = 3;
+ midiEvent.data[0] = 0xB0 | (eventStorage.midi[0] & 0xf);
+ midiEvent.data[1] = eventStorage.midi[1];
+ midiEvent.data[2] = eventStorage.midi[2];
+ break;
+ case CC_ChannelPressure:
+ midiEvent.size = 2;
+ midiEvent.data[0] = 0xD0 | (eventStorage.midi[0] & 0xf);
+ midiEvent.data[1] = eventStorage.midi[1];
+ midiEvent.data[2] = 0;
+ break;
+ case CC_Pitchbend:
+ midiEvent.size = 3;
+ midiEvent.data[0] = 0xE0 | (eventStorage.midi[0] & 0xf);
+ midiEvent.data[1] = eventStorage.midi[1];
+ midiEvent.data[2] = eventStorage.midi[2];
+ break;
+ case UI_MIDI:
+ midiEvent.size = 3;
+ midiEvent.data[0] = eventStorage.midi[0];
+ midiEvent.data[1] = eventStorage.midi[1];
+ midiEvent.data[2] = eventStorage.midi[2];
+ break;
+ default:
+ midiEvent.size = 0;
+ break;
+ }
+ }
+
+ return count;
+ }
+
+ bool appendEvent(const v3_event& event) noexcept
+ {
+ // only save events that can be converted directly into MIDI
+ switch (event.type)
+ {
+ case V3_EVENT_NOTE_ON:
+ case V3_EVENT_NOTE_OFF:
+ // case V3_EVENT_DATA:
+ case V3_EVENT_POLY_PRESSURE:
+ break;
+ default:
+ return false;
+ }
+
+ InputEventStorage& eventStorage(eventListStorage[numUsed]);
+
+ switch (event.type)
+ {
+ case V3_EVENT_NOTE_ON:
+ eventStorage.type = NoteOn;
+ eventStorage.noteOn = event.note_on;
+ break;
+ case V3_EVENT_NOTE_OFF:
+ eventStorage.type = NoteOff;
+ eventStorage.noteOff = event.note_off;
+ break;
+ case V3_EVENT_DATA:
+ eventStorage.type = SysexData;
+ eventStorage.sysexData = event.data;
+ break;
+ case V3_EVENT_POLY_PRESSURE:
+ eventStorage.type = PolyPressure;
+ eventStorage.polyPressure = event.poly_pressure;
+ break;
+ default:
+ return false;
+ }
+
+ eventList[numUsed].sampleOffset = event.sample_offset;
+ eventList[numUsed].storage = &eventStorage;
+
+ return placeSorted(event.sample_offset);
+ }
+
+ bool appendCC(const int32_t sampleOffset, v3_param_id paramId, const double value) noexcept
+ {
+ InputEventStorage& eventStorage(eventListStorage[numUsed]);
+
+ const uint8_t cc = paramId % 130;
+
+ switch (cc)
+ {
+ case 128:
+ eventStorage.type = CC_ChannelPressure;
+ eventStorage.midi[1] = std::max(0, std::min(127, (int)(value * 127)));
+ eventStorage.midi[2] = 0;
+ break;
+ case 129:
+ eventStorage.type = CC_Pitchbend;
+ eventStorage.midi[1] = std::max(0, std::min(16384, (int)(value * 16384))) & 0x7f;
+ eventStorage.midi[2] = std::max(0, std::min(16384, (int)(value * 16384))) >> 7;
+ break;
+ default:
+ eventStorage.type = CC_Normal;
+ eventStorage.midi[1] = cc;
+ eventStorage.midi[2] = std::max(0, std::min(127, (int)(value * 127)));
+ break;
+ }
+
+ eventStorage.midi[0] = paramId / 130;
+
+ eventList[numUsed].sampleOffset = sampleOffset;
+ eventList[numUsed].storage = &eventStorage;
+
+ return placeSorted(sampleOffset);
+ }
+
+ #if DISTRHO_PLUGIN_HAS_UI
+ // NOTE always runs first
+ bool appendFromUI(const uint8_t midiData[3])
+ {
+ InputEventStorage& eventStorage(eventListStorage[numUsed]);
+
+ eventStorage.type = UI_MIDI;
+ std::memcpy(eventStorage.midi, midiData, sizeof(uint8_t)*3);
+
+ InputEventTiming* const event = &eventList[numUsed];
+
+ event->sampleOffset = 0;
+ event->storage = &eventStorage;
+ event->next = nullptr;
+
+ if (numUsed == 0)
+ {
+ firstEvent = lastEvent = event;
+ }
+ else
+ {
+ lastEvent->next = event;
+ lastEvent = event;
+ }
+
+ return ++numUsed == kMaxMidiEvents;
+ }
+ #endif
+
+ private:
+ bool placeSorted(const int32_t sampleOffset) noexcept
+ {
+ InputEventTiming* const event = &eventList[numUsed];
+
+ // initialize
+ if (numUsed == 0)
+ {
+ firstSampleOffset = lastSampleOffset = sampleOffset;
+ firstEvent = lastEvent = event;
+ event->next = nullptr;
+ }
+ // push to the back
+ else if (sampleOffset >= lastSampleOffset)
+ {
+ lastSampleOffset = sampleOffset;
+ lastEvent->next = event;
+ lastEvent = event;
+ event->next = nullptr;
+ }
+ // push to the front
+ else if (sampleOffset < firstSampleOffset)
+ {
+ firstSampleOffset = sampleOffset;
+ event->next = firstEvent;
+ firstEvent = event;
+ }
+ // find place in between events
+ else
+ {
+ // keep reference out of the loop so we can check validity afterwards
+ InputEventTiming* event2 = firstEvent;
+
+ // iterate all events
+ for (; event2 != nullptr; event2 = event2->next)
+ {
+ // if offset is higher than iterated event, stop and insert in-between
+ if (sampleOffset > event2->sampleOffset)
+ break;
+
+ // if offset matches, find the last event with the same offset so we can push after it
+ if (sampleOffset == event2->sampleOffset)
+ {
+ event2 = event2->next;
+ for (; event2 != nullptr && sampleOffset == event2->sampleOffset; event2 = event2->next) {}
+ break;
+ }
+ }
+
+ DISTRHO_SAFE_ASSERT_RETURN(event2 != nullptr, true);
+
+ event->next = event2->next;
+ event2->next = event;
+ }
+
+ return ++numUsed == kMaxMidiEvents;
+ }
+ } inputEventList;
+ #endif // DISTRHO_PLUGIN_WANT_MIDI_INPUT
+
public:
PluginVst3(v3_host_application** const host)
: fPlugin(this, writeMidiCallback, requestParameterValueChangeCallback, nullptr),
@@ -1285,7 +1565,8 @@ public:
#endif
#if DISTRHO_PLUGIN_WANT_MIDI_INPUT
- uint32_t midiEventCount = 0;
+ bool canAppendMoreEvents = true;
+ inputEventList.init();
#if DISTRHO_PLUGIN_HAS_UI
while (fNotesRingBuffer.isDataAvailableForReading())
@@ -1294,121 +1575,34 @@ public:
if (! fNotesRingBuffer.readCustomData(midiData, 3))
break;
- MidiEvent& midiEvent(fMidiEvents[midiEventCount++]);
- midiEvent.frame = 0;
- midiEvent.size = 3;
- std::memcpy(midiEvent.data, midiData, 3);
-
- if (midiEventCount == kMaxMidiEvents)
+ if (inputEventList.appendFromUI(midiData))
+ {
+ canAppendMoreEvents = false;
break;
+ }
}
#endif
- if (v3_event_list** const eventptr = data->input_events)
+ if (canAppendMoreEvents)
{
- v3_event event;
- for (uint32_t i = 0, count = v3_cpp_obj(eventptr)->get_event_count(eventptr); i < count; ++i)
+ if (v3_event_list** const eventptr = data->input_events)
{
- if (v3_cpp_obj(eventptr)->get_event(eventptr, i, &event) != V3_OK)
- break;
-
- // check if event can be encoded as MIDI
- switch (event.type)
+ v3_event event;
+ for (uint32_t i = 0, count = v3_cpp_obj(eventptr)->get_event_count(eventptr); i < count; ++i)
{
- case V3_EVENT_NOTE_ON:
- case V3_EVENT_NOTE_OFF:
- // case V3_EVENT_DATA:
- case V3_EVENT_POLY_PRESSURE:
- break;
- // case V3_EVENT_NOTE_EXP_VALUE:
- // case V3_EVENT_NOTE_EXP_TEXT:
- // case V3_EVENT_CHORD:
- // case V3_EVENT_SCALE:
- // case V3_EVENT_LEGACY_MIDI_CC_OUT:
- default:
- continue;
- }
-
- MidiEvent& midiEvent(fMidiEvents[midiEventCount++]);
- midiEvent.frame = event.sample_offset;
+ if (v3_cpp_obj(eventptr)->get_event(eventptr, i, &event) != V3_OK)
+ break;
- // encode event as MIDI
- switch (event.type)
- {
- case V3_EVENT_NOTE_ON:
- midiEvent.size = 3;
- midiEvent.data[0] = 0x90 | (event.note_on.channel & 0xf);
- midiEvent.data[1] = event.note_on.pitch;
- midiEvent.data[2] = std::max(0, std::min(127, (int)(event.note_on.velocity * 127)));
- midiEvent.data[3] = 0;
- break;
- case V3_EVENT_NOTE_OFF:
- midiEvent.size = 3;
- midiEvent.data[0] = 0x80 | (event.note_off.channel & 0xf);
- midiEvent.data[1] = event.note_off.pitch;
- midiEvent.data[2] = std::max(0, std::min(127, (int)(event.note_off.velocity * 127)));
- midiEvent.data[3] = 0;
- break;
- case V3_EVENT_POLY_PRESSURE:
- midiEvent.size = 3;
- midiEvent.data[0] = 0xA0 | (event.poly_pressure.channel & 0xf);
- midiEvent.data[1] = event.poly_pressure.pitch;
- midiEvent.data[2] = std::max(0, std::min(127, (int)(event.poly_pressure.pressure * 127)));
- midiEvent.data[3] = 0;
- break;
- default:
- midiEvent.size = 0;
- break;
+ if (inputEventList.appendEvent(event))
+ {
+ canAppendMoreEvents = false;
+ break;
+ }
}
-
- if (midiEventCount == kMaxMidiEvents)
- break;
}
}
-
- // TODO append parameter MIDI events in a sorted way
- /*
-#if DISTRHO_PLUGIN_WANT_PROGRAMS
- if (rindex == 0)
- continue;
- --rindex;
-#endif
-#if DISTRHO_PLUGIN_WANT_MIDI_INPUT
- MidiEvent& midiEvent(fMidiEvents[midiEventCount++]);
- midiEvent.frame = offset;
- midiEvent.size = 3;
- midiEvent.data[0] = (rindex / 130) & 0xf;
-
- switch (rindex)
- {
- case 128: // channel pressure
- midiEvent.data[0] |= 0xD0;
- midiEvent.data[1] = std::max(0, std::min(127, (int)(value * 127)));
- midiEvent.data[2] = 0;
- midiEvent.data[3] = 0;
- break;
- case 129: // pitchbend
- midiEvent.data[0] |= 0xE0;
- midiEvent.data[1] = std::max(0, std::min(16384, (int)(value * 16384))) & 0x7f;
- midiEvent.data[2] = std::max(0, std::min(16384, (int)(value * 16384))) >> 7;
- midiEvent.data[3] = 0;
- break;
- default:
- midiEvent.data[0] |= 0xB0;
- midiEvent.data[1] = rindex % 130;
- midiEvent.data[2] = std::max(0, std::min(127, (int)(value * 127)));
- midiEvent.data[3] = 0;
- break;
- }
-
- if (midiEventCount == kMaxMidiEvents)
- break;
- }
-#endif
- */
#endif
- // if there are any parameter changes at frame 0, set them here
if (v3_param_changes** const inparamsptr = data->input_params)
{
int32_t offset;
@@ -1424,12 +1618,32 @@ public:
#if DPF_VST3_HAS_INTERNAL_PARAMETERS
if (rindex < kVst3InternalParameterCount)
+ {
+ #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
+ // if there are any MIDI CC events as parameter changes, handle them here
+ if (canAppendMoreEvents && rindex >= kVst3InternalParameterMidiCC_start && rindex <= kVst3InternalParameterMidiCC_end)
+ {
+ for (int32_t j = 0, pcount = v3_cpp_obj(queue)->get_point_count(queue); j < pcount; ++j)
+ {
+ if (v3_cpp_obj(queue)->get_point(queue, j, &offset, &value) != V3_OK)
+ break;
+
+ if (inputEventList.appendCC(offset, rindex, value))
+ {
+ canAppendMoreEvents = false;
+ break;
+ }
+ }
+ }
+ #endif
continue;
+ }
#endif
if (v3_cpp_obj(queue)->get_point_count(queue) <= 0)
continue;
+ // if there are any parameter changes at frame 0, handle them here
if (v3_cpp_obj(queue)->get_point(queue, 0, &offset, &value) != V3_OK)
break;
@@ -1442,6 +1656,7 @@ public:
}
#if DISTRHO_PLUGIN_WANT_MIDI_INPUT
+ const uint32_t midiEventCount = inputEventList.convert(fMidiEvents);
fPlugin.run(inputs, outputs, data->nframes, fMidiEvents, midiEventCount);
#else
fPlugin.run(inputs, outputs, data->nframes);