DPF

DISTRHO Plugin Framework
Log | Files | Refs | Submodules | README | LICENSE

commit 1db760336079f0bf9968d21f9497f905acbed346
parent 6cbf08d58809b9c70e5283eb40975cf9efd0825b
Author: falkTX <falktx@falktx.com>
Date:   Fri, 24 Sep 2021 19:15:24 +0100

VST3: Implement parameter state save/restore

Signed-off-by: falkTX <falktx@falktx.com>

Diffstat:
Mdistrho/extra/String.hpp | 2+-
Mdistrho/src/DistrhoPluginVST3.cpp | 288+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
2 files changed, 285 insertions(+), 5 deletions(-)

diff --git a/distrho/extra/String.hpp b/distrho/extra/String.hpp @@ -516,7 +516,7 @@ public: */ String& replace(const char before, const char after) noexcept { - DISTRHO_SAFE_ASSERT_RETURN(before != '\0' && after != '\0', *this); + DISTRHO_SAFE_ASSERT_RETURN(before != '\0' /* && after != '\0' */, *this); for (std::size_t i=0; i < fBufferLen; ++i) { diff --git a/distrho/src/DistrhoPluginVST3.cpp b/distrho/src/DistrhoPluginVST3.cpp @@ -507,13 +507,195 @@ public: return V3_OK; } - v3_result setState(v3_bstream*, void*) + /* 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. + * 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. + */ + v3_result setState(v3_bstream* const stream, void* arg) + { +#if DISTRHO_PLUGIN_WANT_STATE + // TODO +#endif + + if (const uint32_t paramCount = fPlugin.getParameterCount()) + { + char buffer[32], orig; + String key, value; + v3_result res; + bool fillingKey = true; + + // temporarily set locale to "C" while converting floats + const ScopedSafeLocale ssl; + + for (int32_t pos = 0, read;; pos += read) + { + std::memset(buffer, '\xff', sizeof(buffer)); + res = stream->read(arg, 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) + { + if (buffer[i] == '\0' && pos == 0 && i == 0) + continue; + + orig = buffer[read]; + buffer[read] = '\0'; + + if (fillingKey) + key += buffer + i; + else + value += buffer + i; + + i += std::strlen(buffer + i); + buffer[read] = orig; + + if (buffer[i] == '\0') + { + fillingKey = !fillingKey; + + if (value.isNotEmpty()) + { + // 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; + + fPlugin.setParameterValue(j, std::atof(value.buffer())); + break; + } + + key.clear(); + value.clear(); + } + + if (buffer[i+1] == '\0') + return V3_OK; + } + } + + if (buffer[read] == '\0') + return V3_OK; + } + } + + return V3_OK; + } + + v3_result getState(v3_bstream* const stream, void* arg) + { + const uint32_t paramCount = fPlugin.getParameterCount(); +#if DISTRHO_PLUGIN_WANT_STATE + const uint32_t stateCount = fPlugin.getStateCount(); +#else + const uint32_t stateCount = 0; +#endif + + if (stateCount == 0 && paramCount == 0) + { + char buffer = '\0'; + int32_t ignored; + return stream->write(arg, &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; + + // join key and value + String tmpStr; + tmpStr = key; + tmpStr += "\xff"; + tmpStr += value; + tmpStr += "\xff"; + + state += tmpStr; + } + */ +#endif + + if (paramCount != 0) + { + // add another separator + state += "\xff"; + + for (uint32_t i=0; i<paramCount; ++i) + { + if (fPlugin.isParameterOutputOrTrigger(i)) + continue; + + // join key and value + String tmpStr; + tmpStr = fPlugin.getParameterSymbol(i); + tmpStr += "\xff"; + tmpStr += String(fPlugin.getParameterValue(i)); + tmpStr += "\xff"; + + state += tmpStr; + } + } + + state.replace('\xff', '\0'); + + // now saving state, carefully until host written bytes matches full state size + const char* buffer = state.buffer(); + const int32_t size = static_cast<int32_t>(state.length())+1; + v3_result res; + + for (int32_t wrtntotal = 0, wrtn; wrtntotal < size; wrtntotal += wrtn) + { + wrtn = 0; + res = stream->write(arg, const_cast<char*>(buffer), size - wrtntotal, &wrtn); + + DISTRHO_SAFE_ASSERT_INT_RETURN(res == V3_OK, res, res); + DISTRHO_SAFE_ASSERT_INT_RETURN(wrtn > 0, wrtn, V3_INTERNAL_ERR); + } + + return V3_OK; + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_bstream interface calls (for state support) + + v3_result read(void* buffer, int32_t num_bytes, int32_t* bytes_read) { // TODO return V3_NOT_IMPLEMENTED; } - v3_result getState(v3_bstream*, void*) + v3_result write(void* buffer, int32_t num_bytes, int32_t* bytes_written) + { + // TODO + return V3_NOT_IMPLEMENTED; + } + + v3_result seek(int64_t pos, int32_t seek_mode, int64_t* result) + { + // TODO + return V3_NOT_IMPLEMENTED; + } + + v3_result tell(int64_t* pos) { // TODO return V3_NOT_IMPLEMENTED; @@ -1303,6 +1485,102 @@ struct dpf_audio_processor : v3_audio_processor_cpp { }; // -------------------------------------------------------------------------------------------------------------------- +// dpf_state_stream + +struct v3_bstream_cpp : v3_funknown { + v3_bstream stream; +}; + +struct dpf_state_stream : v3_bstream_cpp { + ScopedPointer<PluginVst3>& vst3; + + dpf_state_stream(ScopedPointer<PluginVst3>& v) + : vst3(v) + { + static const uint8_t* kSupportedInterfaces[] = { + v3_funknown_iid, + v3_bstream_iid + }; + + // ------------------------------------------------------------------------------------------------------------ + // v3_funknown + + query_interface = []V3_API(void* self, const v3_tuid iid, void** iface) -> v3_result + { + d_stdout("dpf_factory::query_interface => %p %s %p", self, tuid2str(iid), iface); + *iface = NULL; + DISTRHO_SAFE_ASSERT_RETURN(self != nullptr, V3_NO_INTERFACE); + + for (const uint8_t* interface_iid : kSupportedInterfaces) + { + if (v3_tuid_match(interface_iid, iid)) + { + *iface = self; + return V3_OK; + } + } + + return V3_NO_INTERFACE; + }; + + // TODO + ref = []V3_API(void*) -> uint32_t { return 1; }; + unref = []V3_API(void*) -> uint32_t { return 0; }; + + // ------------------------------------------------------------------------------------------------------------ + // v3_bstream + + stream.read = []V3_API(void* self, void* buffer, int32_t num_bytes, int32_t* bytes_read) -> v3_result + { + d_stdout("dpf_state_stream::read => %p %p %i %p", self, buffer, num_bytes, bytes_read); + dpf_state_stream* const stream = *(dpf_state_stream**)self; + DISTRHO_SAFE_ASSERT_RETURN(stream != nullptr, V3_NOT_INITIALISED); + + PluginVst3* const vst3 = stream->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALISED); + + return V3_NOT_IMPLEMENTED; + }; + + stream.write = []V3_API(void* self, void* buffer, int32_t num_bytes, int32_t* bytes_written) -> v3_result + { + d_stdout("dpf_state_stream::write => %p %p %i %p", self, buffer, num_bytes, bytes_written); + dpf_state_stream* const stream = *(dpf_state_stream**)self; + DISTRHO_SAFE_ASSERT_RETURN(stream != nullptr, V3_NOT_INITIALISED); + + PluginVst3* const vst3 = stream->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALISED); + + return V3_NOT_IMPLEMENTED; + }; + + stream.seek = []V3_API(void* self, int64_t pos, int32_t seek_mode, int64_t* result) -> v3_result + { + d_stdout("dpf_state_stream::seek => %p %lu %i %p", self, pos, seek_mode, result); + dpf_state_stream* const stream = *(dpf_state_stream**)self; + DISTRHO_SAFE_ASSERT_RETURN(stream != nullptr, V3_NOT_INITIALISED); + + PluginVst3* const vst3 = stream->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALISED); + + return V3_NOT_IMPLEMENTED; + }; + + stream.tell = []V3_API(void* self, int64_t* pos) -> v3_result + { + d_stdout("dpf_state_stream::tell => %p %p", self, pos); + dpf_state_stream* const stream = *(dpf_state_stream**)self; + DISTRHO_SAFE_ASSERT_RETURN(stream != nullptr, V3_NOT_INITIALISED); + + PluginVst3* const vst3 = stream->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALISED); + + return V3_NOT_IMPLEMENTED; + }; + } +}; + +// -------------------------------------------------------------------------------------------------------------------- // dpf_component struct v3_component_cpp : v3_funknown { @@ -1315,7 +1593,7 @@ struct dpf_component : v3_component_cpp { ScopedPointer<dpf_component>* self; ScopedPointer<dpf_audio_processor> processor; ScopedPointer<dpf_edit_controller> controller; -// ScopedPointer<dpf_plugin_view> view; + ScopedPointer<dpf_state_stream> stream; ScopedPointer<PluginVst3> vst3; dpf_component(ScopedPointer<dpf_component>* const s) @@ -1539,7 +1817,7 @@ struct dpf_component : v3_component_cpp { comp.get_state = []V3_API(void* self, v3_bstream** stream) -> v3_result { - d_stdout("dpf_component::get_state => %p", self); + d_stdout("dpf_component::get_state => %p %p", self, stream); dpf_component* const component = *(dpf_component**)self; DISTRHO_SAFE_ASSERT_RETURN(component != nullptr, V3_NOT_INITIALISED); @@ -1706,6 +1984,8 @@ struct dpf_factory : v3_plugin_factory_cpp { return V3_NOT_IMPLEMENTED; }; } + + DISTRHO_PREVENT_HEAP_ALLOCATION }; END_NAMESPACE_DISTRHO