diff --git a/distrho/DistrhoPlugin.hpp b/distrho/DistrhoPlugin.hpp index 9c3727aa..ef7c228f 100644 --- a/distrho/DistrhoPlugin.hpp +++ b/distrho/DistrhoPlugin.hpp @@ -142,19 +142,42 @@ static const uint32_t kParameterIsTrigger = 0x20 | kParameterIsBoolean; @defgroup StateHints State Hints Various state hints. - @see Plugin::getStateHints + @see State::hints @{ */ /** - State is available for the host to see and change. + State is visible and readable by hosts that support string-type plugin parameters. */ -static const uint32_t kStateIsHostVisible = 0x01; +static const uint32_t kStateIsHostReadable = 0x01; /** - State is a filename path. + State is writable by the host, allowing users to arbitrarily change the state.@n + For obvious reasons a writable state is also readable by the host. */ -static const uint32_t kStateIsFilenamePath = 0x02 | kStateIsHostVisible; +static const uint32_t kStateIsHostWritable = 0x02 | kStateIsHostReadable; + +/** + State is a filename path instead of a regular string.@n + The readable and writable hints are required for filenames to work, and thus are automatically set. + */ +static const uint32_t kStateIsFilenamePath = 0x04 | kStateIsHostWritable; + +/** + State is a base64 encoded string. + */ +static const uint32_t kStateIsBase64Blob = 0x08; + +/** + State is for Plugin/DSP side only, meaning there is never a need to notify the UI when it changes. + */ +static const uint32_t kStateIsOnlyForDSP = 0x10; + +/** + State is for UI side only.@n + If the DSP and UI are separate and the UI is not available, this property won't be saved. + */ +static const uint32_t kStateIsOnlyForUI = 0x20; /** @} */ @@ -639,6 +662,48 @@ struct PortGroup { String symbol; }; +/** + State. + + In DPF states refer to key:value string pairs, used to store arbitrary non-parameter data.@n + By default states are completely internal to the plugin and not visible by the host.@n + Flags can be set to allow hosts to see and/or change them. + + TODO API under construction + */ +struct State { + /** + Hints describing this state. + @note Changing these hints can break compatibility with previously saved data. + @see StateHints + */ + uint32_t hints; + + /** + The key or "symbol" of this state.@n + A state key is a short restricted name used as a machine and human readable identifier. + @note State keys MUST be unique within a plugin instance. + TODO define rules for allowed characters, must be usable as URI non-encoded parameters + */ + String key; + + /** + The default value of this state. + */ + String defaultValue; + + /** + String representation of this state. + */ + String label; + + /** + An extensive description/comment about this state. + @note This value is optional and only used for LV2. + */ + String description; +}; + /** MIDI event. */ @@ -928,9 +993,13 @@ public: #if DISTRHO_PLUGIN_WANT_STATE /** - Notify the host about a state value change. - This function will automatically trigger a state update on the UI side. - It must not be called during run. + Notify the host about a state value change.@n + This function will automatically trigger a state update on the UI side.@n + It must not be called during run.@n + The state must be host readable. + @note this function does nothing on DSSI plugin format, as DSSI only supports UI->DSP messages. + + TODO API under construction */ bool updateStateValue(const char* key, const char* value) noexcept; #endif @@ -1020,17 +1089,14 @@ protected: #if DISTRHO_PLUGIN_WANT_STATE /** - Set the state key and default value of @a index.@n + Initialize the state @a index.@n This function will be called once, shortly after the plugin is created.@n Must be implemented by your plugin class only if DISTRHO_PLUGIN_WANT_STATE is enabled. */ - virtual void initState(uint32_t index, String& stateKey, String& defaultStateValue); + virtual void initState(uint32_t index, State& state); - /** - TODO API under construction, should likely be put in a new State struct with key, name defValue and hints - Returns StateHints mask. - */ - virtual uint32_t getStateHints(uint32_t index); + DISTRHO_DEPRECATED_BY("getStateHints(uint32_t,State&)") + virtual void initState(uint32_t, String&, String&) {} #endif #if DISTRHO_PLUGIN_WANT_STATEFILES diff --git a/distrho/src/DistrhoPlugin.cpp b/distrho/src/DistrhoPlugin.cpp index 58db96aa..b0fa0ae4 100644 --- a/distrho/src/DistrhoPlugin.cpp +++ b/distrho/src/DistrhoPlugin.cpp @@ -72,9 +72,8 @@ Plugin::Plugin(uint32_t parameterCount, uint32_t programCount, uint32_t stateCou if (stateCount > 0) { #if DISTRHO_PLUGIN_WANT_STATE - pData->stateCount = stateCount; - pData->stateKeys = new String[stateCount]; - pData->stateDefValues = new String[stateCount]; + pData->stateCount = stateCount; + pData->states = new State[stateCount]; #else d_stderr2("DPF warning: Plugins with state must define `DISTRHO_PLUGIN_WANT_STATE` to 1"); DPF_ABORT @@ -185,27 +184,11 @@ void Plugin::initProgramName(uint32_t, String&) {} #endif #if DISTRHO_PLUGIN_WANT_STATE -void Plugin::initState(uint32_t, String&, String&) {} -#endif - -/* ------------------------------------------------------------------------------------------------------------ - * Init */ - -float Plugin::getParameterValue(uint32_t) const { return 0.0f; } -void Plugin::setParameterValue(uint32_t, float) {} - -#if DISTRHO_PLUGIN_WANT_PROGRAMS -void Plugin::loadProgram(uint32_t) {} -#endif - -#if DISTRHO_PLUGIN_WANT_FULL_STATE -String Plugin::getState(const char*) const { return String(); } -#endif - -#if DISTRHO_PLUGIN_WANT_STATE -uint32_t Plugin::getStateHints(const uint32_t index) +void Plugin::initState(const uint32_t index, State& state) { - #if DISTRHO_PLUGIN_WANT_STATEFILES + uint hints = 0x0; + String stateKey, defaultStateValue; + #if defined(__clang__) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" @@ -213,23 +196,39 @@ uint32_t Plugin::getStateHints(const uint32_t index) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" #endif + initState(index, stateKey, defaultStateValue); + #if DISTRHO_PLUGIN_WANT_STATEFILES if (isStateFile(index)) + hints = kStateIsFilenamePath; + #endif #if defined(__clang__) #pragma clang diagnostic pop #elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) #pragma GCC diagnostic pop #endif - return kStateIsFilenamePath; - #endif - return 0x0; - - #if !DISTRHO_PLUGIN_WANT_STATEFILES - // unused - (void)index; - #endif + state.hints = hints; + state.key = stateKey; + state.label = stateKey; + state.defaultValue = defaultStateValue; } +#endif + +/* ------------------------------------------------------------------------------------------------------------ + * Init */ + +float Plugin::getParameterValue(uint32_t) const { return 0.0f; } +void Plugin::setParameterValue(uint32_t, float) {} +#if DISTRHO_PLUGIN_WANT_PROGRAMS +void Plugin::loadProgram(uint32_t) {} +#endif + +#if DISTRHO_PLUGIN_WANT_FULL_STATE +String Plugin::getState(const char*) const { return String(); } +#endif + +#if DISTRHO_PLUGIN_WANT_STATE void Plugin::setState(const char*, const char*) {} #endif diff --git a/distrho/src/DistrhoPluginInternal.hpp b/distrho/src/DistrhoPluginInternal.hpp index 5a229d11..00fed5d6 100644 --- a/distrho/src/DistrhoPluginInternal.hpp +++ b/distrho/src/DistrhoPluginInternal.hpp @@ -112,8 +112,7 @@ struct Plugin::PrivateData { #if DISTRHO_PLUGIN_WANT_STATE uint32_t stateCount; - String* stateKeys; - String* stateDefValues; + State* states; #endif #if DISTRHO_PLUGIN_WANT_LATENCY @@ -152,8 +151,7 @@ struct Plugin::PrivateData { #endif #if DISTRHO_PLUGIN_WANT_STATE stateCount(0), - stateKeys(nullptr), - stateDefValues(nullptr), + states(nullptr), #endif #if DISTRHO_PLUGIN_WANT_LATENCY latency(0), @@ -221,16 +219,10 @@ struct Plugin::PrivateData { #endif #if DISTRHO_PLUGIN_WANT_STATE - if (stateKeys != nullptr) + if (states != nullptr) { - delete[] stateKeys; - stateKeys = nullptr; - } - - if (stateDefValues != nullptr) - { - delete[] stateDefValues; - stateDefValues = nullptr; + delete[] states; + states = nullptr; } #endif @@ -418,7 +410,7 @@ public: #if DISTRHO_PLUGIN_WANT_STATE for (uint32_t i=0, count=fData->stateCount; i < count; ++i) - fPlugin->initState(i, fData->stateKeys[i], fData->stateDefValues[i]); + fPlugin->initState(i, fData->states[i]); #endif fData->callbacksPtr = callbacksPtr; @@ -747,25 +739,39 @@ public: { DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr && index < fData->stateCount, 0x0); - return fPlugin->getStateHints(index); + return fData->states[index].hints; } const String& getStateKey(const uint32_t index) const noexcept { DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr && index < fData->stateCount, sFallbackString); - return fData->stateKeys[index]; + return fData->states[index].key; } const String& getStateDefaultValue(const uint32_t index) const noexcept { DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr && index < fData->stateCount, sFallbackString); - return fData->stateDefValues[index]; + return fData->states[index].defaultValue; + } + + const String& getStateLabel(const uint32_t index) const noexcept + { + DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr && index < fData->stateCount, sFallbackString); + + return fData->states[index].label; + } + + const String& getStateDescription(const uint32_t index) const noexcept + { + DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr && index < fData->stateCount, sFallbackString); + + return fData->states[index].description; } # if DISTRHO_PLUGIN_WANT_FULL_STATE - String getState(const char* key) const + String getStateValue(const char* const key) const { DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr, sFallbackString); DISTRHO_SAFE_ASSERT_RETURN(key != nullptr && key[0] != '\0', sFallbackString); @@ -790,7 +796,7 @@ public: for (uint32_t i=0; i < fData->stateCount; ++i) { - if (fData->stateKeys[i] == key) + if (fData->states[i].key == key) return true; } diff --git a/distrho/src/DistrhoPluginLV2.cpp b/distrho/src/DistrhoPluginLV2.cpp index 1ba24631..3d27fb59 100644 --- a/distrho/src/DistrhoPluginLV2.cpp +++ b/distrho/src/DistrhoPluginLV2.cpp @@ -706,7 +706,7 @@ public: const uint32_t hints = fPlugin.getStateHints(i); #if ! DISTRHO_PLUGIN_HAS_UI - if ((hints & kStateIsHostVisible) == 0x0) + if ((hints & kStateIsHostReadable) == 0x0) { fNeededUiSends[i] = false; continue; @@ -727,7 +727,7 @@ public: // set msg size uint32_t msgSize; - if (hints & kStateIsHostVisible) + if (hints & kStateIsHostReadable) { // object, prop key, prop urid, value key, value msgSize = sizeof(LV2_Atom_Object) @@ -753,7 +753,7 @@ public: aev = (LV2_Atom_Event*)(LV2_ATOM_CONTENTS(LV2_Atom_Sequence, fEventsOutData.port) + fEventsOutData.offset); aev->time.frames = 0; - if (hints & kStateIsHostVisible) + if (hints & kStateIsHostReadable) { uint8_t* const msgBuf = (uint8_t*)&aev->body; LV2_Atom_Forge atomForge = fAtomForge; @@ -894,7 +894,7 @@ public: for (StringToStringMap::const_iterator cit=fStateMap.begin(), cite=fStateMap.end(); cit != cite; ++cit) { const String& key = cit->first; - fStateMap[key] = fPlugin.getState(key); + fStateMap[key] = fPlugin.getStateValue(key); } # endif } @@ -910,7 +910,7 @@ public: for (StringToStringMap::const_iterator cit=fStateMap.begin(), cite=fStateMap.end(); cit != cite; ++cit) { const String& key = cit->first; - fStateMap[key] = fPlugin.getState(key); + fStateMap[key] = fPlugin.getStateValue(key); } # endif @@ -930,7 +930,9 @@ public: const String& value(cit->second); - if (const uint32_t hints = fPlugin.getStateHints(i)) + const uint32_t hints = fPlugin.getStateHints(i); + + if (hints & kStateIsHostReadable) { lv2key = DISTRHO_PLUGIN_URI "#"; urid = (hints & kStateIsFilenamePath) == kStateIsFilenamePath @@ -970,7 +972,9 @@ public: { const String& key(fPlugin.getStateKey(i)); - if (const uint32_t hints = fPlugin.getStateHints(i)) + const uint32_t hints = fPlugin.getStateHints(i); + + if (hints & kStateIsHostReadable) { lv2key = DISTRHO_PLUGIN_URI "#"; urid = (hints & kStateIsFilenamePath) == kStateIsFilenamePath diff --git a/distrho/src/DistrhoPluginLV2export.cpp b/distrho/src/DistrhoPluginLV2export.cpp index 9cf62c47..9a8f74e4 100644 --- a/distrho/src/DistrhoPluginLV2export.cpp +++ b/distrho/src/DistrhoPluginLV2export.cpp @@ -376,13 +376,22 @@ void lv2_generate_ttl(const char* const basename) { const uint32_t hints = plugin.getStateHints(i); - if ((hints & kStateIsHostVisible) == 0x0) + if ((hints & kStateIsHostReadable) == 0x0) continue; - const String& key(plugin.getStateKey(i)); - pluginString += "<" DISTRHO_PLUGIN_URI "#" + key + ">\n"; + pluginString += "<" DISTRHO_PLUGIN_URI "#" + plugin.getStateKey(i) + ">\n"; pluginString += " a lv2:Parameter ;\n"; - pluginString += " rdfs:label \"" + key + "\" ;\n"; + pluginString += " rdfs:label \"" + plugin.getStateLabel(i) + "\" ;\n"; + + const String& comment(plugin.getStateDescription(i)); + + if (comment.isNotEmpty()) + { + if (comment.contains('"') || comment.contains('\n')) + pluginString += " rdfs:comment \"\"\"" + comment + "\"\"\" ;\n"; + else + pluginString += " rdfs:comment \"" + comment + "\" ;\n"; + } if ((hints & kStateIsFilenamePath) == kStateIsFilenamePath) pluginString += " rdfs:range atom:Path .\n\n"; @@ -414,11 +423,16 @@ void lv2_generate_ttl(const char* const basename) { for (uint32_t i=0, count=plugin.getStateCount(); i < count; ++i) { - if ((plugin.getStateHints(i) & kStateIsHostVisible) == 0x0) + const uint32_t hints = plugin.getStateHints(i); + + if ((hints & kStateIsHostReadable) == 0x0) continue; const String& key(plugin.getStateKey(i)); - pluginString += " patch:writable <" DISTRHO_PLUGIN_URI "#" + key + ">;\n"; + pluginString += " patch:readable <" DISTRHO_PLUGIN_URI "#" + key + ">;\n"; + + if ((hints & kStateIsHostWritable) == kStateIsHostWritable) + pluginString += " patch:writable <" DISTRHO_PLUGIN_URI "#" + key + ">;\n"; } pluginString += "\n"; } @@ -1274,7 +1288,7 @@ void lv2_generate_ttl(const char* const basename) for (uint32_t j=0; j"; diff --git a/distrho/src/DistrhoPluginVST2.cpp b/distrho/src/DistrhoPluginVST2.cpp index 8e5bbad7..95429c7c 100644 --- a/distrho/src/DistrhoPluginVST2.cpp +++ b/distrho/src/DistrhoPluginVST2.cpp @@ -737,7 +737,7 @@ public: for (StringMap::const_iterator cit=fStateMap.begin(), cite=fStateMap.end(); cit != cite; ++cit) { const String& key = cit->first; - fStateMap[key] = fPlugin.getState(key); + fStateMap[key] = fPlugin.getStateValue(key); } # endif @@ -813,7 +813,7 @@ public: for (StringMap::const_iterator cit=fStateMap.begin(), cite=fStateMap.end(); cit != cite; ++cit) { const String& key = cit->first; - fStateMap[key] = fPlugin.getState(key); + fStateMap[key] = fPlugin.getStateValue(key); } # endif diff --git a/distrho/src/DistrhoPluginVST3.cpp b/distrho/src/DistrhoPluginVST3.cpp index a4355191..9abeb858 100644 --- a/distrho/src/DistrhoPluginVST3.cpp +++ b/distrho/src/DistrhoPluginVST3.cpp @@ -910,7 +910,7 @@ public: for (StringMap::const_iterator cit=fStateMap.begin(), cite=fStateMap.end(); cit != cite; ++cit) { const String& key = cit->first; - fStateMap[key] = fPlugin.getState(key); + fStateMap[key] = fPlugin.getStateValue(key); } #endif @@ -1924,7 +1924,7 @@ public: for (StringMap::const_iterator cit=fStateMap.begin(), cite=fStateMap.end(); cit != cite; ++cit) { const String& key = cit->first; - fStateMap[key] = fPlugin.getState(key); + fStateMap[key] = fPlugin.getStateValue(key); } #endif diff --git a/examples/FileHandling/FileHandlingPlugin.cpp b/examples/FileHandling/FileHandlingPlugin.cpp index 0e1f28f6..196bc13f 100644 --- a/examples/FileHandling/FileHandlingPlugin.cpp +++ b/examples/FileHandling/FileHandlingPlugin.cpp @@ -124,42 +124,29 @@ protected: } /** - Set the state key and default value of @a index.@n - This function will be called once, shortly after the plugin is created.@n - Must be implemented by your plugin class only if DISTRHO_PLUGIN_WANT_STATE is enabled. + Initialize the state @a index.@n + This function will be called once, shortly after the plugin is created. */ - void initState(uint32_t index, String& stateKey, String& defaultStateValue) override + void initState(uint32_t index, State& state) override { switch (index) { case kStateFile1: - stateKey = "file1"; + state.key = "file1"; + state.label = "File 1"; break; case kStateFile2: - stateKey = "file2"; + state.key = "file2"; + state.label = "File 2"; break; case kStateFile3: - stateKey = "file3"; + state.key = "file3"; + state.label = "File 3"; break; } - defaultStateValue = ""; - } - - /** - TODO API under construction - */ - uint32_t getStateHints(uint32_t index) override - { - switch (index) - { - case kStateFile1: - case kStateFile2: - case kStateFile3: - return kStateIsFilenamePath; - } - - return 0x0; + state.hints = kStateIsFilenamePath; + state.defaultValue = ""; } /* -------------------------------------------------------------------------------------------------------- diff --git a/examples/States/ExamplePluginStates.cpp b/examples/States/ExamplePluginStates.cpp index df52e07b..47a8e4dc 100644 --- a/examples/States/ExamplePluginStates.cpp +++ b/examples/States/ExamplePluginStates.cpp @@ -127,43 +127,54 @@ The plugin will be treated as an effect, but it will not change the host audio." } /** - Set the state key and default value of @a index. - This function will be called once, shortly after the plugin is created. + Initialize the state @a index.@n + This function will be called once, shortly after the plugin is created.@n + Must be implemented by your plugin class only if DISTRHO_PLUGIN_WANT_STATE is enabled. */ - void initState(uint32_t index, String& stateKey, String& defaultStateValue) override + void initState(uint32_t index, State& state) override { switch (index) { case 0: - stateKey = "top-left"; + state.key = "top-left"; + state.label = "Top Left"; break; case 1: - stateKey = "top-center"; + state.key = "top-center"; + state.label = "Top Center"; break; case 2: - stateKey = "top-right"; + state.key = "top-right"; + state.label = "Top Right"; break; case 3: - stateKey = "middle-left"; + state.key = "middle-left"; + state.label = "Middle Left"; break; case 4: - stateKey = "middle-center"; + state.key = "middle-center"; + state.label = "Middle Center"; break; case 5: - stateKey = "middle-right"; + state.key = "middle-right"; + state.label = "Middle Right"; break; case 6: - stateKey = "bottom-left"; + state.key = "bottom-left"; + state.label = "Bottom Left"; break; case 7: - stateKey = "bottom-center"; + state.key = "bottom-center"; + state.label = "Bottom Center"; break; case 8: - stateKey = "bottom-right"; + state.key = "bottom-right"; + state.label = "Bottom Right"; break; } - defaultStateValue = "false"; + state.hints = kStateIsHostWritable; + state.defaultValue = "false"; } /* --------------------------------------------------------------------------------------------------------