CRASH in WebBrowserComponent::Pimpl::Pimpl in StudioOne Mac (VST2 & VST3)

Reproduction:

  • create VST2 instrument plugin with an editor that has a WebBrowserComponent
  • open Studio One
  • add plugin to session
  • click remove
  • add plugin a second time

Observed:

The Plug-In crashes in WebBrowserComponent::Pimpl::Pimpl()

Expected:

The plugin does not crash (yes, I linke pointing out the blatantly obvious)

Remarks:

Neither VST3 nor AU crash. It also does not Crash in Live, FL Studio or Reaper. The VST crashes when the plugin is added the second time, the VST3 when added a third time. AU doesn’t crash. I get that this may be Studio One specific, but I’d be glad to hear about workarounds or if someone else encountered this before.

PIP:

NOTE: you still need to set-up the VST SDK, enable the “VST (Legacy)” format and enable “Plugin is a Synth” and “Plugin MIDI Input”.

/*******************************************************************************
 The block below describes the properties of this PIP. A PIP is a short snippet
 of code that can be read by the Projucer and used to generate a JUCE project.

 BEGIN_JUCE_PIP_METADATA

  name:             MyPluginPIP

  dependencies:     juce_audio_basics, juce_audio_devices, juce_audio_formats, juce_audio_plugin_client, 
                    juce_audio_processors, juce_audio_utils, juce_core, juce_data_structures, juce_events, 
                    juce_graphics, juce_gui_basics, juce_gui_extra
  exporters:        xcode_mac

  moduleFlags:      JUCE_STRICT_REFCOUNTEDPOINTER=1

  type:             AudioProcessor
  mainClass:        MyPlugin

 END_JUCE_PIP_METADATA

*******************************************************************************/

#pragma once


//==============================================================================
class MyPlugin  : public AudioProcessor
{
public:
    //==============================================================================
    MyPlugin()
        : AudioProcessor (BusesProperties().withInput  ("Input",  AudioChannelSet::stereo())
                                           .withOutput ("Output", AudioChannelSet::stereo()))
    {
    }

    ~MyPlugin()
    {
    }

    //==============================================================================
    void prepareToPlay (double, int) override
    {
        // Use this method as the place to do any pre-playback
        // initialisation that you need..
    }

    void releaseResources() override
    {
        // When playback stops, you can use this as an opportunity to free up any
        // spare memory, etc.
    }

    void processBlock (AudioBuffer<float>& buffer, MidiBuffer&) override
    {
        ScopedNoDenormals noDenormals;
        auto totalNumInputChannels  = getTotalNumInputChannels();
        auto totalNumOutputChannels = getTotalNumOutputChannels();

        // In case we have more outputs than inputs, this code clears any output
        // channels that didn't contain input data, (because these aren't
        // guaranteed to be empty - they may contain garbage).
        // This is here to avoid people getting screaming feedback
        // when they first compile a plugin, but obviously you don't need to keep
        // this code if your algorithm always overwrites all the output channels.
        for (auto i = totalNumInputChannels; i < totalNumOutputChannels; ++i)
            buffer.clear (i, 0, buffer.getNumSamples());

        // This is the place where you'd normally do the guts of your plugin's
        // audio processing...
        // Make sure to reset the state if your inner loop is processing
        // the samples and the outer loop is handling the channels.
        // Alternatively, you can process the samples with the channels
        // interleaved by keeping the same state.
        for (int channel = 0; channel < totalNumInputChannels; ++channel)
        {
            auto* channelData = buffer.getWritePointer (channel);

            // ..do something to the data...
        }
    }

    //==============================================================================
    class Editor : public AudioProcessorEditor
    {
        public:
            Editor(AudioProcessor* p) : AudioProcessorEditor(p)
            {
                browser = std::make_unique<WebBrowserComponent>(false);
                setSize(500, 500);
            }

            void paint (Graphics& g) override
            {
                    g.fillAll(Colours::crimson);
            }

            void resized() override
            {
                browser->setBounds(getLocalBounds());
            }

        private:
            std::unique_ptr<WebBrowserComponent> browser;
    };

    //==============================================================================
    AudioProcessorEditor* createEditor() override          { return new Editor(this); }
    bool hasEditor() const override                        { return true;   }

    //==============================================================================
    const String getName() const override                  { return "MyPluginPIP"; }
    bool acceptsMidi() const override                      { return false; }
    bool producesMidi() const override                     { return false; }
    double getTailLengthSeconds() const override           { return 0; }

    //==============================================================================
    int getNumPrograms() override                          { return 1; }
    int getCurrentProgram() override                       { return 0; }
    void setCurrentProgram (int) override                  {}
    const String getProgramName (int) override             { return {}; }
    void changeProgramName (int, const String&) override   {}

    //==============================================================================
    void getStateInformation (MemoryBlock& destData) override
    {
        // You should use this method to store your parameters in the memory block.
        // You could do that either as raw data, or use the XML or ValueTree classes
        // as intermediaries to make it easy to save and load complex data.
    }

    void setStateInformation (const void* data, int sizeInBytes) override
    {
        // You should use this method to restore your parameters from this memory block,
        // whose contents will have been created by the getStateInformation() call.
    }

    //==============================================================================
    bool isBusesLayoutSupported (const BusesLayout& layouts) const override
    {
        // This is the place where you check if the layout is supported.
        // In this template code we only support mono or stereo.
        if (layouts.getMainOutputChannelSet() != AudioChannelSet::mono()
            && layouts.getMainOutputChannelSet() != AudioChannelSet::stereo())
            return false;

        // This checks if the input layout matches the output layout
        if (layouts.getMainOutputChannelSet() != layouts.getMainInputChannelSet())
            return false;

        return true;
    }

private:
    //==============================================================================
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MyPlugin)
};

Feeds’n’Speeds:

  • macOS 10.14.6
  • Studio One 3.5.6.46910 OSX x64 (Built on Mar 20 2018)
  • JUCE 02bbe31c0d2fb59ed32fb725b56ad25536c7ed75 (tip of master as of this time)