macOS Plugin View Behaviour

I’ve got a plugin crash that I’m currently investigating and it stems from the following code:

struct AutoResizingNSViewComponentWithParent  : public AutoResizingNSViewComponent,
                                                private Timer
{
    AutoResizingNSViewComponentWithParent()
    {
        JUCE_IOS_MAC_VIEW* v = [[JUCE_IOS_MAC_VIEW alloc] init];
        setView (v);
        [v release];

        startTimer (30);
    }

    JUCE_IOS_MAC_VIEW* getChildView() const
    {
        if (JUCE_IOS_MAC_VIEW* parent = (JUCE_IOS_MAC_VIEW*) getView())
        {
            if ([[parent subviews] count] > 0)
                return [[parent subviews] objectAtIndex: 0];
        }

        return nil;
    }

    void timerCallback() override
    {
        if (JUCE_IOS_MAC_VIEW* child = getChildView())
        {
            stopTimer();
            setView (child);
        }
    }
};

What happens is:

  1. This AutoResizingNSViewComponentWithParent gets created and allocates and sets its own internal view (which you can see in the constructor)
  2. VSTPluginWindow::openPluginWindow is then called, passing in this newlyt created NSView
  3. The plugin takes a reference to that NSView
  4. After 30ms the timer callback happens and the AutoResizingNSViewComponentWithParent looks inside the view it created for any subviews and assigns one of those to the owned view
  5. The originally juce-created NSView is now deleted and the plugin holds a dangling reference to it
  6. The plugin tries to attach a new NSView (like a popup menu) to the dangling NSView
  7. Plugin crashes

Is there a reason for this async reassignment of the child?
And does anyone know if it’s valid for plugins to take a reference to the NSView passed in to the Vst2::effEditOpen call?

Hmm, thinking about this a bit more this is probably so that if the NSView that the plugin creates changes size, the AutoResizingNSViewComponentWithParent can respond to that?

Do we know exactly which plugins require this behaviour?

I’m going to have to raise this again as I’ve got several users complain that Softube plugins are crashing when their UIs are opened on macOS now and I can replicate the problem.

If I comment out that startTimer call in AutoResizingNSViewComponentWithParent, the plugin UIs successfully open. This problem is also evident in pluginval.


I’d like to say all plugin developers would update their plugins and not keep a reference to the passed in NSView but unfortunately I don’t think that’s going to happen and I’ve had several developers already tell me they won’t make that change.

Do we have any examples of plugins that resize a nested view but not the top level NSView?
I did try retaining the created NSView until the destruction of the AutoResizingNSViewComponentWithParent but that results in a blank UI.

I’m struggling to see other alternatives.

Ok, I’ve found a case that breaks if I remove the startTimer call: FabFilter Pro-C.
If I do this, the UI appears 1/4 size in the bottom left:

I’ve spent ages trying to get both Softube plugins to load and this plugin but to no avail.
I’ve tried forcing resizes all over the place, retaining the original view, passing the actual window to openPluginWindow and even closing and reopening the window to try and pass the child view back in to itself.


Basically I’m at a bit of a dead end. One thing I can say after speaking to some plugin developers is that they definitely don’t expect the pointer passed to Vst2::effEditOpen to be deleted on them.
The only thing I can think of at this time is to start adding exceptions for certain plugin manufacturers to not start the timer.

Any better ideas?

I’m wrestling with this issue as well, with JUCE 6.0.8.

I’ve put in some nasty hacks to AutoResizingNSViewComponentWithParent for now, but it would be great if this could get resolved once and for all in the framework.

I’m investigating this at the moment. Are you aware of any plugins, other than the following, which misbehave with the current setup in JUCE?

  • Softube plugins. I’m using Saturation Knob for testing, which crashes when the timer is present.
  • KORG Gadget series, which display a black screen when the timer is present.
  • FabFilter Pro-C, which displays at the wrong size when the timer is not present, if created on a retina display.

I have a potential fix which seems to resolve problems with the above plugins. However, it would be good to check with some other problematic plugins (if there are any) before pushing this new approach.

Can I ask what the potential fix is?

Sure, it’s very much WIP, but something like this seems to work for the three problematic plugins above. I haven’t tested this on iOS yet, so there’s a chance it’ll break things there.

struct AutoResizingNSViewComponentWithParent : public AutoResizingNSViewComponent,
                                               private Timer
{
    AutoResizingNSViewComponentWithParent()
        : view ([[[JUCE_IOS_MAC_VIEW alloc] init] autorelease])
    {
        setView (view);

        // Some FabFilter plugins may open at the wrong scale, so we'll try giving them
        // a nudge once they've had a chance to display.
        startTimer (30);
    }

    ~AutoResizingNSViewComponentWithParent() override { stopTimer(); }

    void resized() override
    {
        const auto currentBounds  = convertToRectFloat ([view frame]);
        const auto requiredBounds = getLocalBounds().toFloat();

        if (currentBounds != requiredBounds)
        {
            const auto convertedBounds = convertToCGRect (requiredBounds);
            [view setFrame: convertedBounds];

            for (JUCE_IOS_MAC_VIEW* subview in [view subviews])
                [subview setFrame: convertedBounds];
        }
    }

private:
    void timerCallback() override
    {
        stopTimer();
        resized();
    }

    NSView* view = nullptr;
};

After a bit of testing, this still doesn’t seem to resize Pro-C properly. It opens at the right scale, but any additional resizing causes it to break again. I’ll have another go at this tomorrow.

All I did was rework the AutoResizingNSViewComponentWithParent constructor, adding a bool parameter to control whether or not the timer gets started. I only disable the timer for Softube plug-ins, which at least makes them usable on the Mac.

I’ve spent a bit more time looking at this, and unfortunately still haven’t managed to find a solution which works for all the plugins mentioned here. The most problematic plugin is Pro-C - all other plugins I’ve tried seem to work properly when the timer code is removed completely. Pro-C seems to be somewhat broken in every configuration I’ve tried, though. Even on the current develop, the view reverts to the incorrect size if the plugin view is resized at all after the timer has fired (at least on Catalina with a retina display - it’s less broken on Big Sur).

Given that Pro-C is slightly broken with or without the timer, and that removing the timer allows us to avoid breakage in Korg and Softube plugins, my current inclination is to just remove the timer. This should allow Korg and Softube plugins to work, without degrading the experience of using Pro-C.

Do you have any thoughts on this approach? Do you foresee it causing issues on your side?

For us I think that would mean that Pro-C would appear 1/4 of the desired size? I can’t see anyway we’d be able to make it the correct size then?

Yes, it’s not ideal. I was thinking that, in a trade-off between Softube plugins crashing the host, and Pro-C rendering incorrectly, the latter scenario is likely preferable. However, I can see that existing Pro-C users may not feel the same way.

I’ll try to think of a better solution.

If it’s really only Pro-C that behaves this way, could you add an exception for it? At least it would document the behaviour. That way, if there are any more reports of plugins appearing 1/4 size, they could be added to the exception list?

Not ideal but might save some time.

Is there an Apple AU host example somewhere? Does this behave correctly? If so, what do they do with the parent view?

I’ve pushed a potential fix now. This seems to do the right thing for all the problematic plugins mentioned in this thread.

As always, please let me know if this change introduces any new issues.

I have a similar issue when hosting VST3 plugins on OSX. I would like to set the editor of the hosted plugin as a child component of my editor, so embed its editor into my GUI. It works fine in case of most plugins, but in case of Native Instrument plugins a new native window is created when the plugin is loaded. It happens only on OSX and only in case of VST3 format. On Windows it works fine. Has anybody else already run into the same problem?
I use JUCE 6.1.2.
Thank you!

Please can you let us know exactly which plugins are causing issues for you so that we can try to reproduce the problem here? Thanks!

We have this issue with NI Choral and NI Raum. Thank you!

I can’t see these plugins provided as VST3, only as VST2, AU, and AAX. Am I missing something, or are you testing a format other than VST3?

I can repro the issue with the VST2 versions of the plugins, so I’ll see if there’s anything we can do to fix that.

2 Likes

I’m sorry, my first problem description was not correct, I have the issue with the VST2. Exactly as you reproduced. Thank you!

1 Like