Need help on legacy JUCE, VST3 and Cubase ASIO Guard

Recently I attempted to enable VST3 support on several of our virtual instrument products that is developed using very old JUCE3. They relies on many JUCE3-specific APIs so that I cannot update its dependent JUCE to current version. After some work, the plugin builds and can be loaded by Cubase 12. However, when I try to create multiple instrument tracks and play on one of them, other non-playing instrument instances produce crappy audio outputs that hears like very short slices from the playing instance.

This issue only occur when Cubase’s Multithread processing and ASIO Guard features are both enabled, and the plugin is loaded as VST3 (we made the same dynamic library loadable as VST2/VST3/AU/AAX). It even happens when I load different instruments (developed on same JUCE3 code base)!

If the plugin is loaded as VST2 by Cubase, it won’t happen; if Cubase ASIO Guard is disabled, it won’t happen; it won’t happen on all other host DAWs like Logic Pro, REAPER.

I made further dive on the runtime behavior of these plugin instances, and I found that:

  • When there are unexpected audio outputs, the address of the audio buffer (which is actually provided from VST3’s process callback) are same.
  • I have set zero on the beginning of the processBlock but it won’t solve the problem at all.
  • If I create a separate audio buffer and perform all rendering/post processing on that, and copy the contents back to the API-provided buffer at last, it would greatly reduce the issue (become very rare).

All these gives me a feel that these buffers are not exclusively accessed by threads of these plugin instances, and they are accidentally contaminated by other plugin instances.

I then tried to find the difference on juce_VST3_Wrapper.cpp between JUCE3 and current version of JUCE, especially processing callback process (Vst::ProcessData&). Both have lock related code const ScopedLock sl (pluginInstance->getCallbackLock()); before processBlock calls.

I also tried to backport the newest VST3 SDK to our very old JUCE3 codebase. After some efforts it compiles and loads, but does not solve this issue at all.

Currently I have completely no clue about the problem. As there are massive changes between JUCE3 to current codebase, I guess it won’t happen nowadays. So I write this post here to ask if anyone knows some clues about it?

After further diving in different versions of juce_VST3_Wrapper.cpp, I possibly find the reason.

VST3 SDK give you input and output buffers separately, while JUCE’s AudioProcessor give them in one set of AudioSampleBuffer / AudioBuffer<> object. In old versions of JUCE, the wrapper code wraps input buffer to AudioSampleBuffer object, and copies input buffer to output buffer after AudioProcessor processing. So my processor do write operations on input buffer, which is probably not expected by host DAWs, especially in multithread environments. In newer versions of JUCE, the wrapper code firstly copy from input buffer to output buffer, then wrap output buffer to AudioBuffer<> object and give to AudioProcessor. This keeps input buffer untouched.

Backport works are really nasty due to massive API changes from JUCE3 to JUCE7, and I will give a final reply after I finished backport work and test it.