1
0
Fork 0
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:
reuk 2023-01-11 20:09:50 +00:00
parent 6bd31bab35
commit 4b222427f9
No known key found for this signature in database
GPG key ID: 9ADCD339CFC98A11
4 changed files with 155 additions and 85 deletions

View file

@ -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,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;

View file

@ -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;
return result;
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

View file

@ -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 {};
}
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<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);

View file

@ -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. */