/* ============================================================================== This file is part of the JUCE library. Copyright (c) 2015 - ROLI Ltd. Permission is granted to use this software under the terms of either: a) the GPL v2 (or any later version) b) the Affero GPL v3 Details of these licenses can be found at: www.gnu.org/licenses JUCE is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. ------------------------------------------------------------------------------ To release a closed-source product which uses JUCE, commercial licenses are available: visit www.juce.com for more information. ============================================================================== */ struct PluginBusUtilities { //============================================================================== typedef Array AudioBusArray; //============================================================================== PluginBusUtilities (AudioProcessor& plugin, bool markDiscreteLayoutsAsSupported, int maxProbeChannels = kDefaultMaxChannels) : processor (plugin), dynamicInBuses (false), dynamicOutBuses (false), plugInFormatSupportsDiscreteLayouts (markDiscreteLayoutsAsSupported), maxChannelsToProbe (maxProbeChannels) { } // this will invoke setPreferredLayout many times void init() { populateLayoutDetails(); } //============================================================================== // Useful short-cuts AudioBusArray& getFilterBus (bool inputBus) noexcept { return inputBus ? processor.busArrangement.inputBuses : processor.busArrangement.outputBuses; } const AudioBusArray& getFilterBus (bool inputBus) const noexcept { return inputBus ? processor.busArrangement.inputBuses : processor.busArrangement.outputBuses; } int getBusCount (bool inputBus) const noexcept { return getFilterBus (inputBus).size(); } AudioChannelSet getChannelSet (bool inp, int bus) noexcept { return isPositiveAndBelow (bus, getBusCount (inp)) ? getFilterBus (inp).getReference (bus).channels : AudioChannelSet(); } int getNumChannels (bool inp, int bus) const noexcept { return isPositiveAndBelow (bus, getBusCount (inp)) ? getFilterBus (inp).getReference (bus).channels.size() : 0; } bool isBusEnabled (bool inputBus, int bus) const noexcept { return (getNumChannels (inputBus, bus) > 0); } bool hasInputs (int bus) const noexcept { return isBusEnabled (true, bus); } bool hasOutputs (int bus) const noexcept { return isBusEnabled (false, bus); } bool hasDynamicInBuses() const noexcept { return dynamicInBuses; } bool hasDynamicOutBuses() const noexcept { return dynamicOutBuses; } //============================================================================== // Channel Counters int getNumEnabledBuses (bool inputBus) const noexcept { int i; for (i = 0; i < getBusCount (inputBus); ++i) if (! isBusEnabled (inputBus, i)) break; return i; } int findTotalNumChannels (bool isInput, int busOffset = 0) const noexcept { int total = 0; const AudioBusArray& ioBuses = getFilterBus (isInput); for (int i = busOffset; i < ioBuses.size(); ++i) total += ioBuses.getReference (i).channels.size(); return total; } int getBusIdxForChannelIdx (bool isInput, int channelIdx, int& totalChannels, int startBusIdx) { const int numBuses = getBusCount (isInput); for (int busIdx = startBusIdx; busIdx < numBuses; ++busIdx) { const int numChannels = getNumChannels (isInput, busIdx); if ((totalChannels + numChannels) > channelIdx) return busIdx; totalChannels += numChannels; } return -1; } int getBusIdxForChannelIdx (bool isInput, int channelIdx) { int totalChannels = 0; return getBusIdxForChannelIdx (isInput, channelIdx, totalChannels, 0); } //============================================================================== // Bus properties & defaults bool busIgnoresLayout (bool inp, int bus) const noexcept { return isPositiveAndBelow (bus, getLayoutDetails (inp).size()) ? getBusLayoutDetails (inp, bus).busIgnoresLayout : true; } bool busCanBeDisabled (bool inp, int bus) const noexcept { return isPositiveAndBelow (bus, getLayoutDetails (inp).size()) ? getBusLayoutDetails (inp, bus).canBeDisabled : false; } bool isBusEnabledByDefault (bool inp, int bus) const noexcept { return isPositiveAndBelow (bus, getLayoutDetails (inp).size()) ? getBusLayoutDetails (inp, bus).isEnabledByDefault : true; } bool checkBusFormatsAreNotDiscrete() const { return (checkBusFormatsAreNotDiscrete (true) && checkBusFormatsAreNotDiscrete (false)); } const AudioChannelSet& getDefaultLayoutForBus (bool isInput, int busIdx) const noexcept { return getBusLayoutDetails (isInput, busIdx).defaultLayout; } AudioChannelSet getDefaultLayoutForChannelNumAndBus (bool isInput, int busIdx, int channelNum) const noexcept { if (busIdx < 0 || busIdx >= getBusCount (isInput) || channelNum == 0) return AudioChannelSet::disabled(); const BusLayoutDetails& layouts = getBusLayoutDetails (isInput, busIdx); const AudioChannelSet& dflt = layouts.defaultLayout; const AudioChannelSet discreteChannels = AudioChannelSet::discreteChannels (channelNum); if (dflt.size() == channelNum && (plugInFormatSupportsDiscreteLayouts || (! dflt.isDiscreteLayout()))) return dflt; Array potentialLayouts = layoutListCompatibleWithChannelCount (channelNum); ScopedBusRestorer busRestorer (*this); // prefer non-discrete layouts if no explicit default layout is given const int n = potentialLayouts.size(); for (int i = 0; i < n; ++i) { const AudioChannelSet& layout = potentialLayouts.getReference (i); if (processor.setPreferredBusArrangement (isInput, busIdx, layout)) return layout; } if (plugInFormatSupportsDiscreteLayouts && processor.setPreferredBusArrangement (isInput, busIdx, discreteChannels)) return discreteChannels; // we are out of options, bail out return AudioChannelSet(); } //============================================================================== // This function is quite heavy so please cache the return value int findMaxNumberOfChannelsForBus (bool isInput, int busNr, int upperlimit = std::numeric_limits::max()) { int maxChannelsPreprocessorDefs = -1; #ifdef JucePlugin_MaxNumInputChannels if (isInput) maxChannelsPreprocessorDefs = jmin (upperlimit, JucePlugin_MaxNumInputChannels); #endif #ifdef JucePlugin_MaxNumOutputChannels if (! isInput) maxChannelsPreprocessorDefs = jmin (upperlimit, JucePlugin_MaxNumOutputChannels); #endif #ifdef JucePlugin_PreferredChannelConfigurations if (busNr == 0) { int maxChannelCount = 0; const short channelConfigs[][2] = { JucePlugin_PreferredChannelConfigurations }; const int numChannelConfigs = sizeof (channelConfigs) / sizeof (*channelConfigs); for (int i = 0; i < numChannelConfigs; ++i) { const int numChannels = channelConfigs [i][isInput ? 0 : 1]; if (numChannels < 0) return -1; maxChannelCount = jmax (maxChannelCount, numChannels); } return jmin (upperlimit, maxChannelCount); } #endif ScopedBusRestorer busRestorer (*this); if (plugInFormatSupportsDiscreteLayouts && processor.setPreferredBusArrangement(isInput, busNr, AudioChannelSet::discreteChannels (insaneNumberOfChannels))) return -1; // no limit in channels int n = maxChannelsPreprocessorDefs > 0 ? maxChannelsPreprocessorDefs : (plugInFormatSupportsDiscreteLayouts ? maxChannelsToProbe : maxNumChannelsOfNonDiscreteLayouts); n = jmin (upperlimit, n); for (int i = n; i > 0; --i) { if (plugInFormatSupportsDiscreteLayouts && processor.setPreferredBusArrangement (isInput, busNr, AudioChannelSet::discreteChannels (i))) return i; Array sets = layoutListCompatibleWithChannelCount (i); for (int j = 0; j < sets.size(); ++j) { const AudioChannelSet& layout = sets.getReference (j); if (processor.setPreferredBusArrangement (isInput, busNr, layout)) return i; } } return 0; } //============================================================================== void restoreBusArrangement (const AudioProcessor::AudioBusArrangement& original) const { const int numInputBuses = getBusCount (true); const int numOutputBuses = getBusCount (false); jassert (original.inputBuses. size() == numInputBuses); jassert (original.outputBuses.size() == numOutputBuses); for (int busNr = 0; busNr < numInputBuses; ++busNr) processor.setPreferredBusArrangement (true, busNr, original.inputBuses.getReference (busNr).channels); for (int busNr = 0; busNr < numOutputBuses; ++busNr) processor.setPreferredBusArrangement (false, busNr, original.outputBuses.getReference (busNr).channels); } void enableAllBuses() { for (int busIdx = 1; busIdx < getBusCount (true); ++busIdx) if (getChannelSet (true, busIdx) == AudioChannelSet::disabled()) processor.setPreferredBusArrangement (true, busIdx, getDefaultLayoutForBus (true, busIdx)); for (int busIdx = 1; busIdx < getBusCount (false); ++busIdx) if (getChannelSet (false, busIdx) == AudioChannelSet::disabled()) processor.setPreferredBusArrangement (false, busIdx, getDefaultLayoutForBus (false, busIdx)); } //============================================================================== // Helper class which restores the original arrangement when it leaves scope class ScopedBusRestorer { public: ScopedBusRestorer (const PluginBusUtilities& bUtils) : busUtils (bUtils), originalArr (bUtils.processor.busArrangement), shouldRestore (true) {} ~ScopedBusRestorer() { if (shouldRestore) busUtils.restoreBusArrangement (originalArr); } void release() noexcept { shouldRestore = false; } private: const PluginBusUtilities& busUtils; const AudioProcessor::AudioBusArrangement originalArr; bool shouldRestore; JUCE_DECLARE_NON_COPYABLE (ScopedBusRestorer) }; //============================================================================== AudioProcessor& processor; enum { kDefaultMaxChannels = 64 }; private: friend class ScopedBusRestorer; enum { maxNumChannelsOfNonDiscreteLayouts = 8, // surround 7.1 has the maximum amount of channels pseudoChannelBitNum = 90, // use this bit index to check if plug-in really doesn't care about layouts insaneNumberOfChannels = 512 }; //============================================================================== // the first layout is the default layout struct BusLayoutDetails { BusLayoutDetails() : busIgnoresLayout (true), canBeDisabled (false), isEnabledByDefault (false) {} AudioChannelSet defaultLayout; bool busIgnoresLayout, canBeDisabled, isEnabledByDefault; }; Array& getLayoutDetails (bool isInput) noexcept { return isInput ? inputLayouts : outputLayouts; } const Array& getLayoutDetails (bool isInput) const noexcept { return isInput ? inputLayouts : outputLayouts; } BusLayoutDetails& getBusLayoutDetails (bool isInput, int busNr) noexcept { return getLayoutDetails (isInput).getReference (busNr); } const BusLayoutDetails& getBusLayoutDetails (bool isInput, int busNr) const noexcept { return getLayoutDetails (isInput).getReference (busNr); } //============================================================================== Array inputLayouts, outputLayouts; bool dynamicInBuses, dynamicOutBuses, plugInFormatSupportsDiscreteLayouts; int maxChannelsToProbe; //============================================================================== void populateLayoutDetails() { clear (getBusCount (true), getBusCount (false)); // save the default layouts for (int i = 0; i < getBusCount (true); ++i) getBusLayoutDetails (true, i).defaultLayout = getChannelSet (true, i); for (int i = 0; i < getBusCount (false); ++i) getBusLayoutDetails (false, i).defaultLayout = getChannelSet (false, i); { ScopedBusRestorer restorer (*this); for (int i = 0; i < getBusCount (true); ++i) addLayoutDetails (true, i); for (int i = 0; i < getBusCount (false); ++i) addLayoutDetails (false, i); // find the defaults for (int i = 0; i < getBusCount (true); ++i) updateDefaultLayout (true, i); for (int i = 0; i < getBusCount (false); ++i) updateDefaultLayout (false, i); } // can any of the buses be disabled/enabled dynamicInBuses = doesPlugInHaveDynamicBuses (true); dynamicOutBuses = doesPlugInHaveDynamicBuses (false); } //============================================================================== bool busIgnoresLayoutForChannelNum (bool isInput, int busNr, int channelNum) { AudioChannelSet set; // If the plug-in does not complain about setting it's layout to an undefined layout // then we assume that the plug-in ignores the layout altogether for (int i = 0; i < channelNum; ++i) set.addChannel (static_cast (pseudoChannelBitNum + i)); return processor.setPreferredBusArrangement (isInput, busNr, set); } void addLayoutDetails (bool isInput, int busNr) { BusLayoutDetails& layouts = getBusLayoutDetails (isInput, busNr); // check if the plug-in bus can be disabled layouts.canBeDisabled = processor.setPreferredBusArrangement (isInput, busNr, AudioChannelSet()); layouts.busIgnoresLayout = true; for (int i = 1; i <= maxNumChannelsOfNonDiscreteLayouts; ++i) { const bool ignoresLayoutForChannel = busIgnoresLayoutForChannelNum (isInput, busNr, i); Array sets = layoutListCompatibleWithChannelCount (i); for (int j = 0; j < sets.size(); ++j) { const AudioChannelSet& layout = sets.getReference (j); if (processor.setPreferredBusArrangement (isInput, busNr, layout)) { if (! ignoresLayoutForChannel) { layouts.busIgnoresLayout = false; return; } } } } } bool doesPlugInHaveDynamicBuses (bool isInput) const { for (int i = 0; i < getBusCount (isInput); ++i) if (getBusLayoutDetails (isInput, i).canBeDisabled) return true; return false; } bool checkBusFormatsAreNotDiscrete (bool isInput) const { const int n = getBusCount (isInput); const Array& bus = isInput ? processor.busArrangement.inputBuses : processor.busArrangement.outputBuses; for (int busIdx = 0; busIdx < n; ++busIdx) if (bus.getReference (busIdx).channels.isDiscreteLayout()) return false; return true; } void updateDefaultLayout (bool isInput, int busIdx) { BusLayoutDetails& layouts = getBusLayoutDetails (isInput, busIdx); AudioChannelSet& dfltLayout = layouts.defaultLayout; layouts.isEnabledByDefault = (dfltLayout.size() > 0); // If you hit this assertion then you are disabling the main bus by default // which is unsupported jassert (layouts.isEnabledByDefault || busIdx >= 0); if ((! plugInFormatSupportsDiscreteLayouts) && dfltLayout.isDiscreteLayout()) { // The default layout is a discrete channel layout, yet some plug-in formats (VST-3) // do not support this format. We need to find a different default with the same // number of channels dfltLayout = getDefaultLayoutForChannelNumAndBus (isInput, busIdx, dfltLayout.size()); } // are we done? if (dfltLayout != AudioChannelSet()) return; const bool mainBusHasInputs = hasInputs (0); const bool mainBusHasOutputs = hasOutputs (0); if (busIdx != 0 && (mainBusHasInputs || mainBusHasOutputs)) { // the AudioProcessor does not give us any default layout // for an aux bus. Use the same number of channels as the // default layout on the main bus as a sensible default for // the aux bus const bool useInput = mainBusHasInputs && mainBusHasOutputs ? isInput : mainBusHasInputs; dfltLayout = getBusLayoutDetails (useInput, 0).defaultLayout; const int numChannels = dfltLayout.size(); const AudioChannelSet discreteChannelLayout = AudioChannelSet::discreteChannels (numChannels); if ((plugInFormatSupportsDiscreteLayouts || dfltLayout != discreteChannelLayout) && processor.setPreferredBusArrangement (isInput, busIdx, dfltLayout)) return; // no exact match: try at least to match the number of channels dfltLayout = getDefaultLayoutForChannelNumAndBus (isInput, busIdx, dfltLayout.size()); if (dfltLayout != AudioChannelSet()) return; } // check stereo first as this is often the more sensible default than mono if (processor.setPreferredBusArrangement (isInput, busIdx, (dfltLayout = AudioChannelSet::stereo()))) return; if (plugInFormatSupportsDiscreteLayouts && processor.setPreferredBusArrangement (isInput, busIdx, (dfltLayout = AudioChannelSet::discreteChannels (2)))) return; // let's guess for (int numChans = 1; numChans < findMaxNumberOfChannelsForBus (isInput, busIdx); ++numChans) { Array sets = layoutListCompatibleWithChannelCount (numChans); for (int j = 0; j < sets.size(); ++j) if (processor.setPreferredBusArrangement (isInput, busIdx, (dfltLayout = sets.getReference (j)))) return; if (plugInFormatSupportsDiscreteLayouts && processor.setPreferredBusArrangement (isInput, busIdx, (dfltLayout = AudioChannelSet::discreteChannels (numChans)))) return; } // Your bus must support at least a single possible layout jassertfalse; } void clear (int inputCount, int outputCount) { inputLayouts.clear(); inputLayouts.resize (inputCount); outputLayouts.clear(); outputLayouts.resize (outputCount); } //============================================================================== static Array layoutListCompatibleWithChannelCount (const int channelCount) noexcept { jassert (channelCount > 0); Array sets; switch (channelCount) { case 1: sets.add (AudioChannelSet::mono()); break; case 2: sets.add (AudioChannelSet::stereo()); break; case 3: sets.add (AudioChannelSet::createLCR()); sets.add (AudioChannelSet::createLRS()); break; case 4: sets.add (AudioChannelSet::createLCRS()); sets.add (AudioChannelSet::quadraphonic()); sets.add (AudioChannelSet::ambisonic()); break; case 5: sets.add (AudioChannelSet::pentagonal()); sets.add (AudioChannelSet::create5point0()); break; case 6: sets.add (AudioChannelSet::hexagonal()); sets.add (AudioChannelSet::create5point1()); sets.add (AudioChannelSet::create6point0()); sets.add (AudioChannelSet::create6point0Music()); break; case 7: sets.add (AudioChannelSet::create6point1()); sets.add (AudioChannelSet::create7point0()); break; case 8: sets.add (AudioChannelSet::octagonal()); sets.add (AudioChannelSet::create7point1()); sets.add (AudioChannelSet::create7point1AC3()); sets.add (AudioChannelSet::createFront7point1()); break; } return sets; } JUCE_DECLARE_NON_COPYABLE (PluginBusUtilities) };