diff --git a/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp b/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp index da79dcc63f..d809ae37e6 100644 --- a/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp +++ b/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp @@ -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> { - auto result = pluginInstance->getBusesLayout(); + Array 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 + { + 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,8 +3364,14 @@ public: { if (auto* bus = pluginInstance->getBus (dir == Vst::kInput, index)) { - arr = getVst3SpeakerArrangement (bus->getLastEnabledLayout()); - return kResultTrue; + 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; diff --git a/modules/juce_audio_processors/format_types/juce_VST3Common.h b/modules/juce_audio_processors/format_types/juce_VST3Common.h index 0090f3854a..eb204b34f5 100644 --- a/modules/juce_audio_processors/format_types/juce_VST3Common.h +++ b/modules/juce_audio_processors/format_types/juce_VST3Common.h @@ -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 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 (type) - (static_cast (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 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 (type)).findNextSetBit (0); - - // VST3 <-> JUCE layout conversion error: report this bug to the JUCE forum - jassert (channelType >= 33); - - return static_cast (static_cast (AudioChannelSet::discreteChannel0) + 6 + (channelType - 33)); + return {}; } namespace detail @@ -423,7 +417,7 @@ inline bool isLayoutTableValid() }); } -static Array getSpeakerOrder (Steinberg::Vst::SpeakerArrangement arr) +static std::optional> getSpeakerOrder (Steinberg::Vst::SpeakerArrangement arr) { using namespace Steinberg::Vst; using namespace Steinberg::Vst::SpeakerArr; @@ -445,12 +439,16 @@ static Array 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); - return result; + if (getChannelCount (arr) == result.size()) + return result; + + return {}; } -static Steinberg::Vst::SpeakerArrangement getVst3SpeakerArrangement (const AudioChannelSet& channels) noexcept +static std::optional 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; - return result; + if (getChannelCount (result) == channels.size()) + return result; + + return {}; } -inline AudioChannelSet getChannelSetForSpeakerArrangement (Steinberg::Vst::SpeakerArrangement arr) noexcept +inline std::optional 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 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 result; @@ -627,14 +643,14 @@ static bool validateLayouts (Iterator first, Iterator last, const std::vector{}, bus); - const auto expectedChannels = static_cast (mapIterator->size()); - const auto actualChannels = static_cast (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::vectorgetName() : String(); } - void repopulateArrangements (Array& inputArrangements, Array& outputArrangements) const + std::vector getActualArrangements (bool isInput) const { - inputArrangements.clearQuick(); - outputArrangements.clearQuick(); + std::vector 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& inputArrangements, Array& outputArrangements) + std::optional> busLayoutsToArrangements (bool isInput) const { - inputArrangements.clearQuick(); - outputArrangements.clearQuick(); + std::vector 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 inputArrangements, outputArrangements; - processorLayoutsToArrangements (inputArrangements, outputArrangements); + auto inArrangements = busLayoutsToArrangements (true) .value_or (std::vector{}); + auto outArrangements = busLayoutsToArrangements (false).value_or (std::vector{}); // 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 (inArrangements .size()), + outData, static_cast (outArrangements.size()))); - Array 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 inputArrangements, outputArrangements; - - for (int i = 0; i < layouts.inputBuses.size(); ++i) + const auto getPotentialArrangements = [&] (bool isInput) -> std::optional> { - const auto& requested = layouts.getChannelSet (true, i); - inputArrangements.add (getVst3SpeakerArrangement (requested.isDisabled() ? getBus (true, i)->getLastEnabledLayout() : requested)); + std::vector 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 {}; + } + + return result; + }; + + auto inArrangements = getPotentialArrangements (true); + auto outArrangements = getPotentialArrangements (false); + + if (! inArrangements.has_value() || ! outArrangements.has_value()) + { + // This bus layout can't be represented as a VST3 speaker arrangement + return false; } - for (int i = 0; i < layouts.outputBuses.size(); ++i) - { - const auto& requested = layouts.getChannelSet (false, i); - outputArrangements.add (getVst3SpeakerArrangement (requested.isDisabled() ? getBus (false, i)->getLastEnabledLayout() : requested)); - } + 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 (inputArrangements .size()), + outputArrangementData, static_cast (outputArrangements.size())) != kResultTrue) return false; // check if the layout matches the request - Array 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); diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessor.h b/modules/juce_audio_processors/processors/juce_AudioProcessor.h index b0acefa927..27accb8a6f 100644 --- a/modules/juce_audio_processors/processors/juce_AudioProcessor.h +++ b/modules/juce_audio_processors/processors/juce_AudioProcessor.h @@ -309,29 +309,37 @@ public: */ struct BusesLayout { + private: + template + 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 inputBuses; /** An array containing the list of output buses that this processor supports. */ Array 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. */