mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-11 23:54:18 +00:00
536 lines
22 KiB
C++
536 lines
22 KiB
C++
/*
|
|
==============================================================================
|
|
|
|
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 inputBus, int bus) noexcept { return getFilterBus (inputBus).getReference (bus).channels; }
|
|
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)
|
|
};
|