BUG - juce_AudioUnitPluginFormat.mm

Hi JUCE devs,

I’m working on an audio plug-in host using Juce, and I’ve found a distinct problem with hosting on iOS.

The app’s transport control starts/stops the hosted plug-ins at various times.

It does this by calling prepareToPlay() on each plug-in in response to start events in the UI.

That calls releaseResources() in the plug-in.

That in turn calls AudioUnitReset (audioUnit, kAudioUnitScope_Global, 0);

That in turn seems to lose the plug-in callbacks - so on 2nd playback and later, the Audio Unit stops responding!

My fix was to do this:

    void prepareToPlay (double newSampleRate, int estimatedSamplesPerBlock) override
    {
        if (audioUnit != nullptr)
        {
            releaseResources();

            createPluginCallbacks(); // MPC added!

… that restores the callbacks, and the AudioUnit now works on 2nd and subsequent playbacks.

This looks like a clear bug to me. Am I missing something?!

Best wishes to all,

Pete

Thanks for the report. It looks like some AUs will remove the host callbacks when being uninitialised or reset so we need to re-add them in prepareToPlay(). We don’t want to re-add the parameter listeners on macOS though so the createPluginCallbacks() call needs to be modified a bit to split out the callback/listener logic.

We’ve got a fix for this that will go onto develop after the 6.1.1 bugfix release.

Fabulous, @ed95 ! Looking forward to seeing the patch.

Best wishes, Pete

This is on develop now:

Thanks @ed95 !

With many thanks to the JUCE team - Wotja 22 is going to be a great release.

Best wishes,

Pete

Dear Juce Devs (@ed95)

Thanks to one of one Beta testers, we’ve found a pretty serious bug in your patch for this issue.
It happens when a hosted AUv3 tries to use the “AU Clock” for timing events

Example:

  1. Use hosted miRack AUv3 - set-up trivial patch connecting “CLOCK” to “TRIG” input on Mutable Instrument’s Macro Oscillator 2.
  2. Start playback from host - all is good
  3. Stop playback from host
  4. Re-start playback on host - miRack doesn’t generate notes; timings are missing somehow

NB: any other configurations / plug-ins that don’t rely on (host) AU Clock, things all play fine

Solution: patch juce_AudioUnitPluginFormat.mm as per the attached update.

Best wishes,

Pete

    void prepareToPlay (double newSampleRate, int estimatedSamplesPerBlock) override
    {
        if (audioUnit != nullptr)
        {
            releaseResources();

            // MARK: MPC this is in the RIGHT PLACE (begin)
            // See https://forum.juce.com/t/bug-juce-audiounitpluginformat-mm/47638
            setPluginCallbacks();
            // MARK: MPC this is in the RIGHT PLACE (end)

            for (int dir = 0; dir < 2; ++dir)
            {
                const bool isInput = (dir == 0);
                const AudioUnitScope scope = isInput ? kAudioUnitScope_Input : kAudioUnitScope_Output;
                const int n = getBusCount (isInput);

                for (int i = 0; i < n; ++i)
                {
                    Float64 sampleRate;
                    UInt32 sampleRateSize = sizeof (sampleRate);
                    const Float64 sr = newSampleRate;

                    AudioUnitGetProperty (audioUnit, kAudioUnitProperty_SampleRate, scope, static_cast<UInt32> (i), &sampleRate, &sampleRateSize);

                    if (sampleRate != sr)
                    {
                        if (isAUv3) // setting kAudioUnitProperty_SampleRate fails on AUv3s
                        {
                            AudioStreamBasicDescription stream;
                            UInt32 dataSize = sizeof (stream);
                            auto err = AudioUnitGetProperty (audioUnit, kAudioUnitProperty_StreamFormat, scope, static_cast<UInt32> (i), &stream, &dataSize);

                            if (err == noErr && dataSize == sizeof (stream))
                            {
                                stream.mSampleRate = sr;
                                AudioUnitSetProperty (audioUnit, kAudioUnitProperty_StreamFormat, scope, static_cast<UInt32> (i), &stream, sizeof (stream));
                            }
                        }
                        else
                        {
                            AudioUnitSetProperty (audioUnit, kAudioUnitProperty_SampleRate, scope, static_cast<UInt32> (i), &sr, sizeof (sr));
                        }
                    }

                    if (isInput)
                    {
                        AURenderCallbackStruct info;
                        zerostruct (info); // (can't use "= { 0 }" on this object because it's typedef'ed as a C struct)

                        info.inputProcRefCon = this;
                        info.inputProc = renderGetInputCallback;

                        AudioUnitSetProperty (audioUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input,
                                              static_cast<UInt32> (i), &info, sizeof (info));
                    }
                    else
                    {
                        outputBufferList.add (new AUBuffer (static_cast<size_t> (getChannelCountOfBus (false, i))));
                    }
                }
            }

            UInt32 frameSize = (UInt32) estimatedSamplesPerBlock;
            AudioUnitSetProperty (audioUnit, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, 0,
                                  &frameSize, sizeof (frameSize));

            setRateAndBufferSizeDetails ((double) newSampleRate, estimatedSamplesPerBlock);

            updateLatency();

            zerostruct (timeStamp);
            timeStamp.mSampleTime = 0;
            timeStamp.mHostTime = GetCurrentHostTime (0, newSampleRate, isAUv3);
            timeStamp.mFlags = kAudioTimeStampSampleTimeValid | kAudioTimeStampHostTimeValid;

            currentBuffer = nullptr;
            wasPlaying = false;

            resetBuses();

            bool ignore;

            if (! syncBusLayouts (getBusesLayout(), false, ignore))
                return;

            prepared = (AudioUnitInitialize (audioUnit) == noErr);

            if (prepared)
            {
                if (! haveParameterList)
                    refreshParameterList();

                // MARK: MPC this is in the WRONG PLACE (begin)
                // See https://forum.juce.com/t/bug-juce-audiounitpluginformat-mm/47638
                //setPluginCallbacks();
                // MARK: MPC this is in the WRONG PLACE (end)


                if (! syncBusLayouts (getBusesLayout(), true, ignore))
                {
                    prepared = false;
                    AudioUnitUninitialize (audioUnit);
                }
            }
        }
    }

Thanks Pete, this change is on develop now:

2 Likes

Hi @ed95 great stuff, thanks for letting me know! :+1:

Best wishes, Pete

1 Like