From 3872c0d3cf4a883e812df0565043f73e8f07eea4 Mon Sep 17 00:00:00 2001 From: attila Date: Mon, 17 Jul 2023 22:27:28 +0200 Subject: [PATCH] AAX Client: Fix returning invalid plugin IDs for layouts added in 2.5.0 --- .../utilities/juce_AAXClientExtensions.cpp | 214 +++++++++++++++++- 1 file changed, 205 insertions(+), 9 deletions(-) diff --git a/modules/juce_audio_processors/utilities/juce_AAXClientExtensions.cpp b/modules/juce_audio_processors/utilities/juce_AAXClientExtensions.cpp index d11ed295dd..91d3a08349 100644 --- a/modules/juce_audio_processors/utilities/juce_AAXClientExtensions.cpp +++ b/modules/juce_audio_processors/utilities/juce_AAXClientExtensions.cpp @@ -26,9 +26,174 @@ namespace juce { +class AAXPluginId +{ +public: + static std::optional create (std::array letters) + { + std::array indices; + + for (size_t i = 0; i < letters.size(); ++i) + { + if (const auto index = findIndexOfChar (letters[i])) + indices[i] = *index; + else + return std::nullopt; + } + + return AAXPluginId { std::move (indices) }; + } + + std::optional withIncrementedLetter (size_t index, size_t increment) const + { + if (indices.size() <= index) + return std::nullopt; + + auto copy = *this; + copy.indices[index] += increment; + + if ((size_t) std::size (validChars) <= copy.indices[index]) + return std::nullopt; + + return copy; + } + + int32 getPluginId() const + { + int32 result = 0; + + for (size_t i = 0; i < indices.size(); ++i) + result |= static_cast (validChars[indices[i]]) << (8 * (indices.size() - 1 - i)); + + return result; + } + + static std::optional findIndexOfChar (char c) + { + const auto ptr = std::find (std::begin (validChars), std::end (validChars), c); + return ptr != std::end (validChars) ? std::make_optional (std::distance (std::begin (validChars), ptr)) + : std::nullopt; + } + +private: + static inline constexpr char validChars[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + + explicit AAXPluginId (std::array indicesIn) + : indices (std::move (indicesIn)) + {} + + std::array indices; +}; + +static const AudioChannelSet channelSets[] { AudioChannelSet::disabled(), + AudioChannelSet::mono(), + AudioChannelSet::stereo(), + AudioChannelSet::createLCR(), + AudioChannelSet::createLCRS(), + AudioChannelSet::quadraphonic(), + AudioChannelSet::create5point0(), + AudioChannelSet::create5point1(), + AudioChannelSet::create6point0(), + AudioChannelSet::create6point1(), + AudioChannelSet::create7point0(), + AudioChannelSet::create7point1(), + AudioChannelSet::create7point0SDDS(), + AudioChannelSet::create7point1SDDS(), + AudioChannelSet::create7point0point2(), + AudioChannelSet::create7point1point2(), + AudioChannelSet::ambisonic (1), + AudioChannelSet::ambisonic (2), + AudioChannelSet::ambisonic (3), + AudioChannelSet::create5point0point2(), + AudioChannelSet::create5point1point2(), + AudioChannelSet::create5point0point4(), + AudioChannelSet::create5point1point4(), + AudioChannelSet::create7point0point4(), + AudioChannelSet::create7point1point4(), + AudioChannelSet::create7point0point6(), + AudioChannelSet::create7point1point6(), + AudioChannelSet::create9point0point4(), + AudioChannelSet::create9point1point4(), + AudioChannelSet::create9point0point6(), + AudioChannelSet::create9point1point6(), + AudioChannelSet::ambisonic (4), + AudioChannelSet::ambisonic (5), + AudioChannelSet::ambisonic (6), + AudioChannelSet::ambisonic (7) }; + int32 AAXClientExtensions::getPluginIDForMainBusConfig (const AudioChannelSet& mainInputLayout, const AudioChannelSet& mainOutputLayout, bool idForAudioSuite) const +{ + auto pluginId = [&] + { + auto opt = idForAudioSuite ? AAXPluginId::create ({ 'j', 'y', 'a', 'a' }) + : AAXPluginId::create ({ 'j', 'c', 'a', 'a' }); + jassert (opt.has_value()); + return *opt; + }(); + + for (const auto& [channelSet, indexToModify] : { std::tuple (&mainInputLayout, (size_t) 2), + std::tuple (&mainOutputLayout, (size_t) 3) }) + { + const auto increment = (size_t) std::distance (std::begin (channelSets), + std::find (std::begin (channelSets), + std::end (channelSets), + *channelSet)); + + if (auto modifiedPluginIdOpt = pluginId.withIncrementedLetter (indexToModify, increment); + increment < (size_t) std::size (channelSets) && modifiedPluginIdOpt.has_value()) + { + pluginId = *modifiedPluginIdOpt; + } + else + { + jassertfalse; + } + } + + return pluginId.getPluginId(); +} + +String AAXClientExtensions::getPageFileName() const +{ + #ifdef JucePlugin_AAXPageTableFile + #warning "JucePlugin_AAXPageTableFile is deprecated, instead implement AAXClientExtensions::getPageFileName()" + return JucePlugin_AAXPageTableFile; + #else + return {}; + #endif +} + +//============================================================================== +//============================================================================== +#if JUCE_UNIT_TESTS + +static std::array toCharArray (int32 identifier) +{ + std::array result; + + for (size_t i = 0; i < result.size(); ++i) + result[i] = static_cast ((identifier >> (i * 8)) & 0xff); + + return result; +} + +static bool isValidAAXPluginId (int32 pluginId) +{ + const auto chars = toCharArray (pluginId); + + return std::all_of (std::begin (chars), + std::end (chars), + [] (const auto& c) + { + return AAXPluginId::findIndexOfChar (c).has_value(); + }); +} + +static int32 getPluginIDForMainBusConfigJuce705 (const AudioChannelSet& mainInputLayout, + const AudioChannelSet& mainOutputLayout, + bool idForAudioSuite) { int uniqueFormatId = 0; @@ -79,7 +244,7 @@ int32 AAXClientExtensions::getPluginIDForMainBusConfig (const AudioChannelSet& m const auto index = (int) std::distance (std::begin (sets), std::find (std::begin (sets), std::end (sets), set)); - if (index != numElementsInArray (sets)) + if (index != (int) std::size (sets)) aaxFormatIndex = index; else jassertfalse; @@ -90,14 +255,45 @@ int32 AAXClientExtensions::getPluginIDForMainBusConfig (const AudioChannelSet& m return (idForAudioSuite ? 0x6a796161 /* 'jyaa' */ : 0x6a636161 /* 'jcaa' */) + uniqueFormatId; } -String AAXClientExtensions::getPageFileName() const +class AAXClientExtensionsTests : public UnitTest { - #ifdef JucePlugin_AAXPageTableFile - #warning "JucePlugin_AAXPageTableFile is deprecated, instead implement AAXClientExtensions::getPageFileName()" - return JucePlugin_AAXPageTableFile; - #else - return {}; - #endif -} +public: + AAXClientExtensionsTests() + : UnitTest ("AAXClientExtensionsTests", UnitTestCategories::audioProcessors) + {} + + void runTest() override + { + AAXClientExtensions extensions; + + beginTest ("Previously valid PluginIds should be unchanged"); + { + for (const auto& input : channelSets) + for (const auto& output : channelSets) + for (const auto idForAudioSuite : { false, true }) + if (const auto oldId = getPluginIDForMainBusConfigJuce705 (input, output, idForAudioSuite); isValidAAXPluginId (oldId)) + expect (extensions.getPluginIDForMainBusConfig (input, output, idForAudioSuite) == oldId); + } + + beginTest ("Valid, unique PluginIds should be generated for all configurations"); + { + std::set pluginIds; + + for (const auto& input : channelSets) + for (const auto& output : channelSets) + for (const auto idForAudioSuite : { false, true }) + pluginIds.insert (extensions.getPluginIDForMainBusConfig (input, output, idForAudioSuite)); + + for (auto identifier : pluginIds) + expect (isValidAAXPluginId (identifier)); + + expect (pluginIds.size() == square (std::size (channelSets)) * 2); + } + } +}; + +static AAXClientExtensionsTests aaxClientExtensionsTests; + +#endif } // namespace juce