Rework state path support in carla lv2 plugins

This commit is contained in:
falkTX 2020-12-30 21:47:03 +00:00
parent 523a6f0026
commit b105847cf8
No known key found for this signature in database
GPG Key ID: CDBAA37ABC74FBA0
7 changed files with 185 additions and 26 deletions

View File

@ -11,6 +11,7 @@
a lv2:UtilityPlugin, lv2:Plugin ;
lv2:optionalFeature <http://lv2plug.in/ns/lv2core#hardRTCapable> ;
lv2:optionalFeature <http://lv2plug.in/ns/ext/state#threadSafeRestore> ;
lv2:requiredFeature <http://lv2plug.in/ns/ext/buf-size#boundedBlockLength> ,
<http://lv2plug.in/ns/ext/worker#schedule> ,
@ -30,6 +31,7 @@
a lv2:InputPort, atom:AtomPort ;
atom:bufferType atom:Sequence ;
atom:supports <http://lv2plug.in/ns/ext/time#Position> ;
atom:supports <http://lv2plug.in/ns/ext/patch#Message> ;
lv2:designation lv2:control ;
lv2:index 0 ;
lv2:symbol "lv2_events_in" ;
@ -41,6 +43,7 @@
lv2:port [
a lv2:OutputPort, atom:AtomPort ;
atom:bufferType atom:Sequence ;
atom:supports <http://lv2plug.in/ns/ext/patch#Message> ;
lv2:index 1 ;
lv2:symbol "lv2_events_out" ;
lv2:name "Events Output" ;

View File

@ -9,6 +9,7 @@
a lv2:UtilityPlugin, lv2:Plugin ;
lv2:optionalFeature <http://lv2plug.in/ns/lv2core#hardRTCapable> ;
lv2:optionalFeature <http://lv2plug.in/ns/ext/state#threadSafeRestore> ;
lv2:requiredFeature <http://lv2plug.in/ns/ext/buf-size#boundedBlockLength> ,
<http://lv2plug.in/ns/ext/options#options> ,
@ -27,6 +28,7 @@
a lv2:InputPort, atom:AtomPort ;
atom:bufferType atom:Sequence ;
atom:supports <http://lv2plug.in/ns/ext/time#Position> ;
atom:supports <http://lv2plug.in/ns/ext/patch#Message> ;
lv2:designation lv2:control ;
lv2:index 0 ;
lv2:symbol "lv2_events_in" ;
@ -39,6 +41,7 @@
a lv2:OutputPort, atom:AtomPort ;
atom:bufferType atom:Sequence ;
atom:supports <http://lv2plug.in/ns/ext/midi#MidiEvent> ;
atom:supports <http://lv2plug.in/ns/ext/patch#Message> ;
lv2:index 1 ;
lv2:symbol "lv2_events_out" ;
lv2:name "Events Output" ;

View File

@ -7303,7 +7303,7 @@ public:
lv2_atom_forge_urid(&atomForge, urid);
lv2_atom_forge_key(&atomForge, kUridPatchValue);
lv2_atom_forge_path(&atomForge, path, static_cast<uint32_t>(std::strlen(path)));
lv2_atom_forge_path(&atomForge, path, static_cast<uint32_t>(std::strlen(path))+1);
lv2_atom_forge_pop(&atomForge, &forgeFrame);

View File

@ -140,7 +140,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)
{
lv2_atom_forge_set_buffer(forge, NULL, 0);
forge->Blank = map->map(map->handle, LV2_ATOM__Blank);

View File

@ -323,10 +323,12 @@ static void writePluginFile(const NativePluginDescriptor* const pluginDesc,
// optional
if (pluginDesc->hints & NATIVE_PLUGIN_IS_RTSAFE)
text += " lv2:optionalFeature <" LV2_CORE__hardRTCapable "> ;\n\n";
text += " lv2:optionalFeature <" LV2_CORE__hardRTCapable "> ;\n";
if (pluginDesc->hints & NATIVE_PLUGIN_HAS_INLINE_DISPLAY)
text += " lv2:optionalFeature <" LV2_INLINEDISPLAY__queue_draw "> ;\n";
if ((pluginDesc->hints & NATIVE_PLUGIN_USES_STATE) || (pluginDesc->hints & NATIVE_PLUGIN_NEEDS_UI_OPEN_SAVE))
text += " lv2:optionalFeature <" LV2_STATE__threadSafeRestore "> ;\n";
text += "\n";
// required
text += " lv2:requiredFeature <" LV2_BUF_SIZE__boundedBlockLength "> ,\n";
@ -404,6 +406,8 @@ static void writePluginFile(const NativePluginDescriptor* const pluginDesc,
{
text += " atom:supports <" LV2_TIME__Position "> ;\n";
}
if (pluginDesc->hints & NATIVE_PLUGIN_NEEDS_UI_OPEN_SAVE)
text += " atom:supports <" LV2_PATCH__Message "> ;\n";
text += " lv2:designation lv2:control ;\n";
text += " lv2:index " + String(portIndex++) + " ;\n";
@ -482,6 +486,8 @@ static void writePluginFile(const NativePluginDescriptor* const pluginDesc,
if (pluginDesc->midiOuts > 0)
text += " atom:supports <" LV2_MIDI__MidiEvent "> ;\n";
if (pluginDesc->hints & NATIVE_PLUGIN_NEEDS_UI_OPEN_SAVE)
text += " atom:supports <" LV2_PATCH__Message "> ;\n";
text += " lv2:index " + String(portIndex++) + " ;\n";

View File

@ -54,9 +54,11 @@ public:
fProgramDesc({0, 0, nullptr}),
#endif
kIgnoreParameters(std::strncmp(desc->label, "carla", 5) == 0),
fAtomForge(),
fMidiEventCount(0),
fLastProjectPath(),
fLoadedFile(),
fNeedsNotifyFileChanged(false),
fPluginNeedsIdle(false),
fWorkerUISignal(0)
{
@ -89,6 +91,9 @@ public:
fHost.ui_open_file = host_ui_open_file;
fHost.ui_save_file = host_ui_save_file;
fHost.dispatcher = host_dispatcher;
carla_zeroStruct(fAtomForge);
lv2_atom_forge_init(&fAtomForge, fUridMap);
}
~NativePlugin()
@ -259,7 +264,8 @@ public:
{
const LV2_Atom_Object* const obj = (const LV2_Atom_Object*)(&event->body);
if (obj->body.otype == fURIs.patchSet) {
if (obj->body.otype == fURIs.patchSet)
{
// Get property URI.
const LV2_Atom* property = nullptr;
lv2_atom_object_get(obj, fURIs.patchProperty, &property, 0);
@ -288,6 +294,11 @@ public:
static_cast<uint32_t>(std::strlen(filepath) + 1U),
filepath);
}
else if (obj->body.otype == fURIs.patchGet)
{
if (fDescriptor->hints & NATIVE_PLUGIN_NEEDS_UI_OPEN_SAVE)
fNeedsNotifyFileChanged = true;
}
continue;
}
@ -317,6 +328,54 @@ public:
break;
}
}
if (fNeedsNotifyFileChanged)
{
fNeedsNotifyFileChanged = false;
uint8_t atomBuf[4096];
LV2_Atom_Forge atomForge = fAtomForge;
lv2_atom_forge_set_buffer(&atomForge, atomBuf, sizeof(atomBuf));
LV2_Atom_Forge_Frame forgeFrame;
lv2_atom_forge_object(&atomForge, &forgeFrame, 0, fURIs.patchSet);
lv2_atom_forge_key(&atomForge, fURIs.patchProperty);
/* */ if (std::strcmp(fDescriptor->label, "audiofile") == 0) {
lv2_atom_forge_urid(&atomForge, fURIs.carlaFileAudio);
} else if (std::strcmp(fDescriptor->label, "midifile") == 0) {
lv2_atom_forge_urid(&atomForge, fURIs.carlaFileMIDI);
} else {
lv2_atom_forge_urid(&atomForge, fURIs.carlaFile);
}
lv2_atom_forge_key(&atomForge, fURIs.patchValue);
lv2_atom_forge_path(&atomForge,
fLoadedFile.buffer(),
static_cast<uint32_t>(fLoadedFile.length()+1));
lv2_atom_forge_pop(&atomForge, &forgeFrame);
LV2_Atom* const atom = (LV2_Atom*)atomBuf;
LV2_Atom_Sequence* const seq = fPorts.eventsOut[0];
Ports::EventsOutData& mData(fPorts.eventsOutData[0]);
if (sizeof(LV2_Atom_Event) + atom->size <= mData.capacity - mData.offset)
{
LV2_Atom_Event* const aev = (LV2_Atom_Event*)(LV2_ATOM_CONTENTS(LV2_Atom_Sequence, seq) + mData.offset);
aev->time.frames = 0;
aev->body.size = atom->size;
aev->body.type = atom->type;
std::memcpy(LV2_ATOM_BODY(&aev->body), atom + 1, atom->size);
const uint32_t size = lv2_atom_pad_size(static_cast<uint32_t>(sizeof(LV2_Atom_Event) + atom->size));
mData.offset += size;
seq->atom.size += size;
}
}
}
fDescriptor->process(fHandle, fPorts.audioCVIns, fPorts.audioCVOuts, frames, fMidiEvents, fMidiEventCount);
@ -324,7 +383,7 @@ public:
if (fPluginNeedsIdle)
{
fPluginNeedsIdle = false;
const char* const msg = "idle";
const char* const msg = "_idle_";
const size_t msgSize = std::strlen(msg);
fWorker->schedule_work(fWorker->handle, static_cast<uint32_t>(msgSize + 1U), msg);
}
@ -453,22 +512,57 @@ public:
if (fDescriptor->hints & NATIVE_PLUGIN_NEEDS_UI_OPEN_SAVE)
{
if (fLoadedFile.isEmpty())
return LV2_STATE_SUCCESS;
const LV2_State_Free_Path* freePath = nullptr;
const LV2_State_Map_Path* mapPath = nullptr;
if (features != nullptr)
{
for (int i=0; features[i] != nullptr; ++i)
{
/**/ if (freePath == nullptr && std::strcmp(features[i]->URI, LV2_STATE__freePath) == 0)
freePath = (const LV2_State_Free_Path*)features[i]->data;
else if (mapPath == nullptr && std::strcmp(features[i]->URI, LV2_STATE__mapPath) == 0)
mapPath = (const LV2_State_Map_Path*)features[i]->data;
}
}
if (mapPath == nullptr || mapPath->abstract_path == nullptr)
return LV2_STATE_ERR_NO_FEATURE;
char* path = mapPath->abstract_path(mapPath->handle, fLoadedFile.buffer());
store(handle,
fUridMap->map(fUridMap->handle, "http://kxstudio.sf.net/ns/carla/file"),
fLoadedFile.buffer(),
fLoadedFile.length()+1,
path,
std::strlen(path)+1,
fURIs.atomPath,
LV2_STATE_IS_POD);
LV2_STATE_IS_POD|LV2_STATE_IS_PORTABLE);
if (freePath != nullptr && freePath->free_path != nullptr)
freePath->free_path(freePath->handle, path);
#ifndef CARLA_OS_WIN
// this is not safe to call under windows
else
std::free(path);
#endif
return LV2_STATE_SUCCESS;
}
if ((fDescriptor->hints & NATIVE_PLUGIN_USES_STATE) == 0 || fDescriptor->get_state == nullptr)
return LV2_STATE_ERR_NO_FEATURE;
return LV2_STATE_ERR_UNKNOWN;
if (char* const state = fDescriptor->get_state(fHandle))
{
store(handle, fUridMap->map(fUridMap->handle, "http://kxstudio.sf.net/ns/carla/chunk"),
state, std::strlen(state)+1, fURIs.atomString, LV2_STATE_IS_POD|LV2_STATE_IS_PORTABLE);
store(handle,
fUridMap->map(fUridMap->handle, "http://kxstudio.sf.net/ns/carla/chunk"),
state,
std::strlen(state)+1,
fURIs.atomString,
LV2_STATE_IS_POD|LV2_STATE_IS_PORTABLE);
std::free(state);
return LV2_STATE_SUCCESS;
}
@ -476,8 +570,10 @@ public:
return LV2_STATE_ERR_UNKNOWN;
}
LV2_State_Status lv2_restore(const LV2_State_Retrieve_Function retrieve, const LV2_State_Handle handle,
uint32_t flags, const LV2_Feature* const* const features)
LV2_State_Status lv2_restore(const LV2_State_Retrieve_Function retrieve,
const LV2_State_Handle handle,
uint32_t flags,
const LV2_Feature* const* const features)
{
saveLastProjectPathIfPossible(features);
@ -490,21 +586,53 @@ public:
const void* const data = retrieve(handle,
fUridMap->map(fUridMap->handle, "http://kxstudio.sf.net/ns/carla/file"),
&size, &type, &flags);
if (size <= 1 || type == 0)
return LV2_STATE_ERR_NO_PROPERTY;
CARLA_SAFE_ASSERT_RETURN(type == fURIs.atomPath, LV2_STATE_ERR_UNKNOWN);
const LV2_State_Free_Path* freePath = nullptr;
const LV2_State_Map_Path* mapPath = nullptr;
if (features != nullptr)
{
for (int i=0; features[i] != nullptr; ++i)
{
/**/ if (freePath == nullptr && std::strcmp(features[i]->URI, LV2_STATE__freePath) == 0)
freePath = (const LV2_State_Free_Path*)features[i]->data;
else if (mapPath == nullptr && std::strcmp(features[i]->URI, LV2_STATE__mapPath) == 0)
mapPath = (const LV2_State_Map_Path*)features[i]->data;
}
}
if (mapPath == nullptr || mapPath->absolute_path == nullptr)
return LV2_STATE_ERR_NO_FEATURE;
const char* const filename = (const char*)data;
fLoadedFile = filename;
fDescriptor->set_custom_data(fHandle, "file", filename);
char* const absolute_filename = mapPath->absolute_path(mapPath->handle, filename);
fLoadedFile = absolute_filename;
if (freePath != nullptr && freePath->free_path != nullptr)
freePath->free_path(freePath->handle, absolute_filename);
#ifndef CARLA_OS_WIN
// this is not safe to call under windows
else
std::free(absolute_filename);
#endif
fNeedsNotifyFileChanged = true;
fDescriptor->set_custom_data(fHandle, "file", fLoadedFile);
return LV2_STATE_SUCCESS;
}
if ((fDescriptor->hints & NATIVE_PLUGIN_USES_STATE) == 0 || fDescriptor->set_state == nullptr)
return LV2_STATE_ERR_NO_FEATURE;
return LV2_STATE_ERR_UNKNOWN;
size = type = 0;
const void* const data = retrieve(handle, fUridMap->map(fUridMap->handle, "http://kxstudio.sf.net/ns/carla/chunk"), &size, &type, &flags);
const void* const data = retrieve(handle,
fUridMap->map(fUridMap->handle, "http://kxstudio.sf.net/ns/carla/chunk"),
&size, &type, &flags);
if (size == 0)
return LV2_STATE_ERR_UNKNOWN;
@ -526,13 +654,14 @@ public:
{
const char* const msg = (const char*)data;
if (fDescriptor->hints & NATIVE_PLUGIN_REQUESTS_IDLE)
if (std::strcmp(msg, "_idle_") == 0)
{
if (std::strcmp(msg, "idle") == 0)
if (fDescriptor->hints & NATIVE_PLUGIN_REQUESTS_IDLE)
{
fDescriptor->dispatcher(fHandle, NATIVE_PLUGIN_OPCODE_IDLE, 0, 0, nullptr, 0.0f);
return LV2_WORKER_SUCCESS;
}
return LV2_WORKER_ERR_UNKNOWN;
}
if (fDescriptor->hints & NATIVE_PLUGIN_NEEDS_UI_OPEN_SAVE)
@ -949,11 +1078,13 @@ private:
// carla as plugin does not implement lv2 parameter API yet, needed for feedback
const bool kIgnoreParameters;
LV2_Atom_Forge fAtomForge;
uint32_t fMidiEventCount;
NativeMidiEvent fMidiEvents[kMaxMidiEvents];
CarlaString fLastProjectPath;
CarlaString fLoadedFile;
volatile bool fNeedsNotifyFileChanged;
volatile bool fPluginNeedsIdle;
int fWorkerUISignal;

View File

@ -963,10 +963,12 @@ public:
if (frames == 0)
return false;
// init midi out data
// init event out data
if (fPorts.numMidiOuts > 0 || fPorts.hasUI)
{
for (uint32_t i=0; i<fPorts.numMidiOuts; ++i)
const uint32_t count = fPorts.numMidiOuts > 0 ? fPorts.numMidiOuts : 1;
for (uint32_t i=0; i < count; ++i)
{
LV2_Atom_Sequence* const seq(fPorts.eventsOut[i]);
CARLA_SAFE_ASSERT_CONTINUE(seq != nullptr);
@ -1371,6 +1373,7 @@ protected:
{
eventsOut = new LV2_Atom_Sequence*[1];
eventsOut[0] = nullptr;
eventsOutData = new EventsOutData[1];
}
if (const uint32_t numAudioCVIns = numAudioIns+numCVIns)
@ -1519,6 +1522,7 @@ protected:
LV2_URID carlaFileMIDI;
LV2_URID midiEvent;
LV2_URID patchProperty;
LV2_URID patchGet;
LV2_URID patchSet;
LV2_URID patchValue;
LV2_URID timePos;
@ -1549,6 +1553,7 @@ protected:
carlaFileMIDI(0),
midiEvent(0),
patchProperty(0),
patchGet(0),
patchSet(0),
patchValue(0),
timePos(0),
@ -1580,6 +1585,7 @@ protected:
carlaFileMIDI = uridMap->map(uridMap->handle, "http://kxstudio.sf.net/carla/file/midi");
midiEvent = uridMap->map(uridMap->handle, LV2_MIDI__MidiEvent);
patchProperty = uridMap->map(uridMap->handle, LV2_PATCH__property);
patchGet = uridMap->map(uridMap->handle, LV2_PATCH__Get);
patchSet = uridMap->map(uridMap->handle, LV2_PATCH__Set);
patchValue = uridMap->map(uridMap->handle, LV2_PATCH__value);
timePos = uridMap->map(uridMap->handle, LV2_TIME__Position);
@ -2344,12 +2350,11 @@ const LV2_RDF_Descriptor* lv2_rdf_new(const LV2_URI uri, const bool loadPresets)
// ----------------------------------------------------------------------------------------------------------------
// Set Plugin Parameters
{
std::map<std::string, LV2_RDF_Parameter> parameters;
Lilv::Nodes patchWritableNodes(lilvPlugin.get_value(lv2World.patch_writable));
if (const uint numParameters = patchWritableNodes.size())
{
rdfDescriptor->Parameters = new LV2_RDF_Parameter[numParameters];
uint numUsed = 0;
LILV_FOREACH(nodes, it, patchWritableNodes)
{
@ -2371,10 +2376,10 @@ const LV2_RDF_Descriptor* lv2_rdf_new(const LV2_URI uri, const bool loadPresets)
lilv_node_free(typeNode);
}
LV2_RDF_Parameter& rdfParam(rdfDescriptor->Parameters[numUsed++]);
CARLA_SAFE_ASSERT_CONTINUE(patchWritableNode.is_uri());
++numUsed;
LV2_RDF_Parameter rdfParam;
rdfParam.URI = carla_strdup(patchWritableNode.as_uri());
// ----------------------------------------------------------------------------------------------------
@ -2557,9 +2562,20 @@ const LV2_RDF_Descriptor* lv2_rdf_new(const LV2_URI uri, const bool loadPresets)
lilv_node_free(unitUnitNode);
}
parameters[rdfParam.URI] = rdfParam;
}
CARLA_SAFE_ASSERT_UINT2(parameters.size() == numUsed, parameters.size(), numUsed);
rdfDescriptor->Parameters = new LV2_RDF_Parameter[numUsed];
rdfDescriptor->ParameterCount = numUsed;
numUsed = 0;
for (std::map<std::string, LV2_RDF_Parameter>::iterator it = parameters.begin(), end = parameters.end();
it != end; ++it)
{
rdfDescriptor->Parameters[numUsed++] = it->second;
}
}
lilv_nodes_free(const_cast<LilvNodes*>(patchWritableNodes.me));