Signed-off-by: falkTX <falktx@falktx.com>pull/375/head
@@ -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 | |||
@@ -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 | |||
@@ -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; | |||
} | |||
@@ -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 | |||
@@ -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<numStates; ++j) | |||
{ | |||
const String key = plugin.getStateKey(j); | |||
const String value = plugin.getState(key); | |||
const String value = plugin.getStateValue(key); | |||
presetString += " <" DISTRHO_PLUGIN_LV2_STATE_PREFIX + key + ">"; | |||
@@ -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 | |||
@@ -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 | |||
@@ -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 = ""; | |||
} | |||
/* -------------------------------------------------------------------------------------------------------- | |||
@@ -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"; | |||
} | |||
/* -------------------------------------------------------------------------------------------------------- | |||