Browse Source

VST3: full state save/restore support, update TODO items

Signed-off-by: falkTX <falktx@falktx.com>
pull/330/head
falkTX 3 years ago
parent
commit
a4aa2d83c8
Signed by: falkTX <falktx@falktx.com> GPG Key ID: CDBAA37ABC74FBA0
3 changed files with 214 additions and 83 deletions
  1. +203
    -77
      distrho/src/DistrhoPluginVST3.cpp
  2. +9
    -5
      distrho/src/DistrhoUIVST3.cpp
  3. +2
    -1
      examples/Meters/Makefile

+ 203
- 77
distrho/src/DistrhoPluginVST3.cpp View File

@@ -47,10 +47,7 @@
* - parameter enumeration as lists * - parameter enumeration as lists
* - hide parameter outputs? * - hide parameter outputs?
* - hide program parameter? * - hide program parameter?
* - save and restore state
* - save and restore current program
* - deal with parameter triggers * - deal with parameter triggers
* - send current state to UI on request
* - midi cc parameter mapping * - midi cc parameter mapping
* - full MIDI1 encode and decode * - full MIDI1 encode and decode
* - decode version number (0x102030 -> 1.2.3) * - decode version number (0x102030 -> 1.2.3)
@@ -58,7 +55,7 @@
* - optional audio buses, create dummy buffer of max_block_size length for them * - optional audio buses, create dummy buffer of max_block_size length for them
* - routing info, do we care? * - routing info, do we care?
* - set sidechain bus name from port group * - 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 class_flags
* - set factory sub_categories * - set factory sub_categories
* - set factory email (needs new DPF API, useful for LV2 as well) * - 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. /* 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 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) 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 #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; 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; return V3_OK;
@@ -789,42 +898,54 @@ public:
return v3_cpp_obj(stream)->write(stream, &buffer, 1, &ignored); return v3_cpp_obj(stream)->write(stream, &buffer, 1, &ignored);
} }


String state;

#if DISTRHO_PLUGIN_WANT_FULL_STATE #if DISTRHO_PLUGIN_WANT_FULL_STATE
/*
// Update current state // Update current state
for (StringMap::const_iterator cit=fStateMap.begin(), cite=fStateMap.end(); cit != cite; ++cit) for (StringMap::const_iterator cit=fStateMap.begin(), cite=fStateMap.end(); cit != cite; ++cit)
{ {
const String& key = cit->first; const String& key = cit->first;
fStateMap[key] = fPlugin.getState(key); fStateMap[key] = fPlugin.getState(key);
} }
*/
#endif #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"; tmpStr += "\xff";


state += tmpStr; 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 #endif


if (paramCount != 0) if (paramCount != 0)
{ {
// add another separator
state += "\xff";
state += "__dpf_parameters_begin__\xff";


for (uint32_t i=0; i<paramCount; ++i) for (uint32_t i=0; i<paramCount; ++i)
{ {
@@ -840,8 +961,13 @@ public:


state += tmpStr; state += tmpStr;
} }

state += "__dpf_parameters_end__\xff";
} }


// terminator
state += "\xfe";

state.replace('\xff', '\0'); state.replace('\xff', '\0');


// now saving state, carefully until host written bytes matches full state size // now saving state, carefully until host written bytes matches full state size
@@ -874,7 +1000,7 @@ public:
{ {
// TODO // TODO
return V3_NOT_IMPLEMENTED; return V3_NOT_IMPLEMENTED;
};
}


uint32_t getLatencySamples() const noexcept uint32_t getLatencySamples() const noexcept
{ {
@@ -1265,7 +1391,7 @@ public:


// TODO // TODO
return V3_NOT_IMPLEMENTED; return V3_NOT_IMPLEMENTED;
};
}


double normalisedParameterToPlain(const v3_param_id rindex, const double normalised) double normalisedParameterToPlain(const v3_param_id rindex, const double normalised)
{ {
@@ -1278,7 +1404,7 @@ public:


const ParameterRanges& ranges(fPlugin.getParameterRanges(rindex - fParameterOffset)); const ParameterRanges& ranges(fPlugin.getParameterRanges(rindex - fParameterOffset));
return ranges.getUnnormalizedValue(normalised); return ranges.getUnnormalizedValue(normalised);
};
}


double plainParameterToNormalised(const v3_param_id rindex, const double plain) double plainParameterToNormalised(const v3_param_id rindex, const double plain)
{ {
@@ -1291,7 +1417,7 @@ public:


const ParameterRanges& ranges(fPlugin.getParameterRanges(rindex - fParameterOffset)); const ParameterRanges& ranges(fPlugin.getParameterRanges(rindex - fParameterOffset));
return ranges.getNormalizedValue(plain); return ranges.getNormalizedValue(plain);
};
}


double getParameterNormalized(const v3_param_id rindex) double getParameterNormalized(const v3_param_id rindex)
{ {
@@ -1513,7 +1639,7 @@ public:


res = v3_cpp_obj(attrs)->get_int(attrs, "rindex", &rindex); 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(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); res = v3_cpp_obj(attrs)->get_float(attrs, "value", &value);
DISTRHO_SAFE_ASSERT_INT_RETURN(res == V3_OK, res, res); 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 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; *iface = NULL;
DISTRHO_SAFE_ASSERT_RETURN(self != nullptr, V3_NO_INTERFACE); DISTRHO_SAFE_ASSERT_RETURN(self != nullptr, V3_NO_INTERFACE);




+ 9
- 5
distrho/src/DistrhoUIVST3.cpp View File

@@ -29,6 +29,10 @@
* - mousewheel event * - mousewheel event
* - key down/up events * - key down/up events
* - size constraints * - size constraints
* - host-side resize
* - oddities with init and size callback (triggered too early?)
* - win/mac native idle loop
* - linux runloop
*/ */


START_NAMESPACE_DISTRHO START_NAMESPACE_DISTRHO
@@ -137,19 +141,19 @@ public:
{ {
// TODO // TODO
return V3_NOT_IMPLEMENTED; return V3_NOT_IMPLEMENTED;
};
}


v3_result onKeyDown(int16_t /*key_char*/, int16_t /*key_code*/, int16_t /*modifiers*/) v3_result onKeyDown(int16_t /*key_char*/, int16_t /*key_code*/, int16_t /*modifiers*/)
{ {
// TODO // TODO
return V3_NOT_IMPLEMENTED; return V3_NOT_IMPLEMENTED;
};
}


v3_result onKeyUp(int16_t /*key_char*/, int16_t /*key_code*/, int16_t /*modifiers*/) v3_result onKeyUp(int16_t /*key_char*/, int16_t /*key_code*/, int16_t /*modifiers*/)
{ {
// TODO // TODO
return V3_NOT_IMPLEMENTED; return V3_NOT_IMPLEMENTED;
};
}


v3_result getSize(v3_view_rect* const rect) const noexcept v3_result getSize(v3_view_rect* const rect) const noexcept
{ {
@@ -268,8 +272,6 @@ public:
return V3_OK; return V3_OK;
} }


d_stdout("UIVst3 received msg '%s'", msgid);

if (std::strcmp(msgid, "parameter-set") == 0) if (std::strcmp(msgid, "parameter-set") == 0)
{ {
int64_t rindex; int64_t rindex;
@@ -342,6 +344,8 @@ public:
return V3_OK; return V3_OK;
} }


d_stdout("UIVst3 received unknown msg '%s'", msgid);

return V3_NOT_IMPLEMENTED; return V3_NOT_IMPLEMENTED;
} }




+ 2
- 1
examples/Meters/Makefile View File

@@ -37,7 +37,8 @@ endif # HAVE_LIBLO
endif # MACOS_OR_WINDOWS endif # MACOS_OR_WINDOWS


TARGETS += lv2_sep TARGETS += lv2_sep
TARGETS += vst
TARGETS += vst2
TARGETS += vst3


endif # HAVE_OPENGL endif # HAVE_OPENGL




Loading…
Cancel
Save