commit a4aa2d83c8d51a47402ac0417a365ec0fe36cce3
parent f90cc570dde844f8963bd63b81913990fc30105b
Author: falkTX <falktx@falktx.com>
Date: Mon, 27 Sep 2021 01:07:06 +0100
VST3: full state save/restore support, update TODO items
Signed-off-by: falkTX <falktx@falktx.com>
Diffstat:
3 files changed, 214 insertions(+), 83 deletions(-)
diff --git a/distrho/src/DistrhoPluginVST3.cpp b/distrho/src/DistrhoPluginVST3.cpp
@@ -47,10 +47,7 @@
* - parameter enumeration as lists
* - hide parameter outputs?
* - hide program parameter?
- * - save and restore state
- * - save and restore current program
* - deal with parameter triggers
- * - send current state to UI on request
* - midi cc parameter mapping
* - full MIDI1 encode and decode
* - decode version number (0x102030 -> 1.2.3)
@@ -58,7 +55,7 @@
* - optional audio buses, create dummy buffer of max_block_size length for them
* - routing info, do we care?
* - set sidechain bus name from port group
- * - implement getParameterValueForString
+ * - implement getParameterValueForString (use names from enumeration if available, fallback to std::atof)
* - set factory class_flags
* - set factory sub_categories
* - set factory email (needs new DPF API, useful for LV2 as well)
@@ -692,82 +689,194 @@ public:
}
/* state: we pack pairs of key-value strings each separated by a null/zero byte.
- * states come first, and then parameters. parameters are simply converted to/from strings and floats.
+ * states come first, then current-program and then parameters.
+ * parameters are simply converted to/from strings and floats.
* the parameter symbol is used as the "key", so it is possible to reorder them or even remove and add safely.
- * the number of states must remain constant though.
+ * there are markers for begin and end of state and parameters, so they never conflict.
*/
v3_result setState(v3_bstream** const stream)
{
-#if DISTRHO_PLUGIN_WANT_STATE
- // TODO
-#endif
-#if DISTRHO_PLUGIN_WANT_PROGRAMS
- // TODO
+#if DISTRHO_PLUGIN_HAS_UI
+ const bool connectedToUI = fConnection != nullptr && fConnectedToUI;
#endif
+ const uint32_t paramCount = fPlugin.getParameterCount();
- if (const uint32_t paramCount = fPlugin.getParameterCount())
- {
- char buffer[32], orig;
- String key, value;
- v3_result res;
- bool fillingKey = true;
+ String key, value;
+ bool fillingKey = true; // if filling key or value
+ char queryingType = 'i'; // can be 'n', 's' or 'p' (none, states, parameters)
- // temporarily set locale to "C" while converting floats
- const ScopedSafeLocale ssl;
+ char buffer[512], orig;
+ buffer[sizeof(buffer)-1] = '\xff';
+ v3_result res;
- for (int32_t pos = 0, read;; pos += read)
+ for (int32_t pos = 0, term = 0, read; term == 0; pos += read)
+ {
+ res = v3_cpp_obj(stream)->read(stream, buffer, sizeof(buffer)-1, &read);
+ DISTRHO_SAFE_ASSERT_INT_RETURN(res == V3_OK, res, res);
+ DISTRHO_SAFE_ASSERT_INT_RETURN(read > 0, read, V3_INTERNAL_ERR);
+
+ for (int32_t i = 0; i < read; ++i)
{
- std::memset(buffer, '\xff', sizeof(buffer));
- res = v3_cpp_obj(stream)->read(stream, buffer, sizeof(buffer)-1, &read);
- DISTRHO_SAFE_ASSERT_INT_RETURN(res == V3_OK, res, res);
- DISTRHO_SAFE_ASSERT_INT_RETURN(read > 0, read, V3_INTERNAL_ERR);
+ // found terminator, stop here
+ if (buffer[i] == '\xfe')
+ {
+ term = 1;
+ break;
+ }
+
+ // store character at read position
+ orig = buffer[read];
+
+ // place null character to create valid string
+ buffer[read] = '\0';
+
+ // append to temporary vars
+ if (fillingKey)
+ key += buffer + i;
+ else
+ value += buffer + i;
+
+ // increase buffer offset by length of string
+ i += std::strlen(buffer + i);
+
+ // restore read character
+ buffer[read] = orig;
- for (int32_t i = 0; i < read; ++i)
+ // if buffer offset points to null, we found the end of a string, lets check
+ if (buffer[i] == '\0')
{
- if (buffer[i] == '\0' && pos == 0 && i == 0)
+ // special keys
+ if (key == "__dpf_state_begin__")
+ {
+ DISTRHO_SAFE_ASSERT_INT_RETURN(queryingType == 'i' || queryingType == 'n',
+ queryingType, V3_INTERNAL_ERR);
+ queryingType = 's';
+ key.clear();
+ value.clear();
+ continue;
+ }
+ if (key == "__dpf_state_end__")
+ {
+ DISTRHO_SAFE_ASSERT_INT_RETURN(queryingType == 's', queryingType, V3_INTERNAL_ERR);
+ queryingType = 'n';
+ key.clear();
+ value.clear();
+ continue;
+ }
+ if (key == "__dpf_parameters_begin__")
+ {
+ DISTRHO_SAFE_ASSERT_INT_RETURN(queryingType == 'i' || queryingType == 'n',
+ queryingType, V3_INTERNAL_ERR);
+ queryingType = 'p';
+ key.clear();
+ value.clear();
+ continue;
+ }
+ if (key == "__dpf_parameters_end__")
+ {
+ DISTRHO_SAFE_ASSERT_INT_RETURN(queryingType == 'p', queryingType, V3_INTERNAL_ERR);
+ queryingType = 'x';
+ key.clear();
+ value.clear();
+ continue;
+ }
+
+ // no special key, swap between reading real key and value
+ fillingKey = !fillingKey;
+
+ // if there is no value yet keep reading until we have one (TODO check empty values on purpose)
+ if (value.isEmpty())
continue;
- orig = buffer[read];
- buffer[read] = '\0';
+ if (key == "__dpf_program__")
+ {
+ DISTRHO_SAFE_ASSERT_INT_RETURN(queryingType == 'i', queryingType, V3_INTERNAL_ERR);
+ queryingType = 'n';
+
+ d_stdout("found program '%s'", value.buffer());
- if (fillingKey)
- key += buffer + i;
- else
- value += buffer + i;
+#if DISTRHO_PLUGIN_WANT_PROGRAMS
+ const int program = std::atoi(value.buffer());
+ DISTRHO_SAFE_ASSERT_CONTINUE(program >= 0);
- i += std::strlen(buffer + i);
- buffer[read] = orig;
+ fCurrentProgram = static_cast<uint32_t>(program);
+ fPlugin.loadProgram(fCurrentProgram);
- if (buffer[i] == '\0')
+# if DISTRHO_PLUGIN_HAS_UI
+ if (connectedToUI)
+ {
+ fChangedParameterValues[0] = false;
+ sendParameterChangeToUI(0, fCurrentProgram);
+ }
+# endif
+#endif
+ }
+ else if (queryingType == 's')
{
- fillingKey = !fillingKey;
+ d_stdout("found state '%s' '%s'", key.buffer(), value.buffer());
- if (value.isNotEmpty())
+#if DISTRHO_PLUGIN_WANT_STATE
+ if (fPlugin.wantStateKey(key))
{
- // find parameter with this symbol, and set its value
- for (uint32_t j=0; j<paramCount; ++j)
- {
- if (fPlugin.isParameterOutputOrTrigger(j))
- continue;
- if (fPlugin.getParameterSymbol(j) != key)
- continue;
+ fStateMap[key] = value;
+ fPlugin.setState(key, value);
- fPlugin.setParameterValue(j, std::atof(value.buffer()));
- break;
- }
+# if DISTRHO_PLUGIN_HAS_UI
+ if (connectedToUI)
+ sendStateChangeToUI(key, value);
+# endif
+ }
+#endif
+ }
+ else if (queryingType == 'p')
+ {
+ d_stdout("found parameter '%s' '%s'", key.buffer(), value.buffer());
- key.clear();
- value.clear();
+ // find parameter with this symbol, and set its value
+ for (uint32_t j=0; j < paramCount; ++j)
+ {
+ if (fPlugin.isParameterOutputOrTrigger(j))
+ continue;
+ if (fPlugin.getParameterSymbol(j) != key)
+ continue;
+
+ const float fvalue = fParameterValues[i] = std::atof(value.buffer());
+ fPlugin.setParameterValue(j, fvalue);
+#if DISTRHO_PLUGIN_HAS_UI
+ if (connectedToUI)
+ {
+ // UI parameter updates are handled outside the read loop (after host param restart)
+ fChangedParameterValues[j + fParameterOffset] = true;
+ }
+#endif
+ break;
}
- if (buffer[i+1] == '\0')
- return V3_OK;
}
+
+ key.clear();
+ value.clear();
}
+ }
+ }
- if (buffer[read] == '\0')
- return V3_OK;
+ if (paramCount != 0)
+ {
+ if (fComponentHandler != nullptr)
+ v3_cpp_obj(fComponentHandler)->restart_component(fComponentHandler, V3_RESTART_PARAM_VALUES_CHANGED);
+
+#if DISTRHO_PLUGIN_HAS_UI
+ if (connectedToUI)
+ {
+ for (uint32_t i=0; i < paramCount; ++i)
+ {
+ if (fPlugin.isParameterOutputOrTrigger(i))
+ continue;
+ fChangedParameterValues[i + fParameterOffset] = false;
+ sendParameterChangeToUI(i + fParameterOffset, fParameterValues[i]);
+ }
}
+#endif
}
return V3_OK;
@@ -789,42 +898,54 @@ public:
return v3_cpp_obj(stream)->write(stream, &buffer, 1, &ignored);
}
- String state;
-
#if DISTRHO_PLUGIN_WANT_FULL_STATE
- /*
// Update current state
for (StringMap::const_iterator cit=fStateMap.begin(), cite=fStateMap.end(); cit != cite; ++cit)
{
const String& key = cit->first;
fStateMap[key] = fPlugin.getState(key);
}
- */
#endif
-#if DISTRHO_PLUGIN_WANT_STATE
- /*
- for (StringMap::const_iterator cit=fStateMap.begin(), cite=fStateMap.end(); cit != cite; ++cit)
- {
- const String& key = cit->first;
- const String& value = cit->second;
+ String state;
- // join key and value
- String tmpStr;
- tmpStr = key;
- tmpStr += "\xff";
- tmpStr += value;
+#if DISTRHO_PLUGIN_WANT_PROGRAMS
+ {
+ String tmpStr("__dpf_program__\xff");
+ tmpStr += String(fCurrentProgram);
tmpStr += "\xff";
state += tmpStr;
}
- */
+#endif
+
+#if DISTRHO_PLUGIN_WANT_STATE
+ if (stateCount != 0)
+ {
+ state += "__dpf_state_begin__\xff";
+
+ for (StringMap::const_iterator cit=fStateMap.begin(), cite=fStateMap.end(); cit != cite; ++cit)
+ {
+ const String& key = cit->first;
+ const String& value = cit->second;
+
+ // join key and value
+ String tmpStr;
+ tmpStr = key;
+ tmpStr += "\xff";
+ tmpStr += value;
+ tmpStr += "\xff";
+
+ state += tmpStr;
+ }
+
+ state += "__dpf_state_end__\xff";
+ }
#endif
if (paramCount != 0)
{
- // add another separator
- state += "\xff";
+ state += "__dpf_parameters_begin__\xff";
for (uint32_t i=0; i<paramCount; ++i)
{
@@ -840,8 +961,13 @@ public:
state += tmpStr;
}
+
+ state += "__dpf_parameters_end__\xff";
}
+ // terminator
+ state += "\xfe";
+
state.replace('\xff', '\0');
// now saving state, carefully until host written bytes matches full state size
@@ -874,7 +1000,7 @@ public:
{
// TODO
return V3_NOT_IMPLEMENTED;
- };
+ }
uint32_t getLatencySamples() const noexcept
{
@@ -1265,7 +1391,7 @@ public:
// TODO
return V3_NOT_IMPLEMENTED;
- };
+ }
double normalisedParameterToPlain(const v3_param_id rindex, const double normalised)
{
@@ -1278,7 +1404,7 @@ public:
const ParameterRanges& ranges(fPlugin.getParameterRanges(rindex - fParameterOffset));
return ranges.getUnnormalizedValue(normalised);
- };
+ }
double plainParameterToNormalised(const v3_param_id rindex, const double plain)
{
@@ -1291,7 +1417,7 @@ public:
const ParameterRanges& ranges(fPlugin.getParameterRanges(rindex - fParameterOffset));
return ranges.getNormalizedValue(plain);
- };
+ }
double getParameterNormalized(const v3_param_id rindex)
{
@@ -1513,7 +1639,7 @@ public:
res = v3_cpp_obj(attrs)->get_int(attrs, "rindex", &rindex);
DISTRHO_SAFE_ASSERT_INT_RETURN(res == V3_OK, res, res);
- DISTRHO_SAFE_ASSERT_INT_RETURN(rindex > 0, rindex, V3_INTERNAL_ERR);
+ DISTRHO_SAFE_ASSERT_INT_RETURN(rindex >= fParameterOffset, rindex, V3_INTERNAL_ERR);
res = v3_cpp_obj(attrs)->get_float(attrs, "value", &value);
DISTRHO_SAFE_ASSERT_INT_RETURN(res == V3_OK, res, res);
@@ -1965,7 +2091,7 @@ struct dpf_attribute_list : v3_attribute_list_cpp {
query_interface = []V3_API(void* self, const v3_tuid iid, void** iface) -> v3_result
{
- d_stdout("dpf_attribute_list::query_interface => %p %s %p", self, tuid2str(iid), iface);
+ // d_stdout("dpf_attribute_list::query_interface => %p %s %p", self, tuid2str(iid), iface);
*iface = NULL;
DISTRHO_SAFE_ASSERT_RETURN(self != nullptr, V3_NO_INTERFACE);
diff --git a/distrho/src/DistrhoUIVST3.cpp b/distrho/src/DistrhoUIVST3.cpp
@@ -29,6 +29,10 @@
* - mousewheel event
* - key down/up events
* - size constraints
+ * - host-side resize
+ * - oddities with init and size callback (triggered too early?)
+ * - win/mac native idle loop
+ * - linux runloop
*/
START_NAMESPACE_DISTRHO
@@ -137,19 +141,19 @@ public:
{
// TODO
return V3_NOT_IMPLEMENTED;
- };
+ }
v3_result onKeyDown(int16_t /*key_char*/, int16_t /*key_code*/, int16_t /*modifiers*/)
{
// TODO
return V3_NOT_IMPLEMENTED;
- };
+ }
v3_result onKeyUp(int16_t /*key_char*/, int16_t /*key_code*/, int16_t /*modifiers*/)
{
// TODO
return V3_NOT_IMPLEMENTED;
- };
+ }
v3_result getSize(v3_view_rect* const rect) const noexcept
{
@@ -268,8 +272,6 @@ public:
return V3_OK;
}
- d_stdout("UIVst3 received msg '%s'", msgid);
-
if (std::strcmp(msgid, "parameter-set") == 0)
{
int64_t rindex;
@@ -342,6 +344,8 @@ public:
return V3_OK;
}
+ d_stdout("UIVst3 received unknown msg '%s'", msgid);
+
return V3_NOT_IMPLEMENTED;
}
diff --git a/examples/Meters/Makefile b/examples/Meters/Makefile
@@ -37,7 +37,8 @@ endif # HAVE_LIBLO
endif # MACOS_OR_WINDOWS
TARGETS += lv2_sep
-TARGETS += vst
+TARGETS += vst2
+TARGETS += vst3
endif # HAVE_OPENGL