This is quite relevant, and explains the problem.
The problem is this: VST3 does not fully and correctly support MIDI. Full stop. It has its own ideas about events and parameters, and thinks it is smarter than decades of industry standards and is entitled to replace them with its own ideas. I took a look at both the JUCE code and the VST3 SDK. Here’s what’s happening:
VST3 does not directly support MIDI CCs as events. It only supports note on/off, sysex, and non-MIDI stuff they made up, like note expression. This is implemented in the (processData) buffer as the inputEvents list. Note this is a list, in order, and each event has a sample offset and ppq timestamp. Events at the same timestamp retain order.
Parameter changes are delivered (in the same processData buffer) in a separate inputParameterChanges list (also in order, with timestamps). Parameters at the same timestamp retain order.
Steinberg expects all plugin developers to stop using CCs and instead use parameters, and the entire virtual MIDI-based plugin ecosystem is supposed to go through gyrations doing a mapping of one to the other (there are tons of threads about problems with this). Besides the hubris behind this disregard for existing practice (has Steinberg never seen any of the various MIDI processing scripting plugins?), there is a more insidious problem. In splitting the MIDI event stream the VST3 system is no longer capable of expressing the relative order of MIDI events - VST3 breaks the serial semantics of MIDI. I wonder if the guys who did this are even as old as MIDI? ![]()
The net result is that a MIDI stream of noteA ccB noteC ccD becomes two lists: noteA noteC (events) and ccB ccD (as “parameter changes”). A kludge inside JUCE, similar to the kludge in every MIDI-based plugin converted to VST3, has to read the parameter changes, convert them back into CC events and insert them back into the event list. But where? It no longer has the information that the order is ABCD - VST3 has lost it. This is not a parallelism or concurrency thing, it is just a bad API design. So plugins have to decide when doing this kludge to either put the params-to-ccs (on same tick) either before or after the notes. All JUCE did for Pianoteq was put them before instead of after.
What does this mean for us poor souls who have to make expression maps with this? You can expect CCs on same tick to retain order, and notes as well, but not an intermixing of CCs and notes. JUCE now puts notes last. I tested this hypothesis with my Dorico->VEP->Bidule (inside VEP) stack, and setup an expression map that interleaved CCs, PCs, and keyswitch notes. Sure enough, VEP7 also puts notes last (I’m not sure if they use JUCE).
Thus when I send this:
006B1600 0FD B0 12 00 01 CC(18)
006B1600 0FD 90 00 64 01 Note On (C0) (keyswitch)
006B1600 0FD B0 04 40 01 CC(4)
006B1600 0FD C0 29 00 01 Program(41)
006B1600 0FD B0 02 40 01 CC(2)
006B1800 0E4 90 45 3D 01 Note On (A5) (note in score)
the plugin gets this (note the C0 keyswitch gets pushed down):
006B1600 0FD B0 12 00 01 CC(18)
006B1600 0FD B0 04 40 01 CC(4)
006B1600 0FD C0 29 00 01 Program(41)
006B1600 0FD B0 02 40 01 CC(2)
006B1600 0FD 90 00 64 01 Note On (C0) (keyswitch)
006B1800 0E4 90 45 3D 01 Note On (A5) (note in score)
Synchron player and VI Pro would not be bothered by this, as they remember CCs. They very much care that keyswitches retain order because not all of their KS articulations/matrices have the same number of KS types/dimensions. If the type/dimension KS comes first it will break.
So, again there’s no parallelism/concurrency issue, nor is it a complete random free-for-all on the same tick. MIDI event stream splitting is just a bad API design of VST3 that is incapable of conveying a MIDI event stream intact to a plugin. For a company with the MIDI heritage of Steinberg (I was using Pro 24 in 1987) this is really sad. The sky is not falling, but it is raining. VST3 caveat emptor.
I hope this helps people understand and move forward.