From a4aa2d83c8d51a47402ac0417a365ec0fe36cce3 Mon Sep 17 00:00:00 2001 From: falkTX Date: Mon, 27 Sep 2021 01:07:33 +0100 Subject: [PATCH] VST3: full state save/restore support, update TODO items Signed-off-by: falkTX --- distrho/src/DistrhoPluginVST3.cpp | 280 ++++++++++++++++++++++-------- distrho/src/DistrhoUIVST3.cpp | 14 +- examples/Meters/Makefile | 3 +- 3 files changed, 214 insertions(+), 83 deletions(-) diff --git a/distrho/src/DistrhoPluginVST3.cpp b/distrho/src/DistrhoPluginVST3.cpp index e4fab400..17fb2207 100644 --- 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(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; jrestart_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; iget_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 index bb6b6cd9..373306d8 100644 --- 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 index fbfcc10b..cd325c7c 100644 --- 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