Some info re: sending out MIDI system exclusive messages from a VST3

I just wanted to share an issue I encountered with the way Juce adds MIDI system exclusive messages to the VST3 wrapper’s outgoing event list. I wouldn’t say it’s a bug, but it is something you should be aware of if you want to output SysEx messages from your plugin.
The relevant code for this resides in juce_VST3Common.h lines 560 to 566:

else if (msg.isSysEx())
    e.type          = Steinberg::Vst::Event::kDataEvent;    = midiEventData + 1;     = (uint32) msg.getSysExDataSize();     = Steinberg::Vst::DataEvent::kMidiSysEx;

Notice that in this code the “begin SysEx” byte (xF0) and the “end SysEx” byte (xF7) are not included in the event that gets sent out. I think that there is an expectation that the plugin’s host program will add back the wrapper bytes when the event gets handed off to it. Unfortunately, not all hosts do this.

As an experiment, I created a simple VST3 that requests a data dump from a hardware synthesizer via a system exclusive message. When I run the plugin in Juce’s AudioPluginHost, it appears that the xF0/xF7 bytes get added back to the request message, the synthesizer recognizes it, and it responds with the data dump. When I run the plugin in Reaper, however, the wrapper bytes do not get added and the synthesizer does not recognize the request message.

I then modified the Juce code so that the wrapper bytes are included in the event:

else if (msg.isSysEx())
    e.type          = Steinberg::Vst::Event::kDataEvent;    = midiEventData;     = (uint32) msg.getRawDataSize();     = Steinberg::Vst::DataEvent::kMidiSysEx;

The request message now comes out as properly-formed SysEx in Reaper, the synthesizer recognizes it, and it responds with the data dump. In AudioPluginHost, I think the message winds up looking like xF0 xF0 [message bytes] xF7 xF7, but the synthesizer still recognizes the request message and responds appropriately. I think in the vast majority of cases the duplicate SysEx wrapper bytes will just be ignored by the target device, but there may be some devices out there that will get confused, so use this with caution.


I’ve done further experimentation with this and have determined that a SysEx message with the form F0 F0 [message bytes] F7 F7 will not be recognized by the target device. The second F0 is not considered the start of a new SysEx message, it is interpreted as the first byte of the message having the value F0. This will make whatever message you’re trying to send invalid. I also tried putting an F7 byte at the start of the SysEx message so it would have the form F0 F7 F0 [message bytes] F7 F7. Unfortunately, when the host hits the first F7 byte, it simply ignores the subsequent bytes in the message and the resulting output is F0 F7. What this all boils down to is that it is necessary to compile a separate version of the plugin that strips off the wrapper bytes in order for it to function properly in hosts that add the wrapper bytes back on.

Experiencing the same thing right now. Juce strips off f0/f7 when sending a kDataEvent and adds them when it receives it. For me as a plugin dev, this results in getting sysex that has got f0,f0,…f7,f7. I now handle that gracefully, but my testing host (VSTHost) does not accept the outgoing packets.

So sad that Steinberg does not clearly state if f0/f7 wrap needs to be part of the kDataEvent or not.

1 Like

If the Steinberg API definition is not clear, the transporting software (DAW) and the other sender/target device might be on error, as well. Obviously the Midi spec is clear (no double F0 etc). Hence for testing we should use a monitor that provides a testing API for the plugin and simply protocols the bytes in the stream.

Same problem here. Sending sysex from Raper or VSTHost with a JUCE VST3 plugin does not work. They expect the full sysex command including 0xF0 and 0xF7. No problem with the VST2.

I will see if I can get a Cubase license. This will show us how the plugin format creators implemented it. I report back.

I am working on a plugin that needs to send Sysex (eventually, although I haven’t implemented it yet). This sounds like a big problem, I will be watching this thread!

I was able to reproduce the problem in Cubase 12. I have created a bug report about this here:

This is also a problem for me. I have just reproduced the issue with JUCE 7.0.9 and Reaper 6.82 on Mac with VST3.

There is now a fix in the JUCE develop branch.

1 Like