mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
VST3: Avoid requesting channel layouts that cannot be represented as SpeakerArrangements
This commit is contained in:
parent
6bd31bab35
commit
4b222427f9
4 changed files with 155 additions and 85 deletions
|
|
@ -3051,7 +3051,9 @@ public:
|
|||
info.mediaType = Vst::kAudio;
|
||||
info.direction = dir;
|
||||
info.channelCount = bus->getLastEnabledLayout().size();
|
||||
jassert (info.channelCount == Steinberg::Vst::SpeakerArr::getChannelCount (getVst3SpeakerArrangement (bus->getLastEnabledLayout())));
|
||||
|
||||
[[maybe_unused]] const auto lastEnabledVst3Layout = getVst3SpeakerArrangement (bus->getLastEnabledLayout());
|
||||
jassert (lastEnabledVst3Layout.has_value() && info.channelCount == Steinberg::Vst::SpeakerArr::getChannelCount (*lastEnabledVst3Layout));
|
||||
toString128 (info.name, bus->getName());
|
||||
|
||||
info.busType = [&]
|
||||
|
|
@ -3288,19 +3290,42 @@ public:
|
|||
// see the following documentation to understand the correct way to react to this callback
|
||||
// https://steinbergmedia.github.io/vst3_doc/vstinterfaces/classSteinberg_1_1Vst_1_1IAudioProcessor.html#ad3bc7bac3fd3b194122669be2a1ecc42
|
||||
|
||||
const auto requestedLayout = [&]
|
||||
const auto toLayoutsArray = [] (auto begin, auto end) -> std::optional<Array<AudioChannelSet>>
|
||||
{
|
||||
auto result = pluginInstance->getBusesLayout();
|
||||
Array<AudioChannelSet> result;
|
||||
|
||||
for (int i = 0; i < numIns; ++i)
|
||||
result.getChannelSet (true, i) = getChannelSetForSpeakerArrangement (inputs[i]);
|
||||
for (auto it = begin; it != end; ++it)
|
||||
{
|
||||
const auto set = getChannelSetForSpeakerArrangement (*it);
|
||||
|
||||
for (int i = 0; i < numOuts; ++i)
|
||||
result.getChannelSet (false, i) = getChannelSetForSpeakerArrangement (outputs[i]);
|
||||
if (! set.has_value())
|
||||
return {};
|
||||
|
||||
result.add (*set);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
const auto optionalRequestedLayout = [&]() -> std::optional<AudioProcessor::BusesLayout>
|
||||
{
|
||||
const auto ins = toLayoutsArray (inputs, inputs + numIns);
|
||||
const auto outs = toLayoutsArray (outputs, outputs + numOuts);
|
||||
|
||||
if (! ins.has_value() || ! outs.has_value())
|
||||
return {};
|
||||
|
||||
AudioProcessor::BusesLayout result;
|
||||
result.inputBuses = *ins;
|
||||
result.outputBuses = *outs;
|
||||
return result;
|
||||
}();
|
||||
|
||||
if (! optionalRequestedLayout.has_value())
|
||||
return kResultFalse;
|
||||
|
||||
const auto& requestedLayout = *optionalRequestedLayout;
|
||||
|
||||
#ifdef JucePlugin_PreferredChannelConfigurations
|
||||
short configs[][2] = { JucePlugin_PreferredChannelConfigurations };
|
||||
if (! AudioProcessor::containsLayout (requestedLayout, configs))
|
||||
|
|
@ -3339,10 +3364,16 @@ public:
|
|||
{
|
||||
if (auto* bus = pluginInstance->getBus (dir == Vst::kInput, index))
|
||||
{
|
||||
arr = getVst3SpeakerArrangement (bus->getLastEnabledLayout());
|
||||
if (const auto arrangement = getVst3SpeakerArrangement (bus->getLastEnabledLayout()))
|
||||
{
|
||||
arr = *arrangement;
|
||||
return kResultTrue;
|
||||
}
|
||||
|
||||
// There's a bus here, but we can't represent its layout in terms of VST3 speakers!
|
||||
jassertfalse;
|
||||
}
|
||||
|
||||
return kResultFalse;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -197,7 +197,7 @@ static inline Steinberg::Vst::SpeakerArrangement getArrangementForBus (Steinberg
|
|||
return arrangement;
|
||||
}
|
||||
|
||||
static Steinberg::Vst::Speaker getSpeakerType (const AudioChannelSet& set, AudioChannelSet::ChannelType type) noexcept
|
||||
static std::optional<Steinberg::Vst::Speaker> getSpeakerType (const AudioChannelSet& set, AudioChannelSet::ChannelType type) noexcept
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
|
|
@ -280,11 +280,10 @@ static Steinberg::Vst::Speaker getSpeakerType (const AudioChannelSet& set, Audio
|
|||
break;
|
||||
}
|
||||
|
||||
auto channelIndex = static_cast<Steinberg::Vst::Speaker> (type) - (static_cast<Steinberg::Vst::Speaker> (AudioChannelSet::discreteChannel0) + 6ull);
|
||||
return (1ull << (channelIndex + 33ull /* last speaker in vst layout + 1 */));
|
||||
return {};
|
||||
}
|
||||
|
||||
static AudioChannelSet::ChannelType getChannelType (Steinberg::Vst::SpeakerArrangement arr, Steinberg::Vst::Speaker type) noexcept
|
||||
static std::optional<AudioChannelSet::ChannelType> getChannelType (Steinberg::Vst::SpeakerArrangement arr, Steinberg::Vst::Speaker type) noexcept
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
|
|
@ -340,12 +339,7 @@ static AudioChannelSet::ChannelType getChannelType (Steinberg::Vst::SpeakerArran
|
|||
case Steinberg::Vst::kSpeakerBrr: return AudioChannelSet::bottomRearRight;
|
||||
}
|
||||
|
||||
auto channelType = BigInteger (static_cast<int64> (type)).findNextSetBit (0);
|
||||
|
||||
// VST3 <-> JUCE layout conversion error: report this bug to the JUCE forum
|
||||
jassert (channelType >= 33);
|
||||
|
||||
return static_cast<AudioChannelSet::ChannelType> (static_cast<int> (AudioChannelSet::discreteChannel0) + 6 + (channelType - 33));
|
||||
return {};
|
||||
}
|
||||
|
||||
namespace detail
|
||||
|
|
@ -423,7 +417,7 @@ inline bool isLayoutTableValid()
|
|||
});
|
||||
}
|
||||
|
||||
static Array<AudioChannelSet::ChannelType> getSpeakerOrder (Steinberg::Vst::SpeakerArrangement arr)
|
||||
static std::optional<Array<AudioChannelSet::ChannelType>> getSpeakerOrder (Steinberg::Vst::SpeakerArrangement arr)
|
||||
{
|
||||
using namespace Steinberg::Vst;
|
||||
using namespace Steinberg::Vst::SpeakerArr;
|
||||
|
|
@ -445,12 +439,16 @@ static Array<AudioChannelSet::ChannelType> getSpeakerOrder (Steinberg::Vst::Spea
|
|||
result.ensureStorageAllocated (channels);
|
||||
|
||||
for (auto i = 0; i < channels; ++i)
|
||||
result.add (getChannelType (arr, getSpeaker (arr, i)));
|
||||
if (const auto t = getChannelType (arr, getSpeaker (arr, i)))
|
||||
result.add (*t);
|
||||
|
||||
if (getChannelCount (arr) == result.size())
|
||||
return result;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
static Steinberg::Vst::SpeakerArrangement getVst3SpeakerArrangement (const AudioChannelSet& channels) noexcept
|
||||
static std::optional<Steinberg::Vst::SpeakerArrangement> getVst3SpeakerArrangement (const AudioChannelSet& channels) noexcept
|
||||
{
|
||||
using namespace Steinberg::Vst::SpeakerArr;
|
||||
|
||||
|
|
@ -470,21 +468,25 @@ static Steinberg::Vst::SpeakerArrangement getVst3SpeakerArrangement (const Audio
|
|||
Steinberg::Vst::SpeakerArrangement result = 0;
|
||||
|
||||
for (const auto& type : channels.getChannelTypes())
|
||||
result |= getSpeakerType (channels, type);
|
||||
if (const auto t = getSpeakerType (channels, type))
|
||||
result |= *t;
|
||||
|
||||
if (getChannelCount (result) == channels.size())
|
||||
return result;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
inline AudioChannelSet getChannelSetForSpeakerArrangement (Steinberg::Vst::SpeakerArrangement arr) noexcept
|
||||
inline std::optional<AudioChannelSet> getChannelSetForSpeakerArrangement (Steinberg::Vst::SpeakerArrangement arr) noexcept
|
||||
{
|
||||
using namespace Steinberg::Vst::SpeakerArr;
|
||||
|
||||
const auto result = AudioChannelSet::channelSetWithChannels (getSpeakerOrder (arr));
|
||||
if (const auto order = getSpeakerOrder (arr))
|
||||
return AudioChannelSet::channelSetWithChannels (*order);
|
||||
|
||||
// VST3 <-> JUCE layout conversion error: report this bug to the JUCE forum
|
||||
jassert (result.size() == getChannelCount (arr));
|
||||
|
||||
return result;
|
||||
jassertfalse;
|
||||
return {};
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
|
|
@ -522,7 +524,21 @@ private:
|
|||
*/
|
||||
static std::vector<int> makeChannelIndices (const AudioChannelSet& juceArrangement)
|
||||
{
|
||||
const auto order = getSpeakerOrder (getVst3SpeakerArrangement (juceArrangement));
|
||||
const auto order = [&]
|
||||
{
|
||||
const auto fallback = juceArrangement.getChannelTypes();
|
||||
const auto vst3Arrangement = getVst3SpeakerArrangement (juceArrangement);
|
||||
|
||||
if (! vst3Arrangement.has_value())
|
||||
return fallback;
|
||||
|
||||
const auto reordered = getSpeakerOrder (*vst3Arrangement);
|
||||
|
||||
if (! reordered.has_value() || AudioChannelSet::channelSetWithChannels (*reordered) != juceArrangement)
|
||||
return fallback;
|
||||
|
||||
return *reordered;
|
||||
}();
|
||||
|
||||
std::vector<int> result;
|
||||
|
||||
|
|
@ -627,14 +643,14 @@ static bool validateLayouts (Iterator first, Iterator last, const std::vector<Dy
|
|||
{
|
||||
auto& bus = *it;
|
||||
auto** busPtr = getAudioBusPointer (detail::Tag<FloatType>{}, bus);
|
||||
const auto expectedChannels = static_cast<int> (mapIterator->size());
|
||||
const auto actualChannels = static_cast<int> (bus.numChannels);
|
||||
const auto limit = jmin (expectedChannels, actualChannels);
|
||||
const auto expectedJuceChannels = (int) mapIterator->size();
|
||||
const auto actualVstChannels = (int) bus.numChannels;
|
||||
const auto limit = jmin (expectedJuceChannels, actualVstChannels);
|
||||
const auto anyChannelIsNull = std::any_of (busPtr, busPtr + limit, [] (auto* ptr) { return ptr == nullptr; });
|
||||
constexpr auto isInput = direction == Direction::input;
|
||||
|
||||
const auto channelCountIsUsable = isInput ? expectedChannels <= actualChannels
|
||||
: actualChannels <= expectedChannels;
|
||||
const auto channelCountIsUsable = isInput ? expectedJuceChannels <= actualVstChannels
|
||||
: actualVstChannels <= expectedJuceChannels;
|
||||
|
||||
// Null channels are allowed if the bus is inactive
|
||||
if (mapIterator->isHostActive() && (anyChannelIsNull || ! channelCountIsUsable))
|
||||
|
|
@ -642,7 +658,7 @@ static bool validateLayouts (Iterator first, Iterator last, const std::vector<Dy
|
|||
|
||||
// If this is hit, the destination bus has fewer channels than the source bus.
|
||||
// As a result, some channels will 'go missing', and channel layouts may be invalid.
|
||||
jassert (actualChannels == expectedChannels);
|
||||
jassert (actualVstChannels == expectedJuceChannels);
|
||||
}
|
||||
|
||||
// If the host didn't provide the full complement of buses, it must be because the other
|
||||
|
|
|
|||
|
|
@ -2458,34 +2458,33 @@ public:
|
|||
return module != nullptr ? module->getName() : String();
|
||||
}
|
||||
|
||||
void repopulateArrangements (Array<Vst::SpeakerArrangement>& inputArrangements, Array<Vst::SpeakerArrangement>& outputArrangements) const
|
||||
std::vector<Vst::SpeakerArrangement> getActualArrangements (bool isInput) const
|
||||
{
|
||||
inputArrangements.clearQuick();
|
||||
outputArrangements.clearQuick();
|
||||
std::vector<Vst::SpeakerArrangement> result;
|
||||
|
||||
auto numInputAudioBuses = getBusCount (true);
|
||||
auto numOutputAudioBuses = getBusCount (false);
|
||||
const auto numBuses = getBusCount (isInput);
|
||||
|
||||
for (int i = 0; i < numInputAudioBuses; ++i)
|
||||
inputArrangements.add (getArrangementForBus (processor, true, i));
|
||||
for (auto i = 0; i < numBuses; ++i)
|
||||
result.push_back (getArrangementForBus (processor, isInput, i));
|
||||
|
||||
for (int i = 0; i < numOutputAudioBuses; ++i)
|
||||
outputArrangements.add (getArrangementForBus (processor, false, i));
|
||||
return result;
|
||||
}
|
||||
|
||||
void processorLayoutsToArrangements (Array<Vst::SpeakerArrangement>& inputArrangements, Array<Vst::SpeakerArrangement>& outputArrangements)
|
||||
std::optional<std::vector<Vst::SpeakerArrangement>> busLayoutsToArrangements (bool isInput) const
|
||||
{
|
||||
inputArrangements.clearQuick();
|
||||
outputArrangements.clearQuick();
|
||||
std::vector<Vst::SpeakerArrangement> result;
|
||||
|
||||
auto numInputBuses = getBusCount (true);
|
||||
auto numOutputBuses = getBusCount (false);
|
||||
const auto numBuses = getBusCount (isInput);
|
||||
|
||||
for (int i = 0; i < numInputBuses; ++i)
|
||||
inputArrangements.add (getVst3SpeakerArrangement (getBus (true, i)->getLastEnabledLayout()));
|
||||
for (auto i = 0; i < numBuses; ++i)
|
||||
{
|
||||
if (const auto arr = getVst3SpeakerArrangement (getBus (isInput, i)->getLastEnabledLayout()))
|
||||
result.push_back (*arr);
|
||||
else
|
||||
return {};
|
||||
}
|
||||
|
||||
for (int i = 0; i < numOutputBuses; ++i)
|
||||
outputArrangements.add (getVst3SpeakerArrangement (getBus (false, i)->getLastEnabledLayout()));
|
||||
return result;
|
||||
}
|
||||
|
||||
void prepareToPlay (double newSampleRate, int estimatedSamplesPerBlock) override
|
||||
|
|
@ -2520,21 +2519,21 @@ public:
|
|||
|
||||
holder->initialise();
|
||||
|
||||
Array<Vst::SpeakerArrangement> inputArrangements, outputArrangements;
|
||||
processorLayoutsToArrangements (inputArrangements, outputArrangements);
|
||||
auto inArrangements = busLayoutsToArrangements (true) .value_or (std::vector<SpeakerArrangement>{});
|
||||
auto outArrangements = busLayoutsToArrangements (false).value_or (std::vector<SpeakerArrangement>{});
|
||||
|
||||
// Some plug-ins will crash if you pass a nullptr to setBusArrangements!
|
||||
SpeakerArrangement nullArrangement = {};
|
||||
auto* inputArrangementData = inputArrangements.isEmpty() ? &nullArrangement : inputArrangements.getRawDataPointer();
|
||||
auto* outputArrangementData = outputArrangements.isEmpty() ? &nullArrangement : outputArrangements.getRawDataPointer();
|
||||
auto* inData = inArrangements .empty() ? &nullArrangement : inArrangements .data();
|
||||
auto* outData = outArrangements.empty() ? &nullArrangement : outArrangements.data();
|
||||
|
||||
warnOnFailure (processor->setBusArrangements (inputArrangementData, inputArrangements.size(),
|
||||
outputArrangementData, outputArrangements.size()));
|
||||
warnOnFailure (processor->setBusArrangements (inData, static_cast<int32> (inArrangements .size()),
|
||||
outData, static_cast<int32> (outArrangements.size())));
|
||||
|
||||
Array<Vst::SpeakerArrangement> actualInArr, actualOutArr;
|
||||
repopulateArrangements (actualInArr, actualOutArr);
|
||||
const auto inArrActual = getActualArrangements (true);
|
||||
const auto outArrActual = getActualArrangements (false);
|
||||
|
||||
jassert (actualInArr == inputArrangements && actualOutArr == outputArrangements);
|
||||
jassert (inArrActual == inArrangements && outArrActual == outArrangements);
|
||||
|
||||
// Needed for having the same sample rate in processBlock(); some plugins need this!
|
||||
setRateAndBufferSizeDetails (newSampleRate, estimatedSamplesPerBlock);
|
||||
|
|
@ -2736,34 +2735,49 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
Array<Vst::SpeakerArrangement> inputArrangements, outputArrangements;
|
||||
|
||||
for (int i = 0; i < layouts.inputBuses.size(); ++i)
|
||||
const auto getPotentialArrangements = [&] (bool isInput) -> std::optional<std::vector<Vst::SpeakerArrangement>>
|
||||
{
|
||||
const auto& requested = layouts.getChannelSet (true, i);
|
||||
inputArrangements.add (getVst3SpeakerArrangement (requested.isDisabled() ? getBus (true, i)->getLastEnabledLayout() : requested));
|
||||
std::vector<Vst::SpeakerArrangement> result;
|
||||
|
||||
for (int i = 0; i < layouts.getBuses (isInput).size(); ++i)
|
||||
{
|
||||
const auto& requested = layouts.getChannelSet (isInput, i);
|
||||
|
||||
if (const auto arr = getVst3SpeakerArrangement (requested.isDisabled() ? getBus (true, i)->getLastEnabledLayout() : requested))
|
||||
result.push_back (*arr);
|
||||
else
|
||||
return {};
|
||||
}
|
||||
|
||||
for (int i = 0; i < layouts.outputBuses.size(); ++i)
|
||||
return result;
|
||||
};
|
||||
|
||||
auto inArrangements = getPotentialArrangements (true);
|
||||
auto outArrangements = getPotentialArrangements (false);
|
||||
|
||||
if (! inArrangements.has_value() || ! outArrangements.has_value())
|
||||
{
|
||||
const auto& requested = layouts.getChannelSet (false, i);
|
||||
outputArrangements.add (getVst3SpeakerArrangement (requested.isDisabled() ? getBus (false, i)->getLastEnabledLayout() : requested));
|
||||
// This bus layout can't be represented as a VST3 speaker arrangement
|
||||
return false;
|
||||
}
|
||||
|
||||
auto& inputArrangements = *inArrangements;
|
||||
auto& outputArrangements = *outArrangements;
|
||||
|
||||
// Some plug-ins will crash if you pass a nullptr to setBusArrangements!
|
||||
Vst::SpeakerArrangement nullArrangement = {};
|
||||
auto* inputArrangementData = inputArrangements.isEmpty() ? &nullArrangement : inputArrangements.getRawDataPointer();
|
||||
auto* outputArrangementData = outputArrangements.isEmpty() ? &nullArrangement : outputArrangements.getRawDataPointer();
|
||||
auto* inputArrangementData = inputArrangements .empty() ? &nullArrangement : inputArrangements .data();
|
||||
auto* outputArrangementData = outputArrangements.empty() ? &nullArrangement : outputArrangements.data();
|
||||
|
||||
if (processor->setBusArrangements (inputArrangementData, inputArrangements.size(),
|
||||
outputArrangementData, outputArrangements.size()) != kResultTrue)
|
||||
if (processor->setBusArrangements (inputArrangementData, static_cast<int32> (inputArrangements .size()),
|
||||
outputArrangementData, static_cast<int32> (outputArrangements.size())) != kResultTrue)
|
||||
return false;
|
||||
|
||||
// check if the layout matches the request
|
||||
Array<Vst::SpeakerArrangement> actualIn, actualOut;
|
||||
repopulateArrangements (actualIn, actualOut);
|
||||
const auto inArrActual = getActualArrangements (true);
|
||||
const auto outArrActual = getActualArrangements (false);
|
||||
|
||||
return (actualIn == inputArrangements && actualOut == outputArrangements);
|
||||
return (inArrActual == inputArrangements && outArrActual == outputArrangements);
|
||||
}
|
||||
|
||||
bool canApplyBusesLayout (const BusesLayout& layouts) const override
|
||||
|
|
@ -3385,7 +3399,8 @@ private:
|
|||
|
||||
Vst::SpeakerArrangement arr;
|
||||
if (processor != nullptr && processor->getBusArrangement (dir, i, arr) == kResultOk)
|
||||
layout = getChannelSetForSpeakerArrangement (arr);
|
||||
if (const auto set = getChannelSetForSpeakerArrangement (arr))
|
||||
layout = *set;
|
||||
|
||||
busProperties.addBus (isInput, toString (info.name), layout,
|
||||
(info.flags & Vst::BusInfo::kDefaultActive) != 0);
|
||||
|
|
|
|||
|
|
@ -309,29 +309,37 @@ public:
|
|||
*/
|
||||
struct BusesLayout
|
||||
{
|
||||
private:
|
||||
template <typename This>
|
||||
static auto& getBuses (This& t, bool isInput) { return isInput ? t.inputBuses : t.outputBuses; }
|
||||
|
||||
public:
|
||||
/** An array containing the list of input buses that this processor supports. */
|
||||
Array<AudioChannelSet> inputBuses;
|
||||
|
||||
/** An array containing the list of output buses that this processor supports. */
|
||||
Array<AudioChannelSet> outputBuses;
|
||||
|
||||
auto& getBuses (bool isInput) const { return getBuses (*this, isInput); }
|
||||
auto& getBuses (bool isInput) { return getBuses (*this, isInput); }
|
||||
|
||||
/** Get the number of channels of a particular bus */
|
||||
int getNumChannels (bool isInput, int busIndex) const noexcept
|
||||
{
|
||||
auto& bus = (isInput ? inputBuses : outputBuses);
|
||||
auto& bus = getBuses (isInput);
|
||||
return isPositiveAndBelow (busIndex, bus.size()) ? bus.getReference (busIndex).size() : 0;
|
||||
}
|
||||
|
||||
/** Get the channel set of a particular bus */
|
||||
AudioChannelSet& getChannelSet (bool isInput, int busIndex) noexcept
|
||||
{
|
||||
return (isInput ? inputBuses : outputBuses).getReference (busIndex);
|
||||
return getBuses (isInput).getReference (busIndex);
|
||||
}
|
||||
|
||||
/** Get the channel set of a particular bus */
|
||||
AudioChannelSet getChannelSet (bool isInput, int busIndex) const noexcept
|
||||
{
|
||||
return (isInput ? inputBuses : outputBuses)[busIndex];
|
||||
return getBuses (isInput)[busIndex];
|
||||
}
|
||||
|
||||
/** Get the input channel layout on the main bus. */
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue