[BUG] setLatencySamples crashes Ableton Live 10 (VST3) [FIXED]

Ableton Live 10 crashes upon loading a saved project with a VST3 plugin that uses setLatencySamples. No call stack or any specific error in debug. VST2 is fine, other hosts seems to be fine as well.

The bug is present in both JUCE 5.4.7 develop and master tips.
Plugins compiled with JUCE 5.4.3 seem to be working instead.

A workaround is to delay the setLatencySamples call when loading for the first time. Waiting for 10-15 buffers at 512 samples seems to be enough.

Edit - this is the commit that introduced this issue: https://github.com/juce-framework/JUCE/commit/d00d23139c659d3fc823e368502f0c47d14f0c04

If it’s a Ableton bug, they should be informed.

Which Operating System?

No call stack or any specific error in debug.

Did you run the Ableton in the debugger? Did you turn on all exceptions in the debugger?

A workaround is to delay the setLatencySample

I think its better to find out the real reason, before introducing such workarounds

Plugins compiled with JUCE 5.4.3 don’t have this issue. But, yeah, I’ll reach out to them as well.

Both macOS and Windows

I run Ableton Live in the debugger (Xcode). It just quits without showing any call stack. I’ll try to set an all exception breakpoint and see what happens.

Apparently there is a workaround for this:

Start debugging Live from XCode as per usual.
Detach debugger from Live (Debug -> Detach)
Reattach debugger to Live (Debug -> Attach to Process)

Not tried this myself (although seen this symptom occasionally), but was mentioned in another thread recently as a working way to get the debugger to catch such exceptions.

I’ve tried an all exceptions breakpoint but still got the same result.

Thank you @richie that actually worked, although not sure if it helps. Here’s the call stack:

MainThread (1) Queue : com.apple.main-thread (serial)
#0	0x0000000100fc7308 in ___lldb_unnamed_symbol53513$$Live ()
#1	0x0000000100fd83e7 in ___lldb_unnamed_symbol53920$$Live ()
#2	0x00000001025769c4 in ___lldb_unnamed_symbol155193$$Live ()
#3	0x000000010256cc68 in ___lldb_unnamed_symbol155008$$Live ()
#4	0x000000010256bb83 in ___lldb_unnamed_symbol154986$$Live ()
#5	0x00000001013832ee in ___lldb_unnamed_symbol77439$$Live ()
#6	0x0000000101383098 in ___lldb_unnamed_symbol77436$$Live ()
#7	0x000000010139459e in ___lldb_unnamed_symbol77892$$Live ()
#8	0x0000000101953fde in ___lldb_unnamed_symbol100768$$Live ()
#9	0x0000000101c52851 in ___lldb_unnamed_symbol117821$$Live ()
#10	0x0000000101e34f32 in ___lldb_unnamed_symbol127747$$Live ()
#11	0x0000000101e4c1f6 in ___lldb_unnamed_symbol128239$$Live ()
#12	0x0000000101ed693d in ___lldb_unnamed_symbol130638$$Live ()
#13	0x000000010057ab09 in ___lldb_unnamed_symbol12031$$Live ()
#14	0x00000001005a3146 in ___lldb_unnamed_symbol12670$$Live ()
#15	0x00000001011de7ab in ___lldb_unnamed_symbol66312$$Live ()
#16	0x00000001011df8b8 in ___lldb_unnamed_symbol66335$$Live ()
#17	0x00000001010e03fb in ___lldb_unnamed_symbol58503$$Live ()
#18	0x00000001011ded5d in ___lldb_unnamed_symbol66316$$Live ()
#19	0x000000010117a8e1 in ___lldb_unnamed_symbol62912$$Live ()
#20	0x00000001010c12ec in ___lldb_unnamed_symbol57992$$Live ()
#21	0x00007fff4113060b in -[NSApplication(NSResponder) sendAction:to:from:] ()
#22	0x00007fff4118d63f in -[NSMenuItem _corePerformAction] ()
#23	0x00007fff4118d3b2 in -[NSCarbonMenuImpl performActionWithHighlightingForItemAtIndex:] ()
#24	0x00007fff411f1b12 in -[NSMenu performActionForItemAtIndex:] ()
#25	0x00007fff411f1a7d in -[NSMenu _internalPerformActionForItemAtIndex:] ()
#26	0x00007fff411f18ae in -[NSCarbonMenuImpl _carbonCommandProcessEvent:handlerCallRef:] ()
#27	0x00007fff4115b9fc in NSSLMMenuEventHandler ()
#28	0x00007fff42b7aaf9 in DispatchEventToHandlers(EventTargetRec*, OpaqueEventRef*, HandlerCallRec*) ()
#29	0x00007fff42b79e36 in SendEventToEventTargetInternal(OpaqueEventRef*, OpaqueEventTargetRef*, HandlerCallRec*) ()
#30	0x00007fff42b9729d in SendEventToEventTarget ()
#31	0x00007fff42bea36f in SendHICommandEvent(unsigned int, HICommand const*, unsigned int, unsigned int, unsigned char, void const*, OpaqueEventTargetRef*, OpaqueEventTargetRef*, OpaqueEventRef**) ()
#32	0x00007fff42c12ac6 in SendMenuCommandWithContextAndModifiers ()
#33	0x00007fff42c12a78 in SendMenuItemSelectedEvent ()
#34	0x00007fff42c1295d in FinishMenuSelection(SelectionData*, MenuResult*, MenuResult*) ()
#35	0x00007fff42c13319 in MenuSelectCore(MenuData*, Point, double, unsigned int, OpaqueMenuRef**, unsigned short*) ()
#36	0x00007fff42c1302e in _HandleMenuSelection2 ()
#37	0x00007fff40e42585 in _NSHandleCarbonMenuEvent ()
#38	0x00007fff40e423ea in _DPSEventHandledByCarbon ()
#39	0x00007fff40e3645c in -[NSApplication(NSEvent) _nextEventMatchingEventMask:untilDate:inMode:dequeue:] ()
#40	0x00007fff40e30165 in -[NSApplication run] ()
#41	0x00000001010be00c in ___lldb_unnamed_symbol57946$$Live ()
#42	0x00007fff70b35ed9 in start ()
#43	0x00007fff70b35ed9 in start ()

MainThread (1): EXC_BAD_ACCESS (code=1, address=0x0)

Live`___lldb_unnamed_symbol53513$$Live:
    0x100fc7300 <+0>:  pushq  %rbp
    0x100fc7301 <+1>:  movq   %rsp, %rbp
    0x100fc7304 <+4>:  movq   0x78(%rdi), %rdi
->  0x100fc7308 <+8>:  movq   (%rdi), %rax
    0x100fc730b <+11>: movq   0x88(%rax), %rax
    0x100fc7312 <+18>: popq   %rbp
    0x100fc7313 <+19>: jmpq   *%rax
    0x100fc7315 <+21>: nopw   %cs:(%rax,%rax)

Does it make any difference if you leave/remove the VST2 binary of that plug-in from the path where Live searches for them? It’s a shot in the dark, but sometimes I’ve seen that playing a role

Thanks for the suggestion. I’ve tried it and made no difference, unfortunately.

Definitely something changed between 5.4.3 and 5.4.7.

Same here. Ableton Live 10, using VST3 plugins built with JUCE 5.4.7 show the same issue. No crash with plugins we previously built with JUCE 5.4.3

EDIT: looks like the issue starts from JUCE 5.4.6. If I build with 5.4.5, the VST3 won’t crash while it does building with 5.4.6.

The suggested workaround works, but I use a different way: I set an uint32 global variable named paramCalled and saving a Time::getMillisecondCounter() in it.

When I have to call setLatencySamples, I check if Time::getMillisecondCounter()-paramCalled>150, then I call setLatencySamples. Lower ms values often won’t work.

This workaround helps to avoid Live 10 crashing, but I’m afraid that delaying the setLatencySamples could make offline renders on some hosts not sounding as expected.

My 2 cents

Just in case -I think latency can only be reported from the message thread. From here:

Q: How report to the host that the Plug-in latency has changed?
The Plug-in should call from the editController its component handler with flag kLatencyChanged: componentHandler->restartComponent (kLatencyChanged);

and here:

virtual tresult restartComponent (int32 flags)
Instructs host to restart the component.
This must be called in the UI-Thread context!

I guess each host may tolerate a non UI thread call differently. I’ve had different responses from different versions of Cubase, but no problems with Live 10 calling from the message thread. Maybe restartComponent was called asynchronously in 5.4.6, and synchronously now?

1 Like

Really? I remember a few examples where it was suggested to use it in the processBlock (and in processBlockBypassed). I’ve used it there for years without issues. I also remember I had issues when setLatencySamples was only placed in prepareToPlay (some hosts didn’t like that), especially when you have a dynamic latency that can change with different parameters/presets.

Anyway, I found the commit that introduced this bug: VST3: Added the ability to change parameter names at runtime · juce-framework/JUCE@d00d231 · GitHub

Again, this seems to be only happening in Ableton Live 10 (on both macOS and Windows).

Looks like the issue is triggered by Vst::kParamTitlesChanged in this check:

if (componentHandler != nullptr && ! inSetupProcessing)
            componentHandler->restartComponent (Vst::kLatencyChanged | Vst::kParamValuesChanged | Vst::kParamTitlesChanged);

By removing it, Ableton won’t crash

if (componentHandler != nullptr && ! inSetupProcessing)
            componentHandler->restartComponent (Vst::kLatencyChanged | Vst::kParamValuesChanged);

Just in case -I think latency can only be reported from the message thread.

There is a workflow diagram in the root docs (can’t get a link to it → go to Steinberg Plug-in Interfaces Documentation and click the “workflow diagrams” on the left hand side near the bottom) showing the call sequence for reporting a latency change from the audio thread.

What’s supposed to happen is the audio processor notifies the host, which then restarts the component, suspends audio processing and waits for the latency to be changed on the audio thread. I’m a little unsure of what interface you’re supposed to call on the host from the plugin to send that message though.

This also seems related to this other thread where I complained about parameter name changes triggering kLatencyChanged component restarts. The whole latency change and updateHostDisplay() mechanism is currently not done well in the Juce vst3 wrapper.

Right now we have crashes from changing latency plus processing gaps from changing parameter names :frowning: in some hosts. The wrapper really should be much more clever when it comes to calling restartComponent and only send the flags that are really needed!

2 Likes

I totally agree. In the meantime I’ll comment those lines in the wrapper, to avoid our plugins to crash in Ableton. Since most of our products are non-linear processors, it’s very common to our users to save a project having the oversampling enabled.

Hmm, but then your plugins won’t report the latency to the host…?

There’s an example in the VST3 SDK (public.sdk/samples/vst/hostchecker) where this is done -the processor calls sendMessage() with id “Latency”, then the controller receives the message with the same id through notify(), and calls restartComponent. This is underdocumented to say the least. Anyway, Juce is not doing it -setLatencySamples calls updateHostDisplay, audioProcessorChanged, restartComponent.

yes, they will. I only “reverted” that method to what it was before the commit that caused Live 10 to crash. We don’t change parameter names, so I can live without that in the meantime.

Right, I’ll do that asap myself… on the other thread I ended up disabling parameter name changes for vst3 anyway because it caused skips in the audio… and now even crashes. Would be nice if this stuff would work, but I guess Ableton needs to fix this crash and the JUCE team should have another look at the vst3 wrapper and restartComponent().

I dived a bit in the SDK. sendMessage/notify is just a connection mechanism between component/processor and controller, the “host” is the PlugProvider which sets up a pair of connection proxies to pass messages from one side to the other. When this is used, messages not coming from the UI thread (specifically, from the thread that created the connections) are just dropped -there’s a threadChecker for that, and a comment: “TODO we should test if we are in UI main thread else postpone the message”. Juce doesn’t need this because the wrapper owns both component and controller, so it makes sense to call restartComponent directly, but it seems clear which thread it should be called from. It makes sense in the same way that it makes sense to call prepareToPlay from the UI thread. As the diagram shows, VST expects the processor to hold the actual change of latency until the next call to prepareToPlay (changeAlgo inside setActive(true)). Whether a host tolerates a non-UI thread call is another thing. In Cubase 5/6, it makes a very long pause and sometimes freezes, versus a short pause when called from the UI thread. In Cubase 9/10, it seems to be indifferent. It’s possible that they’re postponing the call in the host to deal with non-compliant plugins.

Still, Juce jumbling all flags in a single call is a mess in any case.

2 Likes