diff --git a/modules/juce_audio_plugin_client/LV2/juce_LV2_Client.cpp b/modules/juce_audio_plugin_client/LV2/juce_LV2_Client.cpp index b6e3f12cba..a3f221c4e4 100644 --- a/modules/juce_audio_plugin_client/LV2/juce_LV2_Client.cpp +++ b/modules/juce_audio_plugin_client/LV2/juce_LV2_Client.cpp @@ -124,7 +124,14 @@ public: */ static String getIri (const AudioProcessorParameter& param) { - return URL::addEscapeChars (LegacyAudioParameter::getParamID (¶m, false), true); + const auto urlSanitised = URL::addEscapeChars (LegacyAudioParameter::getParamID (¶m, false), true); + const auto ttlSanitised = lv2_shared::sanitiseStringAsTtlName (urlSanitised); + + // If this is hit, the parameter ID could not be represented directly in the plugin ttl. + // We'll replace offending characters with '_'. + jassert (urlSanitised == ttlSanitised); + + return ttlSanitised; } void setValueFromHost (LV2_URID urid, float value) noexcept @@ -217,6 +224,11 @@ private: result.push_back (urid); } + // If this is hit, some parameters have duplicate IDs. + // This may be because the IDs resolve to the same string when removing characters that + // are invalid in a TTL name. + jassert (std::set (result.begin(), result.end()).size() == result.size()); + return result; }(); const std::map uridToIndexMap = [&] diff --git a/modules/juce_audio_processors/format_types/juce_LV2Common.h b/modules/juce_audio_processors/format_types/juce_LV2Common.h index d3f3275dbc..92a5a987a4 100644 --- a/modules/juce_audio_processors/format_types/juce_LV2Common.h +++ b/modules/juce_audio_processors/format_types/juce_LV2Common.h @@ -615,6 +615,55 @@ static inline std::vector findStableBusOrder (const String& mainGro return result; } +/* See https://www.w3.org/TeamSubmission/turtle/#sec-grammar-grammar +*/ +static inline bool isNameStartChar (juce_wchar input) +{ + return ('A' <= input && input <= 'Z') + || input == '_' + || ('a' <= input && input <= 'z') + || (0x000c0 <= input && input <= 0x000d6) + || (0x000d8 <= input && input <= 0x000f6) + || (0x000f8 <= input && input <= 0x000ff) + || (0x00370 <= input && input <= 0x0037d) + || (0x0037f <= input && input <= 0x01fff) + || (0x0200c <= input && input <= 0x0200d) + || (0x02070 <= input && input <= 0x0218f) + || (0x02c00 <= input && input <= 0x02fef) + || (0x03001 <= input && input <= 0x0d7ff) + || (0x0f900 <= input && input <= 0x0fdcf) + || (0x0fdf0 <= input && input <= 0x0fffd) + || (0x10000 <= input && input <= 0xeffff); +} + +static inline bool isNameChar (juce_wchar input) +{ + return isNameStartChar (input) + || input == '-' + || ('0' <= input && input <= '9') + || input == 0x000b7 + || (0x00300 <= input && input <= 0x0036f) + || (0x0203f <= input && input <= 0x02040); +} + +static inline String sanitiseStringAsTtlName (const String& input) +{ + if (input.isEmpty()) + return {}; + + std::vector sanitised; + sanitised.reserve (static_cast (input.length())); + + sanitised.push_back (isNameStartChar (input[0]) ? input[0] : '_'); + + std::for_each (std::begin (input) + 1, std::end (input), [&] (juce_wchar x) + { + sanitised.push_back (isNameChar (x) ? x : '_'); + }); + + return String (CharPointer_UTF32 { sanitised.data() }, sanitised.size()); +} + } }