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:
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