| @@ -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 (auto it = begin; it != end; ++it) | |||
| { | |||
| const auto set = getChannelSetForSpeakerArrangement (*it); | |||
| if (! set.has_value()) | |||
| return {}; | |||
| result.add (*set); | |||
| } | |||
| for (int i = 0; i < numIns; ++i) | |||
| result.getChannelSet (true, i) = getChannelSetForSpeakerArrangement (inputs[i]); | |||
| return result; | |||
| }; | |||
| const auto optionalRequestedLayout = [&]() -> std::optional<AudioProcessor::BusesLayout> | |||
| { | |||
| const auto ins = toLayoutsArray (inputs, inputs + numIns); | |||
| const auto outs = toLayoutsArray (outputs, outputs + numOuts); | |||
| for (int i = 0; i < numOuts; ++i) | |||
| result.getChannelSet (false, i) = getChannelSetForSpeakerArrangement (outputs[i]); | |||
| 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; | |||
| @@ -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); | |||
| return result; | |||
| 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 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. */ | |||