Browse Source

Initial work for host-visible state

Signed-off-by: falkTX <falktx@falktx.com>
pull/367/head
falkTX 3 years ago
parent
commit
7e6e3cc84b
6 changed files with 167 additions and 88 deletions
  1. +32
    -5
      distrho/DistrhoPlugin.hpp
  2. +11
    -1
      distrho/src/DistrhoPlugin.cpp
  3. +7
    -9
      distrho/src/DistrhoPluginInternal.hpp
  4. +100
    -57
      distrho/src/DistrhoPluginLV2.cpp
  5. +16
    -15
      distrho/src/DistrhoPluginLV2export.cpp
  6. +1
    -1
      distrho/src/lv2/atom-forge.h

+ 32
- 5
distrho/DistrhoPlugin.hpp View File

@@ -1,6 +1,6 @@
/*
* DISTRHO Plugin Framework (DPF)
* Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com>
* Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com>
*
* Permission to use, copy, modify, and/or distribute this software for any purpose with
* or without fee is hereby granted, provided that the above copyright notice and this
@@ -135,6 +135,29 @@ static const uint32_t kParameterIsTrigger = 0x20 | kParameterIsBoolean;

/** @} */

/* ------------------------------------------------------------------------------------------------------------
* State Hints */

/**
@defgroup StateHints State Hints

Various state hints.
@see Plugin::getStateHints
@{
*/

/**
State is available for the host to see and change.
*/
static const uint32_t kStateIsHostVisible = 0x01;

/**
State is a filename path.
*/
static const uint32_t kStateIsFilenamePath = 0x02 | kStateIsHostVisible;

/** @} */

/* ------------------------------------------------------------------------------------------------------------
* Base Plugin structs */

@@ -993,13 +1016,17 @@ protected:
Must be implemented by your plugin class only if DISTRHO_PLUGIN_WANT_STATE is enabled.
*/
virtual void initState(uint32_t index, String& stateKey, String& defaultStateValue);
#endif

#if DISTRHO_PLUGIN_WANT_STATEFILES
/**
TODO API under construction
TODO API under construction, should likely be put in a new State struct with key, name defValue and hints
Returns StateHints mask.
*/
virtual bool isStateFile(uint32_t index) = 0;
virtual uint32_t getStateHints(uint32_t index);
#endif

#if DISTRHO_PLUGIN_WANT_STATEFILES
DISTRHO_DEPRECATED_BY("getStateHints")
virtual bool isStateFile(uint32_t index) { return false; }
#endif

/* --------------------------------------------------------------------------------------------------------


+ 11
- 1
distrho/src/DistrhoPlugin.cpp View File

@@ -1,6 +1,6 @@
/*
* DISTRHO Plugin Framework (DPF)
* Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com>
* Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com>
*
* Permission to use, copy, modify, and/or distribute this software for any purpose with
* or without fee is hereby granted, provided that the above copyright notice and this
@@ -200,6 +200,16 @@ String Plugin::getState(const char*) const { return String(); }
#endif

#if DISTRHO_PLUGIN_WANT_STATE
uint32_t Plugin::getStateHints(const uint32_t index)
{
#if DISTRHO_PLUGIN_WANT_STATEFILES
if isStateFile(index)
return kStateIsFilenamePath;
#endif

return 0x0;
}

void Plugin::setState(const char*, const char*) {}
#endif



+ 7
- 9
distrho/src/DistrhoPluginInternal.hpp View File

@@ -727,6 +727,13 @@ public:
return fData->stateCount;
}

uint32_t getStateHints(const uint32_t index) const noexcept
{
DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr && index < fData->stateCount, 0x0);

return fPlugin->getStateHints(index);
}

const String& getStateKey(const uint32_t index) const noexcept
{
DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr && index < fData->stateCount, sFallbackString);
@@ -741,15 +748,6 @@ public:
return fData->stateDefValues[index];
}

# if DISTRHO_PLUGIN_WANT_STATEFILES
bool isStateFile(const uint32_t index) const
{
DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr && index < fData->stateCount, false);

return fPlugin->isStateFile(index);
}
# endif

# if DISTRHO_PLUGIN_WANT_FULL_STATE
String getState(const char* key) const
{


+ 100
- 57
distrho/src/DistrhoPluginLV2.cpp View File

@@ -1,6 +1,6 @@
/*
* DISTRHO Plugin Framework (DPF)
* Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com>
* Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com>
*
* Permission to use, copy, modify, and/or distribute this software for any purpose with
* or without fee is hereby granted, provided that the above copyright notice and this
@@ -17,6 +17,7 @@
#include "DistrhoPluginInternal.hpp"

#include "lv2/atom.h"
#include "lv2/atom-forge.h"
#include "lv2/atom-util.h"
#include "lv2/buf-size.h"
#include "lv2/data-access.h"
@@ -47,8 +48,8 @@
# define DISTRHO_PLUGIN_LV2_STATE_PREFIX "urn:distrho:"
#endif

#define DISTRHO_LV2_USE_EVENTS_IN (DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_TIMEPOS || (DISTRHO_PLUGIN_WANT_STATE && DISTRHO_PLUGIN_HAS_UI) || DISTRHO_PLUGIN_WANT_STATEFILES)
#define DISTRHO_LV2_USE_EVENTS_OUT (DISTRHO_PLUGIN_WANT_MIDI_OUTPUT || (DISTRHO_PLUGIN_WANT_STATE && DISTRHO_PLUGIN_HAS_UI))
#define DISTRHO_LV2_USE_EVENTS_IN (DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_TIMEPOS || DISTRHO_PLUGIN_WANT_STATE)
#define DISTRHO_LV2_USE_EVENTS_OUT (DISTRHO_PLUGIN_WANT_MIDI_OUTPUT || DISTRHO_PLUGIN_WANT_STATE)

START_NAMESPACE_DISTRHO

@@ -126,29 +127,29 @@ public:
#endif

#if DISTRHO_PLUGIN_WANT_STATE
std::memset(&fAtomForge, 0, sizeof(fAtomForge));
lv2_atom_forge_init(&fAtomForge, uridMap);

if (const uint32_t count = fPlugin.getStateCount())
{
fUrids = new LV2_URID[count];
fNeededUiSends = new bool[count];

for (uint32_t i=0; i < count; ++i)
{
fNeededUiSends[i] = false;

const String& dkey(fPlugin.getStateKey(i));
fStateMap[dkey] = fPlugin.getStateDefaultValue(i);
const String& statekey(fPlugin.getStateKey(i));
fStateMap[statekey] = fPlugin.getStateDefaultValue(i);

# if DISTRHO_PLUGIN_WANT_STATEFILES
if (fPlugin.isStateFile(i))
{
const String dpf_lv2_key(DISTRHO_PLUGIN_URI "#" + dkey);
const LV2_URID urid = uridMap->map(uridMap->handle, dpf_lv2_key.buffer());
fUridStateFileMap[urid] = dkey;
}
# endif
const String lv2key(DISTRHO_PLUGIN_URI "#" + statekey);
const LV2_URID urid = fUrids[i] = uridMap->map(uridMap->handle, lv2key.buffer());
fUridStateMap[urid] = statekey;
}
}
else
{
fUrids = nullptr;
fNeededUiSends = nullptr;
}
#else
@@ -544,13 +545,14 @@ public:
}
#endif

// check for messages from UI or files
#if DISTRHO_PLUGIN_WANT_STATE && (DISTRHO_PLUGIN_HAS_UI || DISTRHO_PLUGIN_WANT_STATEFILES)
// check for messages from UI or host
#if DISTRHO_PLUGIN_WANT_STATE
LV2_ATOM_SEQUENCE_FOREACH(fPortEventsIn, event)
{
if (event == nullptr)
break;

#if DISTRHO_PLUGIN_HAS_UI
if (event->body.type == fURIDs.dpfKeyValue)
{
const void* const data = (const void*)(event + 1);
@@ -567,8 +569,9 @@ public:
fWorker->schedule_work(fWorker->handle, sizeof(LV2_Atom)+event->body.size, &event->body);
}
}
# if DISTRHO_PLUGIN_WANT_STATEFILES
else if (event->body.type == fURIDs.atomObject && fWorker != nullptr)
else
#endif
if (event->body.type == fURIDs.atomObject && fWorker != nullptr)
{
const LV2_Atom_Object* const object = (const LV2_Atom_Object*)&event->body;

@@ -576,13 +579,12 @@ public:
const LV2_Atom* value = nullptr;
lv2_atom_object_get(object, fURIDs.patchProperty, &property, fURIDs.patchValue, &value, nullptr);

if (property != nullptr && property->type == fURIDs.atomURID &&
value != nullptr && value->type == fURIDs.atomPath)
if (property != nullptr && property->type == fURIDs.atomURID && value != nullptr &&
(value->type == fURIDs.atomPath || value->type == fURIDs.atomString))
{
fWorker->schedule_work(fWorker->handle, sizeof(LV2_Atom)+event->body.size, &event->body);
}
}
# endif
}
#endif

@@ -681,7 +683,7 @@ public:

updateParameterOutputsAndTriggers();

#if DISTRHO_PLUGIN_WANT_STATE && DISTRHO_PLUGIN_HAS_UI
#if DISTRHO_PLUGIN_WANT_STATE
fEventsOutData.initIfNeeded(fURIDs.atomSequence);

LV2_Atom_Event* aev;
@@ -692,6 +694,16 @@ public:
if (! fNeededUiSends[i])
continue;

const uint32_t hints = fPlugin.getStateHints(i);

#if ! DISTRHO_PLUGIN_HAS_UI
if ((hints & kStateIsHostVisible) == 0x0)
{
fNeededUiSends[i] = false;
continue;
}
#endif

const String& curKey(fPlugin.getStateKey(i));

for (StringToStringMap::const_iterator cit=fStateMap.begin(), cite=fStateMap.end(); cit != cite; ++cit)
@@ -703,8 +715,19 @@ public:

const String& value(cit->second);

// set msg size (key + value + separator + 2x null terminator)
const uint32_t msgSize = static_cast<uint32_t>(key.length()+value.length())+3U;
// set msg size
uint32_t msgSize;

if (hints & kStateIsHostVisible)
{
// object, prop key, prop urid, value key, value
msgSize = sizeof(LV2_Atom_Object) + sizeof(LV2_URID) * 3 + value.length() + 1;
}
else
{
// key + value + 2x null terminator + separator
msgSize = static_cast<uint32_t>(key.length()+value.length())+3U;
}

if (sizeof(LV2_Atom_Event) + msgSize > capacity - fEventsOutData.offset)
{
@@ -715,18 +738,38 @@ public:
// put data
aev = (LV2_Atom_Event*)(LV2_ATOM_CONTENTS(LV2_Atom_Sequence, fEventsOutData.port) + fEventsOutData.offset);
aev->time.frames = 0;
aev->body.type = fURIDs.dpfKeyValue;
aev->body.size = msgSize;

uint8_t* const msgBuf = LV2_ATOM_BODY(&aev->body);
std::memset(msgBuf, 0, msgSize);

// write key and value in atom buffer
std::memcpy(msgBuf, key.buffer(), key.length()+1);
std::memcpy(msgBuf+(key.length()+1), value.buffer(), value.length()+1);
if (hints & kStateIsHostVisible)
{
LV2_Atom_Forge atomForge = fAtomForge;
lv2_atom_forge_set_buffer(&atomForge, msgBuf, msgSize);

fEventsOutData.growBy(lv2_atom_pad_size(sizeof(LV2_Atom_Event) + msgSize));
LV2_Atom_Forge_Frame forgeFrame;
lv2_atom_forge_object(&atomForge, &forgeFrame, 0, fURIDs.patchSet);

lv2_atom_forge_key(&atomForge, fURIDs.patchProperty);
lv2_atom_forge_urid(&atomForge, fUrids[i]);

lv2_atom_forge_key(&atomForge, fURIDs.patchValue);
lv2_atom_forge_path(&atomForge, value.buffer(), static_cast<uint32_t>(value.length()+1));

lv2_atom_forge_pop(&atomForge, &forgeFrame);
}
else
{
aev->body.type = fURIDs.dpfKeyValue;
aev->body.size = msgSize;

std::memset(msgBuf, 0, msgSize);

// write key and value in atom buffer
std::memcpy(msgBuf, key.buffer(), key.length()+1);
std::memcpy(msgBuf+(key.length()+1), value.buffer(), value.length()+1);
}

fEventsOutData.growBy(lv2_atom_pad_size(sizeof(LV2_Atom_Event) + msgSize));
fNeededUiSends[i] = false;
break;
}
@@ -854,7 +897,7 @@ public:
}
# endif

String dpf_lv2_key;
String lv2key;
LV2_URID urid;

for (uint32_t i=0, count=fPlugin.getStateCount(); i < count; ++i)
@@ -870,24 +913,24 @@ public:

const String& value(cit->second);

# if DISTRHO_PLUGIN_WANT_STATEFILES
if (fPlugin.isStateFile(i))
if (const uint32_t hints = fPlugin.getStateHints(i))
{
dpf_lv2_key = DISTRHO_PLUGIN_URI "#";
urid = fURIDs.atomPath;
lv2key = DISTRHO_PLUGIN_URI "#";
urid = (hints & kStateIsFilenamePath) == kStateIsFilenamePath
? fURIDs.atomPath
: fURIDs.atomString;
}
else
# endif
{
dpf_lv2_key = DISTRHO_PLUGIN_LV2_STATE_PREFIX;
lv2key = DISTRHO_PLUGIN_LV2_STATE_PREFIX;
urid = fURIDs.atomString;
}

dpf_lv2_key += key;
lv2key += key;

// some hosts need +1 for the null terminator, even though the type is string
store(handle,
fUridMap->map(fUridMap->handle, dpf_lv2_key.buffer()),
fUridMap->map(fUridMap->handle, lv2key.buffer()),
value.buffer(),
value.length()+1,
urid,
@@ -903,33 +946,33 @@ public:
size_t size;
uint32_t type, flags;

String dpf_lv2_key;
String lv2key;
LV2_URID urid;

for (uint32_t i=0, count=fPlugin.getStateCount(); i < count; ++i)
{
const String& key(fPlugin.getStateKey(i));

# if DISTRHO_PLUGIN_WANT_STATEFILES
if (fPlugin.isStateFile(i))
if (const uint32_t hints = fPlugin.getStateHints(i))
{
dpf_lv2_key = DISTRHO_PLUGIN_URI "#";
urid = fURIDs.atomPath;
lv2key = DISTRHO_PLUGIN_URI "#";
urid = (hints & kStateIsFilenamePath) == kStateIsFilenamePath
? fURIDs.atomPath
: fURIDs.atomString;
}
else
# endif
{
dpf_lv2_key = DISTRHO_PLUGIN_LV2_STATE_PREFIX;
lv2key = DISTRHO_PLUGIN_LV2_STATE_PREFIX;
urid = fURIDs.atomString;
}

dpf_lv2_key += key;
lv2key += key;

size = 0;
type = 0;
flags = LV2_STATE_IS_POD|LV2_STATE_IS_PORTABLE;
const void* data = retrieve(handle,
fUridMap->map(fUridMap->handle, dpf_lv2_key.buffer()),
fUridMap->map(fUridMap->handle, lv2key.buffer()),
&size, &type, &flags);

if (data == nullptr || size == 0)
@@ -967,7 +1010,6 @@ public:
return LV2_WORKER_SUCCESS;
}

# if DISTRHO_PLUGIN_WANT_STATEFILES
if (eventBody->type == fURIDs.atomObject)
{
const LV2_Atom_Object* const object = (const LV2_Atom_Object*)eventBody;
@@ -978,7 +1020,8 @@ public:
DISTRHO_SAFE_ASSERT_RETURN(property != nullptr, LV2_WORKER_ERR_UNKNOWN);
DISTRHO_SAFE_ASSERT_RETURN(property->type == fURIDs.atomURID, LV2_WORKER_ERR_UNKNOWN);
DISTRHO_SAFE_ASSERT_RETURN(value != nullptr, LV2_WORKER_ERR_UNKNOWN);
DISTRHO_SAFE_ASSERT_RETURN(value->type == fURIDs.atomPath, LV2_WORKER_ERR_UNKNOWN);
DISTRHO_SAFE_ASSERT_RETURN(value->type == fURIDs.atomPath ||
value->type == fURIDs.atomString, LV2_WORKER_ERR_UNKNOWN);

const LV2_URID urid = ((const LV2_Atom_URID*)property)->body;
const char* const filename = (const char*)(value + 1);
@@ -986,8 +1029,8 @@ public:
String key;

try {
key = fUridStateFileMap[urid];
} DISTRHO_SAFE_EXCEPTION_RETURN("lv2_work fUridStateFileMap[urid]", LV2_WORKER_ERR_UNKNOWN);
key = fUridStateMap[urid];
} DISTRHO_SAFE_EXCEPTION_RETURN("lv2_work fUridStateMap[urid]", LV2_WORKER_ERR_UNKNOWN);

setState(key, filename);

@@ -1002,7 +1045,6 @@ public:

return LV2_WORKER_SUCCESS;
}
# endif

return LV2_WORKER_ERR_UNKNOWN;
}
@@ -1136,6 +1178,7 @@ private:
LV2_URID atomURID;
LV2_URID dpfKeyValue;
LV2_URID midiEvent;
LV2_URID patchSet;
LV2_URID patchProperty;
LV2_URID patchValue;
LV2_URID timePosition;
@@ -1162,6 +1205,7 @@ private:
atomURID(map(LV2_ATOM__URID)),
dpfKeyValue(map(DISTRHO_PLUGIN_LV2_STATE_PREFIX "KeyValueState")),
midiEvent(map(LV2_MIDI__MidiEvent)),
patchSet(map(LV2_PATCH__Set)),
patchProperty(map(LV2_PATCH__property)),
patchValue(map(LV2_PATCH__value)),
timePosition(map(LV2_TIME__Position)),
@@ -1188,7 +1232,10 @@ private:
const LV2_Worker_Schedule* const fWorker;

#if DISTRHO_PLUGIN_WANT_STATE
LV2_Atom_Forge fAtomForge;
StringToStringMap fStateMap;
UridToStringMap fUridStateMap;
LV2_URID* fUrids;
bool* fNeededUiSends;

void setState(const char* const key, const char* const newValue)
@@ -1213,10 +1260,6 @@ private:

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

# if DISTRHO_PLUGIN_WANT_STATEFILES
UridToStringMap fUridStateFileMap;
# endif
#endif

void updateParameterOutputsAndTriggers()


+ 16
- 15
distrho/src/DistrhoPluginLV2export.cpp View File

@@ -1,6 +1,6 @@
/*
* DISTRHO Plugin Framework (DPF)
* Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com>
* Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com>
*
* Permission to use, copy, modify, and/or distribute this software for any purpose with
* or without fee is hereby granted, provided that the above copyright notice and this
@@ -79,8 +79,8 @@
# define DISTRHO_LV2_UI_TYPE "UI"
#endif

#define DISTRHO_LV2_USE_EVENTS_IN (DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_TIMEPOS || (DISTRHO_PLUGIN_WANT_STATE && DISTRHO_PLUGIN_HAS_UI) || DISTRHO_PLUGIN_WANT_STATEFILES)
#define DISTRHO_LV2_USE_EVENTS_OUT (DISTRHO_PLUGIN_WANT_MIDI_OUTPUT || (DISTRHO_PLUGIN_WANT_STATE && DISTRHO_PLUGIN_HAS_UI))
#define DISTRHO_LV2_USE_EVENTS_IN (DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_TIMEPOS || DISTRHO_PLUGIN_WANT_STATE)
#define DISTRHO_LV2_USE_EVENTS_OUT (DISTRHO_PLUGIN_WANT_MIDI_OUTPUT || DISTRHO_PLUGIN_WANT_STATE)

#define DISTRHO_BYPASS_PARAMETER_NAME "lv2_enabled"

@@ -152,9 +152,7 @@ static const char* const lv2ManifestUiOptionalFeatures[] =
"ui:parent",
"ui:touch",
#endif
#if DISTRHO_PLUGIN_WANT_STATEFILES
"ui:requestValue",
#endif
nullptr
};

@@ -370,23 +368,28 @@ void lv2_generate_ttl(const char* const basename)
pluginString += "@prefix unit: <" LV2_UNITS_PREFIX "> .\n";
pluginString += "\n";

#if DISTRHO_PLUGIN_WANT_STATEFILES
// define writable states as lv2 parameters
bool hasStateFiles = false;
bool hasHostVisibleState = false;

for (uint32_t i=0, count=plugin.getStateCount(); i < count; ++i)
{
if (! plugin.isStateFile(i))
const uint32_t hints = plugin.getStateHints(i);

if ((hints & kStateIsHostVisible) == 0x0)
continue;

const String& key(plugin.getStateKey(i));
pluginString += "<" DISTRHO_PLUGIN_URI "#" + key + ">\n";
pluginString += " a lv2:Parameter ;\n";
pluginString += " rdfs:label \"" + key + "\" ;\n";
pluginString += " rdfs:range atom:Path .\n\n";
hasStateFiles = true;

if ((hints & kStateIsFilenamePath) == kStateIsFilenamePath)
pluginString += " rdfs:range atom:Path .\n\n";
else
pluginString += " rdfs:range atom:String .\n\n";

hasHostVisibleState = true;
}
#endif

// plugin
pluginString += "<" DISTRHO_PLUGIN_URI ">\n";
@@ -404,12 +407,11 @@ void lv2_generate_ttl(const char* const basename)
addAttribute(pluginString, "lv2:requiredFeature", lv2ManifestPluginRequiredFeatures, 4);
addAttribute(pluginString, "opts:supportedOption", lv2ManifestPluginSupportedOptions, 4);

#if DISTRHO_PLUGIN_WANT_STATEFILES
if (hasStateFiles)
if (hasHostVisibleState)
{
for (uint32_t i=0, count=plugin.getStateCount(); i < count; ++i)
{
if (! plugin.isStateFile(i))
if ((plugin.getStateHints(i) & kStateIsHostVisible) == 0x0)
continue;

const String& key(plugin.getStateKey(i));
@@ -417,7 +419,6 @@ void lv2_generate_ttl(const char* const basename)
}
pluginString += "\n";
}
#endif

// UI
#if DISTRHO_PLUGIN_HAS_UI


+ 1
- 1
distrho/src/lv2/atom-forge.h View File

@@ -125,7 +125,7 @@ lv2_atom_forge_set_buffer(LV2_Atom_Forge* forge, uint8_t* buf, size_t size);
not held.
*/
static inline void
lv2_atom_forge_init(LV2_Atom_Forge* forge, LV2_URID_Map* map)
lv2_atom_forge_init(LV2_Atom_Forge* forge, const LV2_URID_Map* map)
{
#if defined(__clang__)
# pragma clang diagnostic push


Loading…
Cancel
Save