Browse Source

Implement clap state extension

pull/321/merge
falkTX 3 years ago
parent
commit
e735e7efdc
4 changed files with 458 additions and 28 deletions
  1. +439
    -16
      distrho/src/DistrhoPluginCLAP.cpp
  2. +12
    -6
      distrho/src/DistrhoPluginVST2.cpp
  3. +6
    -6
      distrho/src/DistrhoUIInternal.hpp
  4. +1
    -0
      examples/States/Makefile

+ 439
- 16
distrho/src/DistrhoPluginCLAP.cpp View File

@@ -31,15 +31,7 @@
# include "../extra/RingBuffer.hpp"
#endif

#if (defined(DISTRHO_OS_MAC) || defined(DISTRHO_OS_WINDOWS)) && ! DISTRHO_PLUGIN_HAS_EXTERNAL_UI
# define DPF_CLAP_USING_HOST_TIMER 0
#else
# define DPF_CLAP_USING_HOST_TIMER 1
#endif

#ifndef DPF_CLAP_TIMER_INTERVAL
# define DPF_CLAP_TIMER_INTERVAL 16 /* ~60 fps */
#endif
#include <map>

#include "clap/entry.h"
#include "clap/plugin-factory.h"
@@ -50,13 +42,25 @@
#include "clap/ext/state.h"
#include "clap/ext/timer-support.h"

#if (defined(DISTRHO_OS_MAC) || defined(DISTRHO_OS_WINDOWS)) && ! DISTRHO_PLUGIN_HAS_EXTERNAL_UI
# define DPF_CLAP_USING_HOST_TIMER 0
#else
# define DPF_CLAP_USING_HOST_TIMER 1
#endif

#ifndef DPF_CLAP_TIMER_INTERVAL
# define DPF_CLAP_TIMER_INTERVAL 16 /* ~60 fps */
#endif

START_NAMESPACE_DISTRHO

// --------------------------------------------------------------------------------------------------------------------

typedef std::map<const String, String> StringMap;

struct ClapEventQueue
{
#if DISTRHO_PLUGIN_HAS_UI
#if DISTRHO_PLUGIN_HAS_UI
enum EventType {
kEventGestureBegin,
kEventGestureEnd,
@@ -103,15 +107,22 @@ struct ClapEventQueue
std::memcpy(&events[used++], &event, sizeof(Event));
}
} fEventQueue;
#endif

#if DISTRHO_PLUGIN_HAS_UI && DISTRHO_PLUGIN_WANT_MIDI_INPUT
#if DISTRHO_PLUGIN_WANT_MIDI_INPUT
SmallStackBuffer fNotesBuffer;
#endif
#endif

#if DISTRHO_PLUGIN_HAS_UI && DISTRHO_PLUGIN_WANT_STATE
#if DISTRHO_PLUGIN_WANT_PROGRAMS
uint32_t fCurrentProgram;
#endif

#if DISTRHO_PLUGIN_WANT_STATE
StringMap fStateMap;
#if DISTRHO_PLUGIN_HAS_UI
virtual void setStateFromUI(const char* key, const char* value) = 0;
#endif
#endif

struct CachedParameters {
uint numParams;
@@ -150,6 +161,9 @@ struct ClapEventQueue
#if DISTRHO_PLUGIN_HAS_UI && DISTRHO_PLUGIN_WANT_MIDI_INPUT && ! defined(DISTRHO_PROPER_CPP11_SUPPORT)
std::memset(&fNotesBuffer, 0, sizeof(fNotesBuffer));
#endif
#if DISTRHO_PLUGIN_WANT_PROGRAMS
fCurrentProgram = 0;
#endif
}

virtual ~ClapEventQueue() {}
@@ -184,6 +198,12 @@ public:
fPluinEventQueue(eventQueue),
fEventQueue(eventQueue->fEventQueue),
fCachedParameters(eventQueue->fCachedParameters),
#if DISTRHO_PLUGIN_WANT_PROGRAMS
fCurrentProgram(eventQueue->fCurrentProgram),
#endif
#if DISTRHO_PLUGIN_WANT_STATE
fStateMap(eventQueue->fStateMap),
#endif
fHost(host),
fHostGui(hostGui),
#if DPF_CLAP_USING_HOST_TIMER
@@ -437,12 +457,42 @@ public:

// ----------------------------------------------------------------------------------------------------------------

void setParameterValueFromPlugin(const uint index, const float value)
{
if (UIExporter* const ui = fUI.get())
ui->parameterChanged(index, value);
}

#if DISTRHO_PLUGIN_WANT_PROGRAMS
void setProgramFromPlugin(const uint index)
{
if (UIExporter* const ui = fUI.get())
ui->programLoaded(index);
}
#endif

#if DISTRHO_PLUGIN_WANT_STATE
void setStateFromPlugin(const char* const key, const char* const value)
{
if (UIExporter* const ui = fUI.get())
ui->stateChanged(key, value);
}
#endif

// ----------------------------------------------------------------------------------------------------------------

private:
// Plugin and UI
PluginExporter& fPlugin;
ClapEventQueue* const fPluinEventQueue;
ClapEventQueue::Queue& fEventQueue;
ClapEventQueue::CachedParameters& fCachedParameters;
#if DISTRHO_PLUGIN_WANT_PROGRAMS
uint32_t& fCurrentProgram;
#endif
#if DISTRHO_PLUGIN_WANT_STATE
StringMap& fStateMap;
#endif
const clap_host_t* const fHost;
const clap_host_gui_t* const fHostGui;
#if DPF_CLAP_USING_HOST_TIMER
@@ -483,7 +533,31 @@ private:
fPlugin.getInstancePointer(),
fScaleFactor);

// TODO fetch and set state too
#if DISTRHO_PLUGIN_WANT_PROGRAMS
fUI->programLoaded(fCurrentProgram);
#endif

#if DISTRHO_PLUGIN_WANT_FULL_STATE
// Update current state from plugin side
for (StringMap::const_iterator cit=fStateMap.begin(), cite=fStateMap.end(); cit != cite; ++cit)
{
const String& key = cit->first;
fStateMap[key] = fPlugin.getStateValue(key);
}
#endif

#if DISTRHO_PLUGIN_WANT_STATE
// Set state
for (StringMap::const_iterator cit=fStateMap.begin(), cite=fStateMap.end(); cit != cite; ++cit)
{
const String& key = cit->first;
const String& value = cit->second;

// TODO skip DSP only states

fUI->stateChanged(key, value);
}
#endif

for (uint32_t i=0; i<fCachedParameters.numParams; ++i)
{
@@ -621,6 +695,13 @@ public:
#if DISTRHO_PLUGIN_HAS_UI && DISTRHO_PLUGIN_WANT_MIDI_INPUT
fNotesRingBuffer.setRingBuffer(&fNotesBuffer, true);
#endif
#if DISTRHO_PLUGIN_WANT_STATE
for (uint32_t i=0, count=fPlugin.getStateCount(); i<count; ++i)
{
const String& dkey(fPlugin.getStateKey(i));
fStateMap[dkey] = fPlugin.getStateDefaultValue(i);
}
#endif
}

// ----------------------------------------------------------------------------------------------------------------
@@ -1062,6 +1143,309 @@ public:
fPlugin.setParameterValue(event->param_id, event->value);
}

// ----------------------------------------------------------------------------------------------------------------
// state

bool stateSave(const clap_ostream_t* const stream)
{
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';
return stream->write(stream, &buffer, 1) == 1;
}

#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.getStateValue(key);
}
#endif

String state;

#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)
{
state += "__dpf_parameters_begin__\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";
if (fPlugin.getParameterHints(i) & kParameterIsInteger)
tmpStr += String(static_cast<int>(std::round(fPlugin.getParameterValue(i))));
else
tmpStr += String(fPlugin.getParameterValue(i));
tmpStr += "\xff";

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
const char* buffer = state.buffer();
const int32_t size = static_cast<int32_t>(state.length())+1;

for (int32_t wrtntotal = 0, wrtn; wrtntotal < size; wrtntotal += wrtn)
{
wrtn = stream->write(stream, buffer, size - wrtntotal);
DISTRHO_SAFE_ASSERT_INT_RETURN(wrtn > 0, wrtn, false);
}

return true;
}

bool stateLoad(const clap_istream_t* const stream)
{
#if DISTRHO_PLUGIN_HAS_UI
ClapUI* const ui = fUI.get();
#endif
String key, value;
bool hasValue = false;
bool fillingKey = true; // if filling key or value
char queryingType = 'i'; // can be 'n', 's' or 'p' (none, states, parameters)

char buffer[512], orig;
buffer[sizeof(buffer)-1] = '\xff';

for (int32_t terminated = 0, read; terminated == 0;)
{
read = stream->read(stream, buffer, sizeof(buffer)-1);
DISTRHO_SAFE_ASSERT_INT_RETURN(read >= 0, read, false);

if (read == 0)
return true;

for (int32_t i = 0; i < read; ++i)
{
// found terminator, stop here
if (buffer[i] == '\xfe')
{
terminated = 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;
hasValue = true;
}

// increase buffer offset by length of string
i += std::strlen(buffer + i);

// restore read character
buffer[read] = orig;

// if buffer offset points to null, we found the end of a string, lets check
if (buffer[i] == '\0')
{
// special keys
if (key == "__dpf_state_begin__")
{
DISTRHO_SAFE_ASSERT_INT_RETURN(queryingType == 'i' || queryingType == 'n',
queryingType, false);
queryingType = 's';
key.clear();
value.clear();
hasValue = false;
continue;
}
if (key == "__dpf_state_end__")
{
DISTRHO_SAFE_ASSERT_INT_RETURN(queryingType == 's', queryingType, false);
queryingType = 'n';
key.clear();
value.clear();
hasValue = false;
continue;
}
if (key == "__dpf_parameters_begin__")
{
DISTRHO_SAFE_ASSERT_INT_RETURN(queryingType == 'i' || queryingType == 'n',
queryingType, false);
queryingType = 'p';
key.clear();
value.clear();
hasValue = false;
continue;
}
if (key == "__dpf_parameters_end__")
{
DISTRHO_SAFE_ASSERT_INT_RETURN(queryingType == 'p', queryingType, false);
queryingType = 'x';
key.clear();
value.clear();
hasValue = false;
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
if (! hasValue)
continue;

if (key == "__dpf_program__")
{
DISTRHO_SAFE_ASSERT_INT_RETURN(queryingType == 'i', queryingType, false);
queryingType = 'n';

d_debug("found program '%s'", value.buffer());

#if DISTRHO_PLUGIN_WANT_PROGRAMS
const int program = std::atoi(value.buffer());
DISTRHO_SAFE_ASSERT_CONTINUE(program >= 0);

fCurrentProgram = static_cast<uint32_t>(program);
fPlugin.loadProgram(fCurrentProgram);

#if DISTRHO_PLUGIN_HAS_UI
if (ui != nullptr)
ui->setProgramFromPlugin(fCurrentProgram);
#endif
#endif
}
else if (queryingType == 's')
{
d_debug("found state '%s' '%s'", key.buffer(), value.buffer());

#if DISTRHO_PLUGIN_WANT_STATE
if (fPlugin.wantStateKey(key))
{
fStateMap[key] = value;
fPlugin.setState(key, value);

#if DISTRHO_PLUGIN_HAS_UI
if (ui != nullptr)
ui->setStateFromPlugin(key, value);
#endif
}
#endif
}
else if (queryingType == 'p')
{
d_debug("found parameter '%s' '%s'", key.buffer(), value.buffer());
float fvalue;

// find parameter with this symbol, and set its value
for (uint32_t j=0; j<fCachedParameters.numParams; ++j)
{
if (fPlugin.isParameterOutputOrTrigger(j))
continue;
if (fPlugin.getParameterSymbol(j) != key)
continue;

if (fPlugin.getParameterHints(j) & kParameterIsInteger)
fvalue = std::atoi(value.buffer());
else
fvalue = std::atof(value.buffer());

fCachedParameters.values[j] = fvalue;
#if DISTRHO_PLUGIN_HAS_UI
if (ui != nullptr)
{
// UI parameter updates are handled outside the read loop (after host param restart)
fCachedParameters.changed[j] = true;
}
#endif
fPlugin.setParameterValue(j, fvalue);
break;
}
}

key.clear();
value.clear();
hasValue = false;
}
}
}

if (const clap_host_params_t* const hostParams = getHostExtension<clap_host_params_t>(CLAP_EXT_PARAMS))
hostParams->rescan(fHost, CLAP_PARAM_RESCAN_VALUES|CLAP_PARAM_RESCAN_TEXT);

#if DISTRHO_PLUGIN_HAS_UI
if (ui != nullptr)
{
for (uint32_t i=0; i<fCachedParameters.numParams; ++i)
{
if (fPlugin.isParameterOutputOrTrigger(i))
continue;
fCachedParameters.changed[i] = false;
ui->setParameterValueFromPlugin(i, fCachedParameters.values[i]);
}
}
#endif

return true;
}

// ----------------------------------------------------------------------------------------------------------------
// gui

@@ -1100,7 +1484,23 @@ public:
{
fPlugin.setState(key, value);

// TODO check if we want to save this key, and save it
// check if we want to save this key
if (! fPlugin.wantStateKey(key))
return;

// check if key already exists
for (StringMap::iterator it=fStateMap.begin(), ite=fStateMap.end(); it != ite; ++it)
{
const String& dkey(it->first);

if (dkey == key)
{
it->second = value;
return;
}
}

d_stderr("Failed to find plugin state with key \"%s\"", key);
}
#endif

@@ -1124,7 +1524,6 @@ private:
RingBufferControl<SmallStackBuffer> fNotesRingBuffer;
#endif
#endif

#if DISTRHO_PLUGIN_WANT_TIMEPOS
TimePosition fTimePosition;
#endif
@@ -1506,6 +1905,26 @@ static const clap_plugin_params_t clap_plugin_params = {
clap_plugin_params_flush
};

// --------------------------------------------------------------------------------------------------------------------
// plugin state

static bool clap_plugin_state_save(const clap_plugin_t* const plugin, const clap_ostream_t* const stream)
{
PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data);
return instance->stateSave(stream);
}

static bool clap_plugin_state_load(const clap_plugin_t* const plugin, const clap_istream_t* const stream)
{
PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data);
return instance->stateLoad(stream);
}

static const clap_plugin_state_t clap_plugin_state = {
clap_plugin_state_save,
clap_plugin_state_load
};

// --------------------------------------------------------------------------------------------------------------------
// plugin

@@ -1570,6 +1989,10 @@ static const void* clap_plugin_get_extension(const clap_plugin_t*, const char* c
return &clap_plugin_note_ports;
if (std::strcmp(id, CLAP_EXT_PARAMS) == 0)
return &clap_plugin_params;
#if DISTRHO_PLUGIN_WANT_STATE
if (std::strcmp(id, CLAP_EXT_STATE) == 0)
return &clap_plugin_state;
#endif
#if DISTRHO_PLUGIN_HAS_UI
if (std::strcmp(id, CLAP_EXT_GUI) == 0)
return &clap_plugin_gui;


+ 12
- 6
distrho/src/DistrhoPluginVST2.cpp View File

@@ -209,12 +209,12 @@ public:
// -------------------------------------------------------------------
// functions called from the plugin side, may block

# if DISTRHO_PLUGIN_WANT_STATE
#if DISTRHO_PLUGIN_WANT_STATE
void setStateFromPlugin(const char* const key, const char* const value)
{
fUI.stateChanged(key, value);
}
# endif
#endif

# if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI
int handlePluginKeyEvent(const bool down, const int32_t index, const intptr_t value)
@@ -611,25 +611,28 @@ public:
# endif
fVstUI = new UIVst(fAudioMaster, fEffect, this, &fPlugin, (intptr_t)ptr, fLastScaleFactor);

# if DISTRHO_PLUGIN_WANT_FULL_STATE
#if DISTRHO_PLUGIN_WANT_FULL_STATE
// Update current state from plugin side
for (StringMap::const_iterator cit=fStateMap.begin(), cite=fStateMap.end(); cit != cite; ++cit)
{
const String& key = cit->first;
fStateMap[key] = fPlugin.getStateValue(key);
}
# endif
#endif

# if DISTRHO_PLUGIN_WANT_STATE
#if DISTRHO_PLUGIN_WANT_STATE
// Set state
for (StringMap::const_iterator cit=fStateMap.begin(), cite=fStateMap.end(); cit != cite; ++cit)
{
const String& key = cit->first;
const String& value = cit->second;

// TODO skip DSP only states

fVstUI->setStateFromPlugin(key, value);
}
# endif
#endif

for (uint32_t i=0, count=fPlugin.getParameterCount(); i < count; ++i)
setParameterValueFromPlugin(i, fPlugin.getParameterValue(i));

@@ -777,7 +780,10 @@ public:

# if DISTRHO_PLUGIN_HAS_UI
if (fVstUI != nullptr)
{
// TODO skip DSP only states
fVstUI->setStateFromPlugin(key, value);
}
# endif

// get next key


+ 6
- 6
distrho/src/DistrhoUIInternal.hpp View File

@@ -193,16 +193,16 @@ public:
ui->parameterChanged(index, value);
}

#if DISTRHO_PLUGIN_WANT_PROGRAMS
#if DISTRHO_PLUGIN_WANT_PROGRAMS
void programLoaded(const uint32_t index)
{
DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,);

ui->programLoaded(index);
}
#endif
#endif

#if DISTRHO_PLUGIN_WANT_STATE
#if DISTRHO_PLUGIN_WANT_STATE
void stateChanged(const char* const key, const char* const value)
{
DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,);
@@ -211,11 +211,11 @@ public:

ui->stateChanged(key, value);
}
#endif
#endif

// -------------------------------------------------------------------

#if DISTRHO_UI_IS_STANDALONE
#if DISTRHO_UI_IS_STANDALONE
void exec(DGL_NAMESPACE::IdleCallback* const cb)
{
DISTRHO_SAFE_ASSERT_RETURN(cb != nullptr,);
@@ -238,7 +238,7 @@ public:
uiData->window->show();
uiData->window->focus();
}
#endif
#endif

bool plugin_idle()
{


+ 1
- 0
examples/States/Makefile View File

@@ -30,6 +30,7 @@ TARGETS += jack
TARGETS += lv2_sep
TARGETS += vst2
TARGETS += vst3
TARGETS += clap

all: $(TARGETS)



Loading…
Cancel
Save