diff --git a/modules/juce_audio_basics/buffers/juce_AudioChannelSet.h b/modules/juce_audio_basics/buffers/juce_AudioChannelSet.h index 423175f234..4151d89c6c 100644 --- a/modules/juce_audio_basics/buffers/juce_AudioChannelSet.h +++ b/modules/juce_audio_basics/buffers/juce_AudioChannelSet.h @@ -240,7 +240,13 @@ public: /** Creates a set for a 9.1.6 surround setup (left, right, centre, LFE, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear, wideLeft, wideRight, topFrontLeft, topFrontRight, topSideLeft, topSideRight, topRearLeft, topRearRight). - Is equivalent to: kAudioChannelLayoutTag_Atmos_9_1_6 (CoreAudio). + Note that the VST3 layout arranges the front speakers "L Lc C Rc R", but the JUCE layout + uses the arrangement "wideLeft left centre right wideRight". To maintain the relative + positions of the speakers, the channels will be remapped accordingly. This means that the + VST3 host's "L" channel will be received on a JUCE plugin's "wideLeft" channel, the + "Lc" channel will be received on a JUCE plugin's "left" channel, and so on. + + Is equivalent to: k91_6 (VST3), kAudioChannelLayoutTag_Atmos_9_1_6 (CoreAudio). */ static AudioChannelSet JUCE_CALLTYPE create9point1point6(); diff --git a/modules/juce_audio_processors/format_types/juce_VST3Common.h b/modules/juce_audio_processors/format_types/juce_VST3Common.h index 8c40015c69..4b9cb482a6 100644 --- a/modules/juce_audio_processors/format_types/juce_VST3Common.h +++ b/modules/juce_audio_processors/format_types/juce_VST3Common.h @@ -346,41 +346,116 @@ static AudioChannelSet::ChannelType getChannelType (Steinberg::Vst::SpeakerArran return static_cast (static_cast (AudioChannelSet::discreteChannel0) + 6 + (channelType - 33)); } +namespace detail +{ + struct LayoutPair + { + Steinberg::Vst::SpeakerArrangement arrangement; + std::initializer_list channelOrder; + }; + + using namespace Steinberg::Vst::SpeakerArr; + using X = AudioChannelSet; + + /* Maps VST3 layouts to the equivalent JUCE channels, in VST3 order. + + The channel types are taken from the equivalent JUCE AudioChannelSet, and then reordered to + match the VST3 speaker positions. + */ + const LayoutPair layoutTable[] + { + { kEmpty, {} }, + { kMono, { X::centre } }, + { kStereo, { X::left, X::right } }, + { k30Cine, { X::left, X::right, X::centre } }, + { k30Music, { X::left, X::right, X::surround } }, + { k40Cine, { X::left, X::right, X::centre, X::surround } }, + { k50, { X::left, X::right, X::centre, X::leftSurround, X::rightSurround } }, + { k51, { X::left, X::right, X::centre, X::LFE, X::leftSurround, X::rightSurround } }, + { k60Cine, { X::left, X::right, X::centre, X::leftSurround, X::rightSurround, X::centreSurround } }, + { k61Cine, { X::left, X::right, X::centre, X::LFE, X::leftSurround, X::rightSurround, X::centreSurround } }, + { k60Music, { X::left, X::right, X::leftSurround, X::rightSurround, X::leftSurroundSide, X::rightSurroundSide } }, + { k61Music, { X::left, X::right, X::LFE, X::leftSurround, X::rightSurround, X::leftSurroundSide, X::rightSurroundSide } }, + { k70Music, { X::left, X::right, X::centre, X::leftSurroundRear, X::rightSurroundRear, X::leftSurroundSide, X::rightSurroundSide } }, + { k70Cine, { X::left, X::right, X::centre, X::leftSurround, X::rightSurround, X::leftCentre, X::rightCentre } }, + { k71Music, { X::left, X::right, X::centre, X::LFE, X::leftSurroundRear, X::rightSurroundRear, X::leftSurroundSide, X::rightSurroundSide } }, + { k71Cine, { X::left, X::right, X::centre, X::LFE, X::leftSurround, X::rightSurround, X::leftCentre, X::rightCentre } }, + { k40Music, { X::left, X::right, X::leftSurround, X::rightSurround } }, + + { k51_4, { X::left, X::right, X::centre, X::LFE, X::leftSurround, X::rightSurround, X::topFrontLeft, X::topFrontRight, X::topRearLeft, X::topRearRight } }, + { k50_4, { X::left, X::right, X::centre, X::leftSurround, X::rightSurround, X::topFrontLeft, X::topFrontRight, X::topRearLeft, X::topRearRight } }, + { k71_2, { X::left, X::right, X::centre, X::LFE, X::leftSurroundRear, X::rightSurroundRear, X::leftSurroundSide, X::rightSurroundSide, X::topSideLeft, X::topSideRight } }, + { k70_2, { X::left, X::right, X::centre, X::leftSurroundRear, X::rightSurroundRear, X::leftSurroundSide, X::rightSurroundSide, X::topSideLeft, X::topSideRight } }, + { k71_4, { X::left, X::right, X::centre, X::LFE, X::leftSurroundRear, X::rightSurroundRear, X::leftSurroundSide, X::rightSurroundSide, X::topFrontLeft, X::topFrontRight, X::topRearLeft, X::topRearRight } }, + { k70_4, { X::left, X::right, X::centre, X::leftSurroundRear, X::rightSurroundRear, X::leftSurroundSide, X::rightSurroundSide, X::topFrontLeft, X::topFrontRight, X::topRearLeft, X::topRearRight } }, + { k71_6, { X::left, X::right, X::centre, X::LFE, X::leftSurroundRear, X::rightSurroundRear, X::leftSurroundSide, X::rightSurroundSide, X::topFrontLeft, X::topFrontRight, X::topRearLeft, X::topRearRight, X::topSideLeft, X::topSideRight } }, + { k70_6, { X::left, X::right, X::centre, X::leftSurroundRear, X::rightSurroundRear, X::leftSurroundSide, X::rightSurroundSide, X::topFrontLeft, X::topFrontRight, X::topRearLeft, X::topRearRight, X::topSideLeft, X::topSideRight } }, + + // The VST3 layout uses 'left/right' and 'left-of-center/right-of-center', but the JUCE layout uses 'left/right' and 'wide-left/wide-right'. + { 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 } }, + }; +} + +inline bool isLayoutTableValid() +{ + for (const auto& item : detail::layoutTable) + if ((size_t) countNumberOfBits (item.arrangement) != item.channelOrder.size()) + return false; + + std::set arrangements; + + for (const auto& item : detail::layoutTable) + arrangements.insert (item.arrangement); + + if (arrangements.size() != (size_t) numElementsInArray (detail::layoutTable)) + return false; // There's a duplicate speaker arrangement + + return std::all_of (std::begin (detail::layoutTable), std::end (detail::layoutTable), [] (const auto& item) + { + return std::set (item.channelOrder).size() == item.channelOrder.size(); + }); +} + +static Array getSpeakerOrder (Steinberg::Vst::SpeakerArrangement arr) +{ + using namespace Steinberg::Vst; + using namespace Steinberg::Vst::SpeakerArr; + + jassert (isLayoutTableValid()); + + // Check if this is a layout with a hard-coded conversion + const auto arrangementMatches = [arr] (const auto& layoutPair) { return layoutPair.arrangement == arr; }; + const auto iter = std::find_if (std::begin (detail::layoutTable), std::end (detail::layoutTable), arrangementMatches); + + if (iter != std::end (detail::layoutTable)) + return iter->channelOrder; + + // There's no hard-coded conversion, so assume that the channels are in the same orders in both layouts. + const auto channels = getChannelCount (arr); + Array result; + result.ensureStorageAllocated (channels); + + for (auto i = 0; i < channels; ++i) + result.add (getChannelType (arr, getSpeaker (arr, i))); + + return result; +} + static Steinberg::Vst::SpeakerArrangement getVst3SpeakerArrangement (const AudioChannelSet& channels) noexcept { using namespace Steinberg::Vst::SpeakerArr; - if (channels == AudioChannelSet::disabled()) return kEmpty; - if (channels == AudioChannelSet::mono()) return kMono; - if (channels == AudioChannelSet::stereo()) return kStereo; - if (channels == AudioChannelSet::createLCR()) return k30Cine; - if (channels == AudioChannelSet::createLRS()) return k30Music; - if (channels == AudioChannelSet::createLCRS()) return k40Cine; - if (channels == AudioChannelSet::create5point0()) return k50; - if (channels == AudioChannelSet::create5point1()) return k51; - if (channels == AudioChannelSet::create6point0()) return k60Cine; - if (channels == AudioChannelSet::create6point1()) return k61Cine; - if (channels == AudioChannelSet::create6point0Music()) return k60Music; - if (channels == AudioChannelSet::create6point1Music()) return k61Music; - if (channels == AudioChannelSet::create7point0()) return k70Music; - if (channels == AudioChannelSet::create7point0SDDS()) return k70Cine; - if (channels == AudioChannelSet::create7point1()) return k71CineSideFill; - if (channels == AudioChannelSet::create7point1SDDS()) return k71Cine; - if (channels == AudioChannelSet::ambisonic()) return kAmbi1stOrderACN; - if (channels == AudioChannelSet::quadraphonic()) return k40Music; - if (channels == AudioChannelSet::create5point1point4()) return k51_4; - if (channels == AudioChannelSet::create7point0point2()) return k71_2 & ~(Steinberg::Vst::kSpeakerLfe); - if (channels == AudioChannelSet::create7point1point2()) return k71_2; - if (channels == AudioChannelSet::create7point0point4()) return k71_4 & ~(Steinberg::Vst::kSpeakerLfe); - if (channels == AudioChannelSet::create7point1point4()) return k71_4; - if (channels == AudioChannelSet::create7point1point6()) return k71_6; - if (channels == AudioChannelSet::create9point1point6()) return k91_6; - if (channels == AudioChannelSet::ambisonic (0)) return (1ull << 20); - if (channels == AudioChannelSet::ambisonic (1)) return (1ull << 20) | (1ull << 21) | (1ull << 22) | (1ull << 23); - #if VST_VERSION >= 0x030608 - if (channels == AudioChannelSet::ambisonic (2)) return kAmbi2cdOrderACN; - if (channels == AudioChannelSet::ambisonic (3)) return kAmbi3rdOrderACN; - #endif + jassert (isLayoutTableValid()); + + const auto channelSetMatches = [&channels] (const auto& layoutPair) + { + return AudioChannelSet::channelSetWithChannels (layoutPair.channelOrder) == channels; + }; + const auto iter = std::find_if (std::begin (detail::layoutTable), std::end (detail::layoutTable), channelSetMatches); + + if (iter != std::end (detail::layoutTable)) + return iter->arrangement; Steinberg::Vst::SpeakerArrangement result = 0; @@ -394,53 +469,61 @@ static AudioChannelSet getChannelSetForSpeakerArrangement (Steinberg::Vst::Speak { using namespace Steinberg::Vst::SpeakerArr; - switch (arr) + const auto result = AudioChannelSet::channelSetWithChannels (getSpeakerOrder (arr)); + + // VST3 <-> JUCE layout conversion error: report this bug to the JUCE forum + jassert (result.size() == getChannelCount (arr)); + + return result; +} + +//============================================================================== +struct ChannelMapping +{ + explicit ChannelMapping (const AudioChannelSet& layout) + : indices (makeChannelIndices (layout)), + active (true) { - case kEmpty: return AudioChannelSet::disabled(); - case kMono: return AudioChannelSet::mono(); - case kStereo: return AudioChannelSet::stereo(); - case k30Cine: return AudioChannelSet::createLCR(); - case k30Music: return AudioChannelSet::createLRS(); - case k40Cine: return AudioChannelSet::createLCRS(); - case k50: return AudioChannelSet::create5point0(); - case k51: return AudioChannelSet::create5point1(); - case k60Cine: return AudioChannelSet::create6point0(); - case k61Cine: return AudioChannelSet::create6point1(); - case k60Music: return AudioChannelSet::create6point0Music(); - case k61Music: return AudioChannelSet::create6point1Music(); - case k70Music: return AudioChannelSet::create7point0(); - case k70Cine: return AudioChannelSet::create7point0SDDS(); - case k71CineSideFill: return AudioChannelSet::create7point1(); - case k71Cine: return AudioChannelSet::create7point1SDDS(); - case k40Music: return AudioChannelSet::quadraphonic(); - case k71_2: return AudioChannelSet::create7point1point2(); - case k71_2 & ~(Steinberg::Vst::kSpeakerLfe): return AudioChannelSet::create7point0point2(); - case k71_4: return AudioChannelSet::create7point1point4(); - case k71_4 & ~(Steinberg::Vst::kSpeakerLfe): return AudioChannelSet::create7point0point4(); - case k71_6: return AudioChannelSet::create7point1point6(); - case (1 << 20): return AudioChannelSet::ambisonic (0); - case kAmbi1stOrderACN: return AudioChannelSet::ambisonic (1); - #if VST_VERSION >= 0x030608 - case kAmbi2cdOrderACN: return AudioChannelSet::ambisonic (2); - case kAmbi3rdOrderACN: return AudioChannelSet::ambisonic (3); - #endif } - AudioChannelSet result; + explicit ChannelMapping (const AudioProcessor::Bus& juceBus) + : indices (makeChannelIndices (juceBus.getLastEnabledLayout())), + active (juceBus.isEnabled()) + { + } - BigInteger vstChannels (static_cast (arr)); - for (auto bit = vstChannels.findNextSetBit (0); bit != -1; bit = vstChannels.findNextSetBit (bit + 1)) + int getJuceChannelForVst3Channel (int vst3Channel) const { return indices[(size_t) vst3Channel]; } + + size_t size() const { return indices.size(); } + + void setActive (bool activeIn) { active = activeIn; } + bool isActive() const { return active; } + +private: + /* Builds a table that provides the index of the corresponding JUCE channel, given a VST3 channel. + + Depending on the mapping, the VST3 arrangement and JUCE arrangement may not contain channels + that map 1:1 via getChannelType. For example, the VST3 7.1 layout contains + 'kSpeakerLs' which maps to the 'leftSurround' channel type, but the JUCE 7.1 layout does not + contain this channel type. As a result, we need to try to map the channels sensibly, even + if there's not a 'direct' mapping. + */ + static std::vector makeChannelIndices (const AudioChannelSet& juceArrangement) { - AudioChannelSet::ChannelType channelType = getChannelType (arr, 1ull << static_cast (bit)); - if (channelType != AudioChannelSet::unknown) - result.addChannel (channelType); + const auto order = getSpeakerOrder (getVst3SpeakerArrangement (juceArrangement)); + + std::vector result; + + for (const auto& type : order) + result.push_back (juceArrangement.getChannelIndexForType (type)); + + return result; } - // VST3 <-> JUCE layout conversion error: report this bug to the JUCE forum - jassert (result.size() == vstChannels.countNumberOfSetBits()); + std::vector indices; + bool active = true; +}; - return result; -} //============================================================================== template