Is the threadSafeRestore feature supported on the MOD host? I’m trying to schedule a worker task in the state restore function, but I get the following error: “The Worker Schedule feature is only allowed in the audio threading class”. I’m running this on MOD Desktop for debugging purposes.
And I’m writing the plugin in Rust, so it might be there’s something missing there. But I would like to know if it should work in the first place.
You should call the worker thread from the audio callback thread only.
What I do when I need the worker to do some work on state restore is, set a flag ( and the needed variables) on restore. The flag then would be checked in the audio callback, and, when it is set, the worker get called from the audio thread.
Thanks for the info! This is how I approached it originally. From reading the docs (LV2 State) on threadSafeRestore I thought the worker could be called from the restore function directly. But this will do then.
@brummer That actually works perfectly!
I have another question regarding state management. I’m trying to store an abstract path in the plugin state. Unfortunately it stores my atom:Path as an absolute path with a file://
prefix. When I save it as an atom:String it stores an absolute path without the scheme prefix. Is there something specific needed in my plugin code to store the abstract path instead of an absolute path?
I am calling the abstract_path and absolute_path functions by the way
assuming you are using a parameter, not a port, you could write to the state what ever you want. So here is a save state function I use, it’s C++
LV2_State_Status Xneuralrack::save_state(LV2_Handle instance,
LV2_State_Store_Function store,
LV2_State_Handle handle, uint32_t flags,
const LV2_Feature* const* features) {
Xneuralrack* self = static_cast<Xneuralrack*>(instance);
self->storeFile(store, handle, self->xlv2_model_file, self->engine.model_file);
self->storeFile(store, handle, self->xlv2_model_file1, self->engine.model_file1);
self->storeFile(store, handle, self->xlv2_ir_file, self->engine.ir_file);
self->storeFile(store, handle, self->xlv2_ir_file1, self->engine.ir_file1);
return LV2_STATE_SUCCESS;
}
and my storeFile function is:
inline void Xneuralrack::storeFile(LV2_State_Store_Function store,
LV2_State_Handle handle, const LV2_URID urid, const std::string file) {
store(handle, urid, file.data(), strlen(file.data()) + 1,
atom_String, LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE);
}
so basically I hand over a URID and a string. The string could contain what ever I’ve put into it.
The resulting state locks then like this:
<urn:brummer:neuralrack#Neural_Model> "/AIDA-X-Models/Boss-DS1/BossDs-1(Dist_03oclock).aidax" ;
then, on restore the state
LV2_State_Status Xneuralrack::restore_state(LV2_Handle instance,
LV2_State_Retrieve_Function retrieve,
LV2_State_Handle handle, uint32_t flags,
const LV2_Feature* const* features) {
Xneuralrack* self = static_cast<Xneuralrack*>(instance);
if (self->restoreFile(retrieve, handle, self->xlv2_model_file, &self->engine.model_file))
self->engine._ab.fetch_add(1, std::memory_order_relaxed);
if (self->restoreFile(retrieve, handle, self->xlv2_model_file1, &self->engine.model_file1))
self->engine._ab.fetch_add(2, std::memory_order_relaxed);
if (self->restoreFile(retrieve, handle, self->xlv2_ir_file, &self->engine.ir_file))
self->engine._cd.fetch_add(1, std::memory_order_relaxed);
if (self->restoreFile(retrieve, handle, self->xlv2_ir_file1, &self->engine.ir_file1))
self->engine._cd.fetch_add(2, std::memory_order_relaxed);
self-> _restore.store(true, std::memory_order_release);
return LV2_STATE_SUCCESS;
}
and my restoreFile function:
inline bool Xneuralrack::restoreFile(LV2_State_Retrieve_Function retrieve,
LV2_State_Handle handle, const LV2_URID urid, std::string *file) {
size_t size;
uint32_t type;
uint32_t fflags;
const void* name = retrieve(handle, urid, &size, &type, &fflags);
if (name) {
*file = (const char*)(name);
return (!(*file).empty() && ((*file) != "None"));
}
return false;
}
I check if for the given URID is a entry in the state file, if so I use it to restore my variable.
Thanks!
I did some more debugging. The saving and restoring of the parameter actually works. My file loads and starts playing when I reload my pedalboard. And the state value in the effect.ttl looks correct.
But the UI doesn’t show what file is loaded. So the problem I’m actually trying to solve is that the UI doesn’t initialize with the parameter value. I’m logging an empty string instead of the file name on load in javascript here:
function (event, funcs)
{
if (event.type == 'start')
{
for (var i in event.parameters)
{
if (event.parameters[i].uri === 'https://github.com/davemollen/dm-TimeWarp#sample')
{
console.log('sample', event.parameters[i].value);
break;
}
}
}
}
And also, the mod UI isn’t showing the file’s basename in this HTML:
<div mod-role="enumeration-option" mod-parameter-value="{{fullname}}">
{{basename}}
</div>
Am I supposed to send a patch message to load the parameter value in the UI maybe? Or should the parameter value just be accessible in the UI when the plugin loads?
(I have added the state:loadDefaultState
state:threadSafeRestore features for that)
From my experience, to get it going on MOD you must mark at least one port as monitoredOutputs.
To do so, add in modgui.ttl a line like
modgui:monitoredOutputs [ lv2:symbol "METER" ] ;
(I’ve just grab that line from one of my plugs, I guess you understand what I mean)
As soon you monitor one port, all ports/parameters been reported in the java script environment. That may be a bug, hence I tend to mark all output ports/parameters I would monitor in the java script as monitoredOutputs.
For reference, this is my javascript for ratatouille:
function (event, funcs)
{
function check_neural_model(icon, value)
{
if (value == 'None')
icon.find ('[rata-role=SlotA]').text ('-- choose a NAM/AIDA-X model --');
else
{
var string = value.split('\\').pop().split('/').pop();
icon.find ('[rata-role=SlotA]').text (string.substring(0, 48));
}
}
function check_neural_model1(icon, value)
{
if (value == 'None')
icon.find ('[rata-role=SlotB]').text ('-- choose a NAM/AIDA-X model --');
else
{
var string = value.split('\\').pop().split('/').pop();
icon.find ('[rata-role=SlotB]').text (string.substring(0, 48));
}
}
function check_irfile(icon, value)
{
if (value == 'None')
icon.find ('[rata-role=IrA]').text ('-- choose an IR file --');
else
{
var string = value.split('\\').pop().split('/').pop();
icon.find ('[rata-role=IrA]').text (string.substring(0, 48));
}
}
function check_irfile1(icon, value)
{
if (value == 'None')
icon.find ('[rata-role=IrB]').text ('-- choose an IR file --');
else
{
var string = value.split('\\').pop().split('/').pop();
icon.find ('[rata-role=IrB]').text (string.substring(0, 48));
}
}
function set_latency(icon, value)
{
v = value.toFixed(2);
icon.find ('[rata-role=latency]').text ('Latency: ' + `${v}` + 'ms');
}
if (event.type == 'start')
{
}
else if (event.type == 'change')
{
if (event.uri == 'urn:brummer:ratatouille#Neural_Model')
check_neural_model(event.icon, event.value);
else if (event.uri == 'urn:brummer:ratatouille#Neural_Model1')
check_neural_model1(event.icon, event.value);
else if (event.uri == 'urn:brummer:ratatouille#irfile')
check_irfile(event.icon, event.value);
else if (event.uri == 'urn:brummer:ratatouille#irfile1')
check_irfile1(event.icon, event.value);
else if (event.symbol == 'ms_latency')
set_latency(event.icon, event.value);
}
}
in my modguit.ttl file I’ve a line
modgui:monitoredOutputs [ lv2:symbol "ms_latency" ] ;
to get it going. Without a monitored Output port I got empty/non values as well.
Good to know. It doesn’t fix my initialise parameter problem yet, but I’ll keep this in there.
Do you send a patch message in your plugin to notify the UI about the parameter value on load?
listen for the event change type, not the “start”.
it is very likely that the “start” message doesnt contain the proper value, as the plugin is first loaded and only shortly after the state is applied to it.
I’m also listening to the event change type. But in my plugin this event is never called on or after initialisation. It’s only called when I change the file from my dropdown UI element.
Is the UI “just” supposed to pick up the parameter value from the state that’s stored in the pedalboards effects.ttl file?
No. It’s your plugin which should send a message to the UI. So you need a
a lv2:InputPort ,
atom:AtomPort ;
were your plug is listen, and a
a lv2:OutputPort ,
atom:AtomPort ;
were your plug is write to. When you build a “normal” UI you could make your UI thread listen to the OutputPort, on MOD, it’s the javascript which listen therefore. So, when ever a parameter in your plug change, you should send a message to the UI to inform it about that. You don’t need to do that for ControlPorts, just for lv2:Parameter.
That’s because ControlPorts been owned by the host and be only one directional (input or output) while Parameter been owned by the plug and could be input and output.
Ok, that makes a lot of sense! This must be what I’m missing then.