From cafb437762a62af53f3f1d2deb66480bb012deb7 Mon Sep 17 00:00:00 2001 From: reuk Date: Thu, 15 Apr 2021 19:10:47 +0100 Subject: [PATCH] AU Wrapper: Use correct sample rate for MIDI FX with no buses MIDI FX AudioUnits can retrieve their current sample rate from an output bus. However, it's reasonable for a JUCE MIDI FX plugin to require 0 output buses, in which case the correct sample rate cannot be retrieved. This patch modifies the AU wrapper to ensure that MIDI FX plugins will always have at least one output bus. This bus does not necessarily correspond to a matching bus in the AudioProcessor, and it will not be passed to AudioProcessor::processBlock. With the output bus in place, the correct sample rate is reported in `prepareToPlay`. --- .../AU/juce_AU_Wrapper.mm | 172 +++++++++++------- .../AU/juce_AUv3_Wrapper.mm | 8 +- .../format_types/juce_AU_Shared.h | 23 ++- 3 files changed, 126 insertions(+), 77 deletions(-) diff --git a/modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm b/modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm index a329eb6ed6..0a8b130474 100644 --- a/modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm +++ b/modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm @@ -131,8 +131,8 @@ public: JuceAU (AudioUnit component) : AudioProcessorHolder (activePlugins.size() + activeUIs.size() == 0), MusicDeviceBase (component, - (UInt32) AudioUnitHelpers::getBusCount (juceFilter.get(), true), - (UInt32) AudioUnitHelpers::getBusCount (juceFilter.get(), false)), + (UInt32) AudioUnitHelpers::getBusCountForWrapper (*juceFilter, true), + (UInt32) AudioUnitHelpers::getBusCountForWrapper (*juceFilter, false)), mapper (*juceFilter) { inParameterChangedCallback = false; @@ -210,7 +210,7 @@ public: return err; mapper.alloc(); - pulledSucceeded.calloc (static_cast (AudioUnitHelpers::getBusCount (juceFilter.get(), true))); + pulledSucceeded.calloc (static_cast (AudioUnitHelpers::getBusCountForWrapper (*juceFilter, true))); prepareToPlay(); @@ -300,7 +300,7 @@ public: if (isInput) return false; #endif - const int busCount = AudioUnitHelpers::getBusCount (juceFilter.get(), isInput); + const int busCount = AudioUnitHelpers::getBusCount (*juceFilter, isInput); return (juceFilter->canAddBus (isInput) || (busCount > 0 && juceFilter->canRemoveBus (isInput))); #endif } @@ -313,12 +313,12 @@ public: if ((err = scopeToDirection (scope, isInput)) != noErr) return err; - if (count != (UInt32) AudioUnitHelpers::getBusCount (juceFilter.get(), isInput)) + if (count != (UInt32) AudioUnitHelpers::getBusCount (*juceFilter, isInput)) { #ifdef JucePlugin_PreferredChannelConfigurations return kAudioUnitErr_PropertyNotWritable; #else - const int busCount = AudioUnitHelpers::getBusCount (juceFilter.get(), isInput); + const int busCount = AudioUnitHelpers::getBusCount (*juceFilter, isInput); if ((! juceFilter->canAddBus (isInput)) && ((busCount == 0) || (! juceFilter->canRemoveBus (isInput)))) return kAudioUnitErr_PropertyNotWritable; @@ -362,7 +362,7 @@ public: if (err != noErr) { // restore bus state - const int newBusCount = AudioUnitHelpers::getBusCount (juceFilter.get(), isInput); + const int newBusCount = AudioUnitHelpers::getBusCount (*juceFilter, isInput); for (int i = newBusCount; i != busCount; i += (busCount > newBusCount ? 1 : -1)) { if (busCount > newBusCount) @@ -798,15 +798,14 @@ public: UInt32 GetAudioChannelLayout (AudioUnitScope scope, AudioUnitElement element, AudioChannelLayout* outLayoutPtr, Boolean& outWritable) override { - bool isInput; - int busNr; - outWritable = false; - if (elementToBusIdx (scope, element, isInput, busNr) != noErr) + const auto info = getElementInfo (scope, element); + + if (info.error != noErr) return 0; - if (busIgnoresLayout (isInput, busNr)) + if (busIgnoresLayout (info.isInput, info.busNr)) return 0; outWritable = true; @@ -816,7 +815,7 @@ public: if (outLayoutPtr != nullptr) { zeromem (outLayoutPtr, sizeInBytes); - outLayoutPtr->mChannelLayoutTag = getCurrentLayout (isInput, busNr); + outLayoutPtr->mChannelLayoutTag = getCurrentLayout (info.isInput, info.busNr); } return sizeInBytes; @@ -824,16 +823,15 @@ public: UInt32 GetChannelLayoutTags (AudioUnitScope scope, AudioUnitElement element, AudioChannelLayoutTag* outLayoutTags) override { - bool isInput; - int busNr; + const auto info = getElementInfo (scope, element); - if (elementToBusIdx (scope, element, isInput, busNr) != noErr) + if (info.error != noErr) return 0; - if (busIgnoresLayout (isInput, busNr)) + if (busIgnoresLayout (info.isInput, info.busNr)) return 0; - const Array& layouts = getSupportedBusLayouts (isInput, busNr); + const Array& layouts = getSupportedBusLayouts (info.isInput, info.busNr); if (outLayoutTags != nullptr) std::copy (layouts.begin(), layouts.end(), outLayoutTags); @@ -843,20 +841,18 @@ public: OSStatus SetAudioChannelLayout (AudioUnitScope scope, AudioUnitElement element, const AudioChannelLayout* inLayout) override { - bool isInput; - int busNr; - OSStatus err; + const auto info = getElementInfo (scope, element); - if ((err = elementToBusIdx (scope, element, isInput, busNr)) != noErr) - return err; + if (info.error != noErr) + return info.error; - if (busIgnoresLayout (isInput, busNr)) + if (busIgnoresLayout (info.isInput, info.busNr)) return kAudioUnitErr_PropertyNotWritable; if (inLayout == nullptr) return kAudioUnitErr_InvalidPropertyValue; - if (const AUIOElement* ioElement = GetIOElement (isInput ? kAudioUnitScope_Input : kAudioUnitScope_Output, element)) + if (const AUIOElement* ioElement = GetIOElement (info.isInput ? kAudioUnitScope_Input : kAudioUnitScope_Output, element)) { const AudioChannelSet newChannelSet = CoreAudioLayouts::fromCoreAudio (*inLayout); const int currentNumChannels = static_cast (ioElement->GetStreamFormat().NumberChannels()); @@ -872,11 +868,11 @@ public: if (! AudioUnitHelpers::isLayoutSupported (*juceFilter, isInput, busNr, newChannelNum, configs)) return kAudioUnitErr_FormatNotSupported; #else - if (! juceFilter->getBus (isInput, busNr)->isLayoutSupported (newChannelSet)) + if (! juceFilter->getBus (info.isInput, info.busNr)->isLayoutSupported (newChannelSet)) return kAudioUnitErr_FormatNotSupported; #endif - getCurrentLayout (isInput, busNr) = CoreAudioLayouts::toCoreAudio (newChannelSet); + getCurrentLayout (info.isInput, info.busNr) = CoreAudioLayouts::toCoreAudio (newChannelSet); return noErr; } @@ -1051,7 +1047,14 @@ public: ComponentResult Version() override { return JucePlugin_VersionCode; } bool SupportsTail() override { return true; } Float64 GetTailTime() override { return juceFilter->getTailLengthSeconds(); } - double getSampleRate() { return AudioUnitHelpers::getBusCount (juceFilter.get(), false) > 0 ? GetOutput (0)->GetStreamFormat().mSampleRate : 44100.0; } + + double getSampleRate() + { + if (AudioUnitHelpers::getBusCountForWrapper (*juceFilter, false) > 0) + return GetOutput (0)->GetStreamFormat().mSampleRate; + + return 44100.0; + } Float64 GetLatency() override { @@ -1206,32 +1209,33 @@ public: //============================================================================== bool StreamFormatWritable (AudioUnitScope scope, AudioUnitElement element) override { - bool ignore; - int busIdx; + const auto info = getElementInfo (scope, element); - return ((! IsInitialized()) && (elementToBusIdx (scope, element, ignore, busIdx) == noErr)); + return ((! IsInitialized()) && (info.error == noErr)); } bool ValidFormat (AudioUnitScope scope, AudioUnitElement element, const CAStreamBasicDescription& format) override { - bool isInput; - int busNr; - // DSP Quattro incorrectly uses global scope for the ValidFormat call if (scope == kAudioUnitScope_Global) return ValidFormat (kAudioUnitScope_Input, element, format) || ValidFormat (kAudioUnitScope_Output, element, format); - if (elementToBusIdx (scope, element, isInput, busNr) != noErr) + const auto info = getElementInfo (scope, element); + + if (info.error != noErr) return false; + if (info.kind == BusKind::wrapperOnly) + return true; + const int newNumChannels = static_cast (format.NumberChannels()); - const int oldNumChannels = juceFilter->getChannelCountOfBus (isInput, busNr); + const int oldNumChannels = juceFilter->getChannelCountOfBus (info.isInput, info.busNr); if (newNumChannels == oldNumChannels) return true; - if (AudioProcessor::Bus* bus = juceFilter->getBus (isInput, busNr)) + if (AudioProcessor::Bus* bus = juceFilter->getBus (info.isInput, info.busNr)) { if (! MusicDeviceBase::ValidFormat (scope, element, format)) return false; @@ -1252,17 +1256,15 @@ public: // AU requires us to override this for the sole reason that we need to find a default layout tag if the number of channels have changed OSStatus ChangeStreamFormat (AudioUnitScope scope, AudioUnitElement element, const CAStreamBasicDescription& old, const CAStreamBasicDescription& format) override { - bool isInput; - int busNr; - OSStatus err = elementToBusIdx (scope, element, isInput, busNr); + const auto info = getElementInfo (scope, element); - if (err != noErr) - return err; + if (info.error != noErr) + return info.error; - AudioChannelLayoutTag& currentTag = getCurrentLayout (isInput, busNr); + AudioChannelLayoutTag& currentTag = getCurrentLayout (info.isInput, info.busNr); const int newNumChannels = static_cast (format.NumberChannels()); - const int oldNumChannels = juceFilter->getChannelCountOfBus (isInput, busNr); + const int oldNumChannels = juceFilter->getChannelCountOfBus (info.isInput, info.busNr); #ifdef JucePlugin_PreferredChannelConfigurations short configs[][2] = {JucePlugin_PreferredChannelConfigurations}; @@ -1272,13 +1274,21 @@ public: #endif // predict channel layout - AudioChannelSet set = (newNumChannels != oldNumChannels) ? juceFilter->getBus (isInput, busNr)->supportedLayoutWithChannels (newNumChannels) - : juceFilter->getChannelLayoutOfBus (isInput, busNr); + const auto set = [&] + { + if (info.kind == BusKind::wrapperOnly) + return AudioChannelSet::discreteChannels (newNumChannels); + + if (newNumChannels != oldNumChannels) + return juceFilter->getBus (info.isInput, info.busNr)->supportedLayoutWithChannels (newNumChannels); + + return juceFilter->getChannelLayoutOfBus (info.isInput, info.busNr); + }(); if (set == AudioChannelSet()) return kAudioUnitErr_FormatNotSupported; - err = MusicDeviceBase::ChangeStreamFormat (scope, element, old, format); + const auto err = MusicDeviceBase::ChangeStreamFormat (scope, element, old, format); if (err == noErr) currentTag = CoreAudioLayouts::toCoreAudio (set); @@ -1302,8 +1312,8 @@ public: ioActionFlags &= ~kAudioUnitRenderAction_OutputIsSilence; - const int numInputBuses = static_cast (GetScope (kAudioUnitScope_Input) .GetNumberOfElements()); - const int numOutputBuses = static_cast (GetScope (kAudioUnitScope_Output).GetNumberOfElements()); + const int numInputBuses = AudioUnitHelpers::getBusCount (*juceFilter, true); + const int numOutputBuses = AudioUnitHelpers::getBusCount (*juceFilter, false); // set buffer pointers to minimize copying { @@ -1839,14 +1849,18 @@ private: void prepareOutputBuffers (const UInt32 nFrames) noexcept { - const unsigned int numOutputBuses = GetScope (kAudioUnitScope_Output).GetNumberOfElements(); + const auto numProcessorBuses = AudioUnitHelpers::getBusCount (*juceFilter, false); + const auto numWrapperBuses = GetScope (kAudioUnitScope_Output).GetNumberOfElements(); - for (unsigned int busIdx = 0; busIdx < numOutputBuses; ++busIdx) + for (UInt32 busIdx = 0; busIdx < numWrapperBuses; ++busIdx) { AUOutputElement* output = GetOutput (busIdx); if (output->WillAllocateBuffer()) output->PrepareBuffer (nFrames); + + if (busIdx >= (UInt32) numProcessorBuses) + AudioUnitHelpers::clearAudioBuffer (output->GetBufferList()); } } @@ -1944,16 +1958,37 @@ private: ? (OSStatus) kAudioUnitErr_InvalidScope : (OSStatus) noErr; } - OSStatus elementToBusIdx (AudioUnitScope scope, AudioUnitElement element, bool& isInput, int& busIdx) noexcept + enum class BusKind { + processor, + wrapperOnly, + }; + + struct ElementInfo + { + int busNr; + BusKind kind; + bool isInput; + OSStatus error; + }; + + ElementInfo getElementInfo (AudioUnitScope scope, AudioUnitElement element) noexcept + { + bool isInput = false; OSStatus err; - busIdx = static_cast (element); + if ((err = scopeToDirection (scope, isInput)) != noErr) + return { {}, {}, {}, err }; - if ((err = scopeToDirection (scope, isInput)) != noErr) return err; - if (isPositiveAndBelow (busIdx, AudioUnitHelpers::getBusCount (juceFilter.get(), isInput))) return noErr; + const auto busIdx = static_cast (element); - return kAudioUnitErr_InvalidElement; + if (isPositiveAndBelow (busIdx, AudioUnitHelpers::getBusCount (*juceFilter, isInput))) + return { busIdx, BusKind::processor, isInput, noErr }; + + if (isPositiveAndBelow (busIdx, AudioUnitHelpers::getBusCountForWrapper (*juceFilter, isInput))) + return { busIdx, BusKind::wrapperOnly, isInput, noErr }; + + return { {}, {}, {}, kAudioUnitErr_InvalidElement }; } //============================================================================== @@ -2069,22 +2104,25 @@ private: OSStatus syncAudioUnitWithProcessor() { OSStatus err = noErr; - const int enabledInputs = AudioUnitHelpers::getBusCount (juceFilter.get(), true); - const int enabledOutputs = AudioUnitHelpers::getBusCount (juceFilter.get(), false); + const auto numWrapperInputs = AudioUnitHelpers::getBusCountForWrapper (*juceFilter, true); + const auto numWrapperOutputs = AudioUnitHelpers::getBusCountForWrapper (*juceFilter, false); - if ((err = MusicDeviceBase::SetBusCount (kAudioUnitScope_Input, static_cast (enabledInputs))) != noErr) + if ((err = MusicDeviceBase::SetBusCount (kAudioUnitScope_Input, static_cast (numWrapperInputs))) != noErr) return err; - if ((err = MusicDeviceBase::SetBusCount (kAudioUnitScope_Output, static_cast (enabledOutputs))) != noErr) + if ((err = MusicDeviceBase::SetBusCount (kAudioUnitScope_Output, static_cast (numWrapperOutputs))) != noErr) return err; addSupportedLayoutTags(); - for (int i = 0; i < enabledInputs; ++i) + const auto numProcessorInputs = AudioUnitHelpers::getBusCount (*juceFilter, true); + const auto numProcessorOutputs = AudioUnitHelpers::getBusCount (*juceFilter, false); + + for (int i = 0; i < numProcessorInputs; ++i) if ((err = syncAudioUnitWithChannelSet (true, i, juceFilter->getChannelLayoutOfBus (true, i))) != noErr) return err; - for (int i = 0; i < enabledOutputs; ++i) + for (int i = 0; i < numProcessorOutputs; ++i) if ((err = syncAudioUnitWithChannelSet (false, i, juceFilter->getChannelLayoutOfBus (false, i))) != noErr) return err; @@ -2093,8 +2131,8 @@ private: OSStatus syncProcessorWithAudioUnit() { - const int numInputBuses = AudioUnitHelpers::getBusCount (juceFilter.get(), true); - const int numOutputBuses = AudioUnitHelpers::getBusCount (juceFilter.get(), false); + const int numInputBuses = AudioUnitHelpers::getBusCount (*juceFilter, true); + const int numOutputBuses = AudioUnitHelpers::getBusCount (*juceFilter, false); const int numInputElements = static_cast (GetScope(kAudioUnitScope_Input). GetNumberOfElements()); const int numOutputElements = static_cast (GetScope(kAudioUnitScope_Output).GetNumberOfElements()); @@ -2229,7 +2267,7 @@ private: { auto& layouts = isInput ? supportedInputLayouts : supportedOutputLayouts; layouts.clear(); - auto numBuses = AudioUnitHelpers::getBusCount (juceFilter.get(), isInput); + auto numBuses = AudioUnitHelpers::getBusCount (*juceFilter, isInput); for (int busNr = 0; busNr < numBuses; ++busNr) { @@ -2244,8 +2282,8 @@ private: { currentInputLayout.clear(); currentOutputLayout.clear(); - currentInputLayout. resize (AudioUnitHelpers::getBusCount (juceFilter.get(), true)); - currentOutputLayout.resize (AudioUnitHelpers::getBusCount (juceFilter.get(), false)); + currentInputLayout. resize (AudioUnitHelpers::getBusCountForWrapper (*juceFilter, true)); + currentOutputLayout.resize (AudioUnitHelpers::getBusCountForWrapper (*juceFilter, false)); addSupportedLayoutTagsForDirection (true); addSupportedLayoutTagsForDirection (false); diff --git a/modules/juce_audio_plugin_client/AU/juce_AUv3_Wrapper.mm b/modules/juce_audio_plugin_client/AU/juce_AUv3_Wrapper.mm index 13627c743b..80a10fedba 100644 --- a/modules/juce_audio_plugin_client/AU/juce_AUv3_Wrapper.mm +++ b/modules/juce_audio_plugin_client/AU/juce_AUv3_Wrapper.mm @@ -782,7 +782,7 @@ public: for (int dir = 0; dir < 2; ++dir) { const bool isInput = (dir == 0); - const int n = AudioUnitHelpers::getBusCount (&processor, isInput); + const int n = AudioUnitHelpers::getBusCount (processor, isInput); Array& channelSets = (isInput ? layouts.inputBuses : layouts.outputBuses); AUAudioUnitBusArray* auBuses = (isInput ? [getAudioUnit() inputBusses] : [getAudioUnit() outputBusses]); @@ -839,7 +839,7 @@ public: audioBuffer.prepare (totalInChannels, totalOutChannels, static_cast (maxFrames)); - double sampleRate = (jmax (AudioUnitHelpers::getBusCount (&processor, true), AudioUnitHelpers::getBusCount (&processor, false)) > 0 ? + double sampleRate = (jmax (AudioUnitHelpers::getBusCount (processor, true), AudioUnitHelpers::getBusCount (processor, false)) > 0 ? [[[([inputBusses.get() count] > 0 ? inputBusses.get() : outputBusses.get()) objectAtIndexedSubscript: 0] format] sampleRate] : 44100.0); processor.setRateAndBufferSizeDetails (sampleRate, static_cast (maxFrames)); @@ -1125,7 +1125,7 @@ private: { std::unique_ptr, NSObjectDeleter> array ([[NSMutableArray alloc] init]); AudioProcessor& processor = getAudioProcessor(); - const int n = AudioUnitHelpers::getBusCount (&processor, isInput); + const int n = AudioUnitHelpers::getBusCount (processor, isInput); for (int i = 0; i < n; ++i) { @@ -1383,7 +1383,7 @@ private: OwnedArray& busBuffers = isInput ? inBusBuffers : outBusBuffers; busBuffers.clear(); - const int n = AudioUnitHelpers::getBusCount (&getAudioProcessor(), isInput); + const int n = AudioUnitHelpers::getBusCount (getAudioProcessor(), isInput); const AUAudioFrameCount maxFrames = [getAudioUnit() maximumFramesToRender]; for (int busIdx = 0; busIdx < n; ++busIdx) diff --git a/modules/juce_audio_processors/format_types/juce_AU_Shared.h b/modules/juce_audio_processors/format_types/juce_AU_Shared.h index c311fdc0cb..570677aff0 100644 --- a/modules/juce_audio_processors/format_types/juce_AU_Shared.h +++ b/modules/juce_audio_processors/format_types/juce_AU_Shared.h @@ -43,8 +43,8 @@ struct AudioUnitHelpers void alloc() { - const int numInputBuses = AudioUnitHelpers::getBusCount (&processor, true); - const int numOutputBuses = AudioUnitHelpers::getBusCount (&processor, false); + const int numInputBuses = AudioUnitHelpers::getBusCount (processor, true); + const int numOutputBuses = AudioUnitHelpers::getBusCount (processor, false); initializeChannelMapArray (true, numInputBuses); initializeChannelMapArray (false, numOutputBuses); @@ -334,8 +334,8 @@ struct AudioUnitHelpers { Array channelInfo; - auto hasMainInputBus = (AudioUnitHelpers::getBusCount (&processor, true) > 0); - auto hasMainOutputBus = (AudioUnitHelpers::getBusCount (&processor, false) > 0); + auto hasMainInputBus = (AudioUnitHelpers::getBusCountForWrapper (processor, true) > 0); + auto hasMainOutputBus = (AudioUnitHelpers::getBusCountForWrapper (processor, false) > 0); if ((! hasMainInputBus) && (! hasMainOutputBus)) { @@ -471,9 +471,9 @@ struct AudioUnitHelpers } //============================================================================== - static int getBusCount (const AudioProcessor* juceFilter, bool isInput) + static int getBusCount (const AudioProcessor& juceFilter, bool isInput) { - int busCount = juceFilter->getBusCount (isInput); + int busCount = juceFilter.getBusCount (isInput); #ifdef JucePlugin_PreferredChannelConfigurations short configs[][2] = {JucePlugin_PreferredChannelConfigurations}; @@ -491,6 +491,17 @@ struct AudioUnitHelpers return busCount; } + static int getBusCountForWrapper (const AudioProcessor& juceFilter, bool isInput) + { + #if JucePlugin_IsMidiEffect + const auto numRequiredBuses = isInput ? 0 : 1; + #else + const auto numRequiredBuses = 0; + #endif + + return jmax (numRequiredBuses, getBusCount (juceFilter, isInput)); + } + static bool setBusesLayout (AudioProcessor* juceFilter, const AudioProcessor::BusesLayout& requestedLayouts) { #ifdef JucePlugin_PreferredChannelConfigurations