diff --git a/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp b/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp index 3e63d33722..e10476149f 100644 --- a/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp +++ b/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp @@ -233,7 +233,19 @@ public: static Vst::UnitID getUnitID (const AudioProcessorParameterGroup* group) { - return group == nullptr ? Vst::kRootUnitId : group->getID().hashCode(); + if (group == nullptr || group->getParent() == nullptr) + return Vst::kRootUnitId; + + // From the VST3 docs: + // Up to 2^31 parameters can be exported with id range [0, 2147483648] + // (the range [2147483649, 429496729] is reserved for host application). + auto unitID = group->getID().hashCode() & 0x7fffffff; + + // If you hit this assertion then your group ID is hashing to a value + // reserved by the VST3 SDK. Use a different group ID. + jassert (unitID != Vst::kRootUnitId); + + return unitID; } int getNumParameters() const noexcept { return vstParamIDs.size(); } @@ -263,6 +275,21 @@ private: { parameterGroups = audioProcessor->getParameterTree().getSubgroups (true); + #if JUCE_DEBUG + auto allGroups = parameterGroups; + allGroups.add (&audioProcessor->getParameterTree()); + std::unordered_set unitIDs; + + for (auto* group : allGroups) + { + auto insertResult = unitIDs.insert (getUnitID (group)); + + // If you hit this assertion then either a group ID is not unique or + // you are very unlucky and a hashed group ID is not unique + jassert (insertResult.second); + } + #endif + #if JUCE_FORCE_USE_LEGACY_PARAM_IDS const bool forceLegacyParamIDs = true; #else diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp b/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp index c805991315..1c23e9d3ec 100644 --- a/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp +++ b/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp @@ -443,6 +443,24 @@ void AudioProcessor::checkForDuplicateParamID (AudioProcessorParameter* param) #endif } +void AudioProcessor::checkForDuplicateGroupIDs (const AudioProcessorParameterGroup& newGroup) +{ + ignoreUnused (newGroup); + + #if JUCE_DEBUG + auto groups = newGroup.getSubgroups (true); + groups.add (&newGroup); + + for (auto* group : groups) + { + auto insertResult = groupIDs.insert (group->getID()); + + // If you hit this assertion then a group ID is not unique + jassert (insertResult.second); + } + #endif +} + const Array& AudioProcessor::getParameters() const { return flatParameterList; } const AudioProcessorParameterGroup& AudioProcessor::getParameterTree() const { return parameterTree; } @@ -461,6 +479,7 @@ void AudioProcessor::addParameter (AudioProcessorParameter* param) void AudioProcessor::addParameterGroup (std::unique_ptr group) { jassert (group != nullptr); + checkForDuplicateGroupIDs (*group); auto oldSize = flatParameterList.size(); flatParameterList.addArray (group->getParameters (true)); @@ -481,9 +500,12 @@ void AudioProcessor::setParameterTree (AudioProcessorParameterGroup&& newTree) { #if JUCE_DEBUG paramIDs.clear(); + groupIDs.clear(); #endif parameterTree = std::move (newTree); + checkForDuplicateGroupIDs (parameterTree); + flatParameterList = parameterTree.getParameters (true); for (int i = 0; i < flatParameterList.size(); ++i) diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessor.h b/modules/juce_audio_processors/processors/juce_AudioProcessor.h index 02c3de12eb..27e4338640 100644 --- a/modules/juce_audio_processors/processors/juce_AudioProcessor.h +++ b/modules/juce_audio_processors/processors/juce_AudioProcessor.h @@ -1487,10 +1487,11 @@ private: #endif bool textRecursionCheck = false; - std::unordered_set paramIDs; + std::unordered_set paramIDs, groupIDs; #endif void checkForDuplicateParamID (AudioProcessorParameter*); + void checkForDuplicateGroupIDs (const AudioProcessorParameterGroup&); AudioProcessorListener* getListenerLocked (int) const noexcept; void updateSpeakerFormatStrings();