diff --git a/distrho/DistrhoPlugin.hpp b/distrho/DistrhoPlugin.hpp index 80de8c8c..3bd4b9e2 100644 --- a/distrho/DistrhoPlugin.hpp +++ b/distrho/DistrhoPlugin.hpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2021 Filipe Coelho + * Copyright (C) 2012-2022 Filipe Coelho * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -135,6 +135,29 @@ static const uint32_t kParameterIsTrigger = 0x20 | kParameterIsBoolean; /** @} */ +/* ------------------------------------------------------------------------------------------------------------ + * State Hints */ + +/** + @defgroup StateHints State Hints + + Various state hints. + @see Plugin::getStateHints + @{ + */ + +/** + State is available for the host to see and change. + */ +static const uint32_t kStateIsHostVisible = 0x01; + +/** + State is a filename path. + */ +static const uint32_t kStateIsFilenamePath = 0x02 | kStateIsHostVisible; + +/** @} */ + /* ------------------------------------------------------------------------------------------------------------ * Base Plugin structs */ @@ -993,13 +1016,17 @@ protected: 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); -#endif -#if DISTRHO_PLUGIN_WANT_STATEFILES /** - TODO API under construction + TODO API under construction, should likely be put in a new State struct with key, name defValue and hints + Returns StateHints mask. */ - virtual bool isStateFile(uint32_t index) = 0; + virtual uint32_t getStateHints(uint32_t index); +#endif + +#if DISTRHO_PLUGIN_WANT_STATEFILES + DISTRHO_DEPRECATED_BY("getStateHints") + virtual bool isStateFile(uint32_t index) { return false; } #endif /* -------------------------------------------------------------------------------------------------------- diff --git a/distrho/src/DistrhoPlugin.cpp b/distrho/src/DistrhoPlugin.cpp index 2242b16c..e9f9810c 100644 --- a/distrho/src/DistrhoPlugin.cpp +++ b/distrho/src/DistrhoPlugin.cpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2021 Filipe Coelho + * Copyright (C) 2012-2022 Filipe Coelho * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -200,6 +200,16 @@ String Plugin::getState(const char*) const { return String(); } #endif #if DISTRHO_PLUGIN_WANT_STATE +uint32_t Plugin::getStateHints(const uint32_t index) +{ + #if DISTRHO_PLUGIN_WANT_STATEFILES + if isStateFile(index) + return kStateIsFilenamePath; + #endif + + return 0x0; +} + void Plugin::setState(const char*, const char*) {} #endif diff --git a/distrho/src/DistrhoPluginInternal.hpp b/distrho/src/DistrhoPluginInternal.hpp index 017eb234..52185510 100644 --- a/distrho/src/DistrhoPluginInternal.hpp +++ b/distrho/src/DistrhoPluginInternal.hpp @@ -727,6 +727,13 @@ public: return fData->stateCount; } + uint32_t getStateHints(const uint32_t index) const noexcept + { + DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr && index < fData->stateCount, 0x0); + + return fPlugin->getStateHints(index); + } + const String& getStateKey(const uint32_t index) const noexcept { DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr && index < fData->stateCount, sFallbackString); @@ -741,15 +748,6 @@ public: return fData->stateDefValues[index]; } -# if DISTRHO_PLUGIN_WANT_STATEFILES - bool isStateFile(const uint32_t index) const - { - DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr && index < fData->stateCount, false); - - return fPlugin->isStateFile(index); - } -# endif - # if DISTRHO_PLUGIN_WANT_FULL_STATE String getState(const char* key) const { diff --git a/distrho/src/DistrhoPluginLV2.cpp b/distrho/src/DistrhoPluginLV2.cpp index 60378f2a..92ba3309 100644 --- a/distrho/src/DistrhoPluginLV2.cpp +++ b/distrho/src/DistrhoPluginLV2.cpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2021 Filipe Coelho + * Copyright (C) 2012-2022 Filipe Coelho * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -17,6 +17,7 @@ #include "DistrhoPluginInternal.hpp" #include "lv2/atom.h" +#include "lv2/atom-forge.h" #include "lv2/atom-util.h" #include "lv2/buf-size.h" #include "lv2/data-access.h" @@ -47,8 +48,8 @@ # define DISTRHO_PLUGIN_LV2_STATE_PREFIX "urn:distrho:" #endif -#define DISTRHO_LV2_USE_EVENTS_IN (DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_TIMEPOS || (DISTRHO_PLUGIN_WANT_STATE && DISTRHO_PLUGIN_HAS_UI) || DISTRHO_PLUGIN_WANT_STATEFILES) -#define DISTRHO_LV2_USE_EVENTS_OUT (DISTRHO_PLUGIN_WANT_MIDI_OUTPUT || (DISTRHO_PLUGIN_WANT_STATE && DISTRHO_PLUGIN_HAS_UI)) +#define DISTRHO_LV2_USE_EVENTS_IN (DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_TIMEPOS || DISTRHO_PLUGIN_WANT_STATE) +#define DISTRHO_LV2_USE_EVENTS_OUT (DISTRHO_PLUGIN_WANT_MIDI_OUTPUT || DISTRHO_PLUGIN_WANT_STATE) START_NAMESPACE_DISTRHO @@ -126,29 +127,29 @@ public: #endif #if DISTRHO_PLUGIN_WANT_STATE + std::memset(&fAtomForge, 0, sizeof(fAtomForge)); + lv2_atom_forge_init(&fAtomForge, uridMap); + if (const uint32_t count = fPlugin.getStateCount()) { + fUrids = new LV2_URID[count]; fNeededUiSends = new bool[count]; for (uint32_t i=0; i < count; ++i) { fNeededUiSends[i] = false; - const String& dkey(fPlugin.getStateKey(i)); - fStateMap[dkey] = fPlugin.getStateDefaultValue(i); + const String& statekey(fPlugin.getStateKey(i)); + fStateMap[statekey] = fPlugin.getStateDefaultValue(i); -# if DISTRHO_PLUGIN_WANT_STATEFILES - if (fPlugin.isStateFile(i)) - { - const String dpf_lv2_key(DISTRHO_PLUGIN_URI "#" + dkey); - const LV2_URID urid = uridMap->map(uridMap->handle, dpf_lv2_key.buffer()); - fUridStateFileMap[urid] = dkey; - } -# endif + const String lv2key(DISTRHO_PLUGIN_URI "#" + statekey); + const LV2_URID urid = fUrids[i] = uridMap->map(uridMap->handle, lv2key.buffer()); + fUridStateMap[urid] = statekey; } } else { + fUrids = nullptr; fNeededUiSends = nullptr; } #else @@ -544,13 +545,14 @@ public: } #endif - // check for messages from UI or files -#if DISTRHO_PLUGIN_WANT_STATE && (DISTRHO_PLUGIN_HAS_UI || DISTRHO_PLUGIN_WANT_STATEFILES) + // check for messages from UI or host +#if DISTRHO_PLUGIN_WANT_STATE LV2_ATOM_SEQUENCE_FOREACH(fPortEventsIn, event) { if (event == nullptr) break; + #if DISTRHO_PLUGIN_HAS_UI if (event->body.type == fURIDs.dpfKeyValue) { const void* const data = (const void*)(event + 1); @@ -567,8 +569,9 @@ public: fWorker->schedule_work(fWorker->handle, sizeof(LV2_Atom)+event->body.size, &event->body); } } -# if DISTRHO_PLUGIN_WANT_STATEFILES - else if (event->body.type == fURIDs.atomObject && fWorker != nullptr) + else + #endif + if (event->body.type == fURIDs.atomObject && fWorker != nullptr) { const LV2_Atom_Object* const object = (const LV2_Atom_Object*)&event->body; @@ -576,13 +579,12 @@ public: const LV2_Atom* value = nullptr; lv2_atom_object_get(object, fURIDs.patchProperty, &property, fURIDs.patchValue, &value, nullptr); - if (property != nullptr && property->type == fURIDs.atomURID && - value != nullptr && value->type == fURIDs.atomPath) + if (property != nullptr && property->type == fURIDs.atomURID && value != nullptr && + (value->type == fURIDs.atomPath || value->type == fURIDs.atomString)) { fWorker->schedule_work(fWorker->handle, sizeof(LV2_Atom)+event->body.size, &event->body); } } -# endif } #endif @@ -681,7 +683,7 @@ public: updateParameterOutputsAndTriggers(); -#if DISTRHO_PLUGIN_WANT_STATE && DISTRHO_PLUGIN_HAS_UI +#if DISTRHO_PLUGIN_WANT_STATE fEventsOutData.initIfNeeded(fURIDs.atomSequence); LV2_Atom_Event* aev; @@ -692,6 +694,16 @@ public: if (! fNeededUiSends[i]) continue; + const uint32_t hints = fPlugin.getStateHints(i); + + #if ! DISTRHO_PLUGIN_HAS_UI + if ((hints & kStateIsHostVisible) == 0x0) + { + fNeededUiSends[i] = false; + continue; + } + #endif + const String& curKey(fPlugin.getStateKey(i)); for (StringToStringMap::const_iterator cit=fStateMap.begin(), cite=fStateMap.end(); cit != cite; ++cit) @@ -703,8 +715,19 @@ public: const String& value(cit->second); - // set msg size (key + value + separator + 2x null terminator) - const uint32_t msgSize = static_cast(key.length()+value.length())+3U; + // set msg size + uint32_t msgSize; + + if (hints & kStateIsHostVisible) + { + // object, prop key, prop urid, value key, value + msgSize = sizeof(LV2_Atom_Object) + sizeof(LV2_URID) * 3 + value.length() + 1; + } + else + { + // key + value + 2x null terminator + separator + msgSize = static_cast(key.length()+value.length())+3U; + } if (sizeof(LV2_Atom_Event) + msgSize > capacity - fEventsOutData.offset) { @@ -715,18 +738,38 @@ public: // put data aev = (LV2_Atom_Event*)(LV2_ATOM_CONTENTS(LV2_Atom_Sequence, fEventsOutData.port) + fEventsOutData.offset); aev->time.frames = 0; - aev->body.type = fURIDs.dpfKeyValue; - aev->body.size = msgSize; uint8_t* const msgBuf = LV2_ATOM_BODY(&aev->body); - std::memset(msgBuf, 0, msgSize); - // write key and value in atom buffer - std::memcpy(msgBuf, key.buffer(), key.length()+1); - std::memcpy(msgBuf+(key.length()+1), value.buffer(), value.length()+1); + if (hints & kStateIsHostVisible) + { + LV2_Atom_Forge atomForge = fAtomForge; + lv2_atom_forge_set_buffer(&atomForge, msgBuf, msgSize); - fEventsOutData.growBy(lv2_atom_pad_size(sizeof(LV2_Atom_Event) + msgSize)); + LV2_Atom_Forge_Frame forgeFrame; + lv2_atom_forge_object(&atomForge, &forgeFrame, 0, fURIDs.patchSet); + + lv2_atom_forge_key(&atomForge, fURIDs.patchProperty); + lv2_atom_forge_urid(&atomForge, fUrids[i]); + + lv2_atom_forge_key(&atomForge, fURIDs.patchValue); + lv2_atom_forge_path(&atomForge, value.buffer(), static_cast(value.length()+1)); + + lv2_atom_forge_pop(&atomForge, &forgeFrame); + } + else + { + aev->body.type = fURIDs.dpfKeyValue; + aev->body.size = msgSize; + std::memset(msgBuf, 0, msgSize); + + // write key and value in atom buffer + std::memcpy(msgBuf, key.buffer(), key.length()+1); + std::memcpy(msgBuf+(key.length()+1), value.buffer(), value.length()+1); + } + + fEventsOutData.growBy(lv2_atom_pad_size(sizeof(LV2_Atom_Event) + msgSize)); fNeededUiSends[i] = false; break; } @@ -854,7 +897,7 @@ public: } # endif - String dpf_lv2_key; + String lv2key; LV2_URID urid; for (uint32_t i=0, count=fPlugin.getStateCount(); i < count; ++i) @@ -870,24 +913,24 @@ public: const String& value(cit->second); -# if DISTRHO_PLUGIN_WANT_STATEFILES - if (fPlugin.isStateFile(i)) + if (const uint32_t hints = fPlugin.getStateHints(i)) { - dpf_lv2_key = DISTRHO_PLUGIN_URI "#"; - urid = fURIDs.atomPath; + lv2key = DISTRHO_PLUGIN_URI "#"; + urid = (hints & kStateIsFilenamePath) == kStateIsFilenamePath + ? fURIDs.atomPath + : fURIDs.atomString; } else -# endif { - dpf_lv2_key = DISTRHO_PLUGIN_LV2_STATE_PREFIX; + lv2key = DISTRHO_PLUGIN_LV2_STATE_PREFIX; urid = fURIDs.atomString; } - dpf_lv2_key += key; + lv2key += key; // some hosts need +1 for the null terminator, even though the type is string store(handle, - fUridMap->map(fUridMap->handle, dpf_lv2_key.buffer()), + fUridMap->map(fUridMap->handle, lv2key.buffer()), value.buffer(), value.length()+1, urid, @@ -903,33 +946,33 @@ public: size_t size; uint32_t type, flags; - String dpf_lv2_key; + String lv2key; LV2_URID urid; for (uint32_t i=0, count=fPlugin.getStateCount(); i < count; ++i) { const String& key(fPlugin.getStateKey(i)); -# if DISTRHO_PLUGIN_WANT_STATEFILES - if (fPlugin.isStateFile(i)) + if (const uint32_t hints = fPlugin.getStateHints(i)) { - dpf_lv2_key = DISTRHO_PLUGIN_URI "#"; - urid = fURIDs.atomPath; + lv2key = DISTRHO_PLUGIN_URI "#"; + urid = (hints & kStateIsFilenamePath) == kStateIsFilenamePath + ? fURIDs.atomPath + : fURIDs.atomString; } else -# endif { - dpf_lv2_key = DISTRHO_PLUGIN_LV2_STATE_PREFIX; + lv2key = DISTRHO_PLUGIN_LV2_STATE_PREFIX; urid = fURIDs.atomString; } - dpf_lv2_key += key; + lv2key += key; size = 0; type = 0; flags = LV2_STATE_IS_POD|LV2_STATE_IS_PORTABLE; const void* data = retrieve(handle, - fUridMap->map(fUridMap->handle, dpf_lv2_key.buffer()), + fUridMap->map(fUridMap->handle, lv2key.buffer()), &size, &type, &flags); if (data == nullptr || size == 0) @@ -967,7 +1010,6 @@ public: return LV2_WORKER_SUCCESS; } -# if DISTRHO_PLUGIN_WANT_STATEFILES if (eventBody->type == fURIDs.atomObject) { const LV2_Atom_Object* const object = (const LV2_Atom_Object*)eventBody; @@ -978,7 +1020,8 @@ public: DISTRHO_SAFE_ASSERT_RETURN(property != nullptr, LV2_WORKER_ERR_UNKNOWN); DISTRHO_SAFE_ASSERT_RETURN(property->type == fURIDs.atomURID, LV2_WORKER_ERR_UNKNOWN); DISTRHO_SAFE_ASSERT_RETURN(value != nullptr, LV2_WORKER_ERR_UNKNOWN); - DISTRHO_SAFE_ASSERT_RETURN(value->type == fURIDs.atomPath, LV2_WORKER_ERR_UNKNOWN); + DISTRHO_SAFE_ASSERT_RETURN(value->type == fURIDs.atomPath || + value->type == fURIDs.atomString, LV2_WORKER_ERR_UNKNOWN); const LV2_URID urid = ((const LV2_Atom_URID*)property)->body; const char* const filename = (const char*)(value + 1); @@ -986,8 +1029,8 @@ public: String key; try { - key = fUridStateFileMap[urid]; - } DISTRHO_SAFE_EXCEPTION_RETURN("lv2_work fUridStateFileMap[urid]", LV2_WORKER_ERR_UNKNOWN); + key = fUridStateMap[urid]; + } DISTRHO_SAFE_EXCEPTION_RETURN("lv2_work fUridStateMap[urid]", LV2_WORKER_ERR_UNKNOWN); setState(key, filename); @@ -1002,7 +1045,6 @@ public: return LV2_WORKER_SUCCESS; } -# endif return LV2_WORKER_ERR_UNKNOWN; } @@ -1136,6 +1178,7 @@ private: LV2_URID atomURID; LV2_URID dpfKeyValue; LV2_URID midiEvent; + LV2_URID patchSet; LV2_URID patchProperty; LV2_URID patchValue; LV2_URID timePosition; @@ -1162,6 +1205,7 @@ private: atomURID(map(LV2_ATOM__URID)), dpfKeyValue(map(DISTRHO_PLUGIN_LV2_STATE_PREFIX "KeyValueState")), midiEvent(map(LV2_MIDI__MidiEvent)), + patchSet(map(LV2_PATCH__Set)), patchProperty(map(LV2_PATCH__property)), patchValue(map(LV2_PATCH__value)), timePosition(map(LV2_TIME__Position)), @@ -1188,7 +1232,10 @@ private: const LV2_Worker_Schedule* const fWorker; #if DISTRHO_PLUGIN_WANT_STATE + LV2_Atom_Forge fAtomForge; StringToStringMap fStateMap; + UridToStringMap fUridStateMap; + LV2_URID* fUrids; bool* fNeededUiSends; void setState(const char* const key, const char* const newValue) @@ -1213,10 +1260,6 @@ private: d_stderr("Failed to find plugin state with key \"%s\"", key); } - -# if DISTRHO_PLUGIN_WANT_STATEFILES - UridToStringMap fUridStateFileMap; -# endif #endif void updateParameterOutputsAndTriggers() diff --git a/distrho/src/DistrhoPluginLV2export.cpp b/distrho/src/DistrhoPluginLV2export.cpp index 202b6335..15623e1d 100644 --- a/distrho/src/DistrhoPluginLV2export.cpp +++ b/distrho/src/DistrhoPluginLV2export.cpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2021 Filipe Coelho + * Copyright (C) 2012-2022 Filipe Coelho * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -79,8 +79,8 @@ # define DISTRHO_LV2_UI_TYPE "UI" #endif -#define DISTRHO_LV2_USE_EVENTS_IN (DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_TIMEPOS || (DISTRHO_PLUGIN_WANT_STATE && DISTRHO_PLUGIN_HAS_UI) || DISTRHO_PLUGIN_WANT_STATEFILES) -#define DISTRHO_LV2_USE_EVENTS_OUT (DISTRHO_PLUGIN_WANT_MIDI_OUTPUT || (DISTRHO_PLUGIN_WANT_STATE && DISTRHO_PLUGIN_HAS_UI)) +#define DISTRHO_LV2_USE_EVENTS_IN (DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_TIMEPOS || DISTRHO_PLUGIN_WANT_STATE) +#define DISTRHO_LV2_USE_EVENTS_OUT (DISTRHO_PLUGIN_WANT_MIDI_OUTPUT || DISTRHO_PLUGIN_WANT_STATE) #define DISTRHO_BYPASS_PARAMETER_NAME "lv2_enabled" @@ -152,9 +152,7 @@ static const char* const lv2ManifestUiOptionalFeatures[] = "ui:parent", "ui:touch", #endif -#if DISTRHO_PLUGIN_WANT_STATEFILES "ui:requestValue", -#endif nullptr }; @@ -370,23 +368,28 @@ void lv2_generate_ttl(const char* const basename) pluginString += "@prefix unit: <" LV2_UNITS_PREFIX "> .\n"; pluginString += "\n"; -#if DISTRHO_PLUGIN_WANT_STATEFILES // define writable states as lv2 parameters - bool hasStateFiles = false; + bool hasHostVisibleState = false; for (uint32_t i=0, count=plugin.getStateCount(); i < count; ++i) { - if (! plugin.isStateFile(i)) + const uint32_t hints = plugin.getStateHints(i); + + if ((hints & kStateIsHostVisible) == 0x0) continue; const String& key(plugin.getStateKey(i)); pluginString += "<" DISTRHO_PLUGIN_URI "#" + key + ">\n"; pluginString += " a lv2:Parameter ;\n"; pluginString += " rdfs:label \"" + key + "\" ;\n"; - pluginString += " rdfs:range atom:Path .\n\n"; - hasStateFiles = true; + + if ((hints & kStateIsFilenamePath) == kStateIsFilenamePath) + pluginString += " rdfs:range atom:Path .\n\n"; + else + pluginString += " rdfs:range atom:String .\n\n"; + + hasHostVisibleState = true; } -#endif // plugin pluginString += "<" DISTRHO_PLUGIN_URI ">\n"; @@ -404,12 +407,11 @@ void lv2_generate_ttl(const char* const basename) addAttribute(pluginString, "lv2:requiredFeature", lv2ManifestPluginRequiredFeatures, 4); addAttribute(pluginString, "opts:supportedOption", lv2ManifestPluginSupportedOptions, 4); -#if DISTRHO_PLUGIN_WANT_STATEFILES - if (hasStateFiles) + if (hasHostVisibleState) { for (uint32_t i=0, count=plugin.getStateCount(); i < count; ++i) { - if (! plugin.isStateFile(i)) + if ((plugin.getStateHints(i) & kStateIsHostVisible) == 0x0) continue; const String& key(plugin.getStateKey(i)); @@ -417,7 +419,6 @@ void lv2_generate_ttl(const char* const basename) } pluginString += "\n"; } -#endif // UI #if DISTRHO_PLUGIN_HAS_UI diff --git a/distrho/src/lv2/atom-forge.h b/distrho/src/lv2/atom-forge.h index d803d23f..d808f0d9 100644 --- a/distrho/src/lv2/atom-forge.h +++ b/distrho/src/lv2/atom-forge.h @@ -125,7 +125,7 @@ lv2_atom_forge_set_buffer(LV2_Atom_Forge* forge, uint8_t* buf, size_t size); not held. */ static inline void -lv2_atom_forge_init(LV2_Atom_Forge* forge, LV2_URID_Map* map) +lv2_atom_forge_init(LV2_Atom_Forge* forge, const LV2_URID_Map* map) { #if defined(__clang__) # pragma clang diagnostic push