diff --git a/modules/juce_audio_processors/format_types/juce_VST3Common.h b/modules/juce_audio_processors/format_types/juce_VST3Common.h index 4b9cb482a6..6060206774 100644 --- a/modules/juce_audio_processors/format_types/juce_VST3Common.h +++ b/modules/juce_audio_processors/format_types/juce_VST3Common.h @@ -395,6 +395,10 @@ namespace detail { k91_6, { X::wideLeft, X::wideRight, X::centre, X::LFE, X::leftSurroundRear, X::rightSurroundRear, X::left, X::right, X::leftSurroundSide, X::rightSurroundSide, X::topFrontLeft, X::topFrontRight, X::topRearLeft, X::topRearRight, X::topSideLeft, X::topSideRight } }, { k90_6, { X::wideLeft, X::wideRight, X::centre, X::leftSurroundRear, X::rightSurroundRear, X::left, X::right, X::leftSurroundSide, X::rightSurroundSide, X::topFrontLeft, X::topFrontRight, X::topRearLeft, X::topRearRight, X::topSideLeft, X::topSideRight } }, }; + + #if JUCE_DEBUG + static std::once_flag layoutTableCheckedFlag; + #endif } inline bool isLayoutTableValid() @@ -422,7 +426,9 @@ static Array getSpeakerOrder (Steinberg::Vst::Spea using namespace Steinberg::Vst; using namespace Steinberg::Vst::SpeakerArr; - jassert (isLayoutTableValid()); + #if JUCE_DEBUG + std::call_once (detail::layoutTableCheckedFlag, [] { jassert (isLayoutTableValid()); }); + #endif // Check if this is a layout with a hard-coded conversion const auto arrangementMatches = [arr] (const auto& layoutPair) { return layoutPair.arrangement == arr; }; @@ -446,7 +452,9 @@ static Steinberg::Vst::SpeakerArrangement getVst3SpeakerArrangement (const Audio { using namespace Steinberg::Vst::SpeakerArr; - jassert (isLayoutTableValid()); + #if JUCE_DEBUG + std::call_once (detail::layoutTableCheckedFlag, [] { jassert (isLayoutTableValid()); }); + #endif const auto channelSetMatches = [&channels] (const auto& layoutPair) { @@ -525,6 +533,82 @@ private: }; +//============================================================================== +/* + Remaps a JUCE buffer to an equivalent VST3 layout. + + An instance of this class handles mappings for both float and double buffers, but in a single + direction (input or output). +*/ +class HostBufferMapper +{ +public: + /* Builds a cached map of juce <-> vst3 channel mappings. */ + void prepare (std::vector arrangements) + { + mappings = std::move (arrangements); + + floatBusMap .resize (mappings.size()); + doubleBusMap.resize (mappings.size()); + busBuffers .resize (mappings.size()); + } + + /* Applies the mapping to an AudioBuffer using JUCE channel layout. */ + template + Steinberg::Vst::AudioBusBuffers* getVst3LayoutForJuceBuffer (AudioBuffer& source) + { + int channelIndexOffset = 0; + + for (size_t i = 0; i < mappings.size(); ++i) + { + const auto& mapping = mappings[i]; + associateBufferTo (busBuffers[i], get (detail::Tag{})[i], source, mapping, channelIndexOffset); + channelIndexOffset += mapping.isActive() ? (int) mapping.size() : 0; + } + + return busBuffers.data(); + } + +private: + template + using Bus = std::vector; + + template + using BusMap = std::vector>; + + static void assignRawPointer (Steinberg::Vst::AudioBusBuffers& vstBuffers, float** raw) { vstBuffers.channelBuffers32 = raw; } + static void assignRawPointer (Steinberg::Vst::AudioBusBuffers& vstBuffers, double** raw) { vstBuffers.channelBuffers64 = raw; } + + template + void associateBufferTo (Steinberg::Vst::AudioBusBuffers& vstBuffers, + Bus& bus, + AudioBuffer& buffer, + const ChannelMapping& busMap, + int channelStartOffset) const + { + bus.clear(); + + for (size_t i = 0; i < busMap.size(); ++i) + { + bus.push_back (busMap.isActive() ? buffer.getWritePointer (channelStartOffset + busMap.getJuceChannelForVst3Channel ((int) i)) + : nullptr); + } + + assignRawPointer (vstBuffers, bus.data()); + vstBuffers.numChannels = (Steinberg::int32) busMap.size(); + vstBuffers.silenceFlags = busMap.isActive() ? 0 : std::numeric_limits::max(); + } + + auto& get (detail::Tag) { return floatBusMap; } + auto& get (detail::Tag) { return doubleBusMap; } + + BusMap floatBusMap; + BusMap doubleBusMap; + + std::vector busBuffers; + std::vector mappings; +}; + //============================================================================== template class VSTComSmartPtr @@ -1049,107 +1133,6 @@ private: JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiEventList) }; -//============================================================================== -template -struct VST3BufferExchange -{ - using Bus = Array; - using BusMap = Array; - - static void assignRawPointer (Steinberg::Vst::AudioBusBuffers& vstBuffers, float** raw) { vstBuffers.channelBuffers32 = raw; } - static void assignRawPointer (Steinberg::Vst::AudioBusBuffers& vstBuffers, double** raw) { vstBuffers.channelBuffers64 = raw; } - - /** Assigns a series of AudioBuffer's channels to an AudioBusBuffers' - @warning For speed, does not check the channel count and offsets according to the AudioBuffer - */ - static void associateBufferTo (Steinberg::Vst::AudioBusBuffers& vstBuffers, - Bus& bus, - AudioBuffer& buffer, - int numChannels, int channelStartOffset, - int sampleOffset = 0) - { - const int channelEnd = numChannels + channelStartOffset; - jassert (channelEnd >= 0 && channelEnd <= buffer.getNumChannels()); - - bus.clearQuick(); - - for (int i = channelStartOffset; i < channelEnd; ++i) - bus.add (buffer.getWritePointer (i, sampleOffset)); - - assignRawPointer (vstBuffers, (numChannels > 0 ? bus.getRawDataPointer() : nullptr)); - vstBuffers.numChannels = numChannels; - vstBuffers.silenceFlags = 0; - } - - static void mapArrangementToBuses (int& channelIndexOffset, int index, - Array& result, - BusMap& busMapToUse, const AudioChannelSet& arrangement, - AudioBuffer& source) - { - const int numChansForBus = arrangement.size(); - - if (index >= result.size()) - result.add (Steinberg::Vst::AudioBusBuffers()); - - if (index >= busMapToUse.size()) - busMapToUse.add (Bus()); - - associateBufferTo (result.getReference (index), - busMapToUse.getReference (index), - source, numChansForBus, channelIndexOffset); - - channelIndexOffset += numChansForBus; - } - - static void mapBufferToBuses (Array& result, BusMap& busMapToUse, - const Array& arrangements, - AudioBuffer& source) - { - int channelIndexOffset = 0; - - for (int i = 0; i < arrangements.size(); ++i) - mapArrangementToBuses (channelIndexOffset, i, result, busMapToUse, - arrangements.getUnchecked (i), source); - } - - static void mapBufferToBuses (Array& result, - Steinberg::Vst::IAudioProcessor& processor, - BusMap& busMapToUse, bool isInput, int numBuses, - AudioBuffer& source) - { - int channelIndexOffset = 0; - - for (int i = 0; i < numBuses; ++i) - mapArrangementToBuses (channelIndexOffset, i, - result, busMapToUse, - getArrangementForBus (&processor, isInput, i), - source); - } -}; - -template -struct VST3FloatAndDoubleBusMapCompositeHelper {}; - -struct VST3FloatAndDoubleBusMapComposite -{ - VST3BufferExchange::BusMap floatVersion; - VST3BufferExchange::BusMap doubleVersion; - - template - inline typename VST3BufferExchange::BusMap& get() { return VST3FloatAndDoubleBusMapCompositeHelper::get (*this); } -}; - - -template <> struct VST3FloatAndDoubleBusMapCompositeHelper -{ - static VST3BufferExchange::BusMap& get (VST3FloatAndDoubleBusMapComposite& impl) { return impl.floatVersion; } -}; - -template <> struct VST3FloatAndDoubleBusMapCompositeHelper -{ - static VST3BufferExchange::BusMap& get (VST3FloatAndDoubleBusMapComposite& impl) { return impl.doubleVersion; } -}; - //============================================================================== class FloatCache { diff --git a/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp b/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp index 90121aa093..62a47f99e4 100644 --- a/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp +++ b/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp @@ -2435,7 +2435,9 @@ public: warnOnFailure (holder->component->activateBus (Vst::kAudio, Vst::kOutput, i, getBus (false, i)->isEnabled() ? 1 : 0)); setLatencySamples (jmax (0, (int) processor->getLatencySamples())); - cachedBusLayouts = getBusesLayout(); + + inputBusMap .prepare (createChannelMappings (true)); + outputBusMap.prepare (createChannelMappings (false)); setStateForAllMidiBuses (true); @@ -2670,7 +2672,7 @@ public: bool result = syncBusLayouts (layouts); - // didn't succeed? Make sure it's back in it's original state + // didn't succeed? Make sure it's back in its original state if (! result) syncBusLayouts (getBusesLayout()); @@ -3017,9 +3019,7 @@ private: even if there aren't enough channels to process, as very poorly specified by the Steinberg SDK */ - VST3FloatAndDoubleBusMapComposite inputBusMap, outputBusMap; - Array inputBuses, outputBuses; - AudioProcessor::BusesLayout cachedBusLayouts; + HostBufferMapper inputBusMap, outputBusMap; StringArray programNames; Vst::ParamID programParameterID = (Vst::ParamID) -1; @@ -3214,6 +3214,17 @@ private: setStateForAllBusesOfType (holder->component, newState, false, false); // Activate/deactivate MIDI outputs } + std::vector createChannelMappings (bool isInput) const + { + std::vector result; + result.reserve ((size_t) getBusCount (isInput)); + + for (auto i = 0; i < getBusCount (isInput); ++i) + result.emplace_back (*getBus (isInput, i)); + + return result; + } + void setupIO() { setStateForAllMidiBuses (true); @@ -3226,7 +3237,8 @@ private: warnOnFailure (processor->setupProcessing (setup)); - cachedBusLayouts = getBusesLayout(); + inputBusMap .prepare (createChannelMappings (true)); + outputBusMap.prepare (createChannelMappings (false)); setRateAndBufferSizeDetails (setup.sampleRate, (int) setup.maxSamplesPerBlock); } @@ -3317,11 +3329,8 @@ private: template void associateWith (Vst::ProcessData& destination, AudioBuffer& buffer) { - VST3BufferExchange::mapBufferToBuses (inputBuses, inputBusMap.get(), cachedBusLayouts.inputBuses, buffer); - VST3BufferExchange::mapBufferToBuses (outputBuses, outputBusMap.get(), cachedBusLayouts.outputBuses, buffer); - - destination.inputs = inputBuses.getRawDataPointer(); - destination.outputs = outputBuses.getRawDataPointer(); + destination.inputs = inputBusMap .getVst3LayoutForJuceBuffer (buffer); + destination.outputs = outputBusMap.getVst3LayoutForJuceBuffer (buffer); } void associateWith (Vst::ProcessData& destination, MidiBuffer& midiBuffer)