|
- /*
- ==============================================================================
-
- 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<AudioProcessor::AudioProcessorBus> 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<AudioChannelSet> 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<int>::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<AudioChannelSet> 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<BusLayoutDetails>& getLayoutDetails (bool isInput) noexcept { return isInput ? inputLayouts : outputLayouts; }
- const Array<BusLayoutDetails>& 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<BusLayoutDetails> 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<AudioChannelSet::ChannelType> (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<AudioChannelSet> 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<AudioProcessor::AudioProcessorBus>& 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<AudioChannelSet> 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<AudioChannelSet> layoutListCompatibleWithChannelCount (const int channelCount) noexcept
- {
- jassert (channelCount > 0);
-
- Array<AudioChannelSet> 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)
- };
|