Open swesterfeld opened 11 months ago
yes, it probably uses the first it encounters in given m_lv2_features, as qtractor does have its own LV2_STATE__mapPath hook; it doesn't use any of the other though.
do you have any specific concern on this or wish to propose a fix anyhow?
No, its not simply a matter of lilv using the first one. It passes the array with both LV2_STATE__mapPath features to the LV2 plugin that is being saved. For my SpectMorph plugin, it would use the second one:
which means that the "lilv" implementation and not your implementation gets used. I don't consider this a SpectMorph bug, because really I wouldn't expect to get the same feature passed twice, unless the LV2 spec would clarify on this. So as it stands the qtractor saving doesn't work for SpectMorph and even if I added a workaround for that (which I don't think is the right thing to do) there may be other plugins which likewise get confused here.
Here is another piece of code that is taking the "wrong" mapPath:
I think my concern is that you cannot really convince all plugin developers to agree on the one true strategy to deal with duplicated features. So it is either qtractor cannot use lilv_state_new_from_instance
or the function needs to be altered in lilv.
yeah you're certainly right; it all actually depends on how the lilv::add_features(() works, whether to append or prepend the lilv local map_features etc. it even might have changed across lilv releases because i'm sure this wrong doing wasn't there a few releases back.
guess that it means that qtractor must have its own lilv_state_new_from_instance() and scrap the provided one; lilv should check if the caller already provides its own features (same URI) before duplicating them--but that's probably just me.
thanks for the heads up.
my take for the fix in lilv side:
diff --git a/src/state.c b/src/state.c
index c8e8f14..9c284fb 100644
--- a/src/state.c
+++ b/src/state.c
@@ -378,6 +378,12 @@ add_features(const LV2_Feature* const* features,
{
size_t n_features = 0;
for (; features && features[n_features]; ++n_features) {
+ if (map && strcmp(features[n_features]->URI, LV2_STATE__mapPath) == 0)
+ map = NULL;
+ if (make && strcmp(features[n_features]->URI, LV2_STATE__makePath) == 0)
+ make = NULL;
+ if (free && strcmp(features[n_features]->URI, LV2_STATE__freePath) == 0)
+ free = NULL;
}
const LV2_Feature** ret =
suppose this would solve the duplicates for good. :) what do you think?
Yes, I think your fix is good. On Linux/macOS it should just work in any situation. On Windows, unfortunately there is this issue: https://github.com/drobilla/lilv/issues/14 which means that if you mix lilv path allocation with host freePath implementation (or the other way around) you're in trouble. But I think that is solvable on the host side with a two easy rules:
lilv_state_new_from_instance
, set all directories to NULL
So yes, I think your solution is appropriate to fix the problem.
thanks,
an MR has been submitted to lilv upstream: https://gitlab.com/lv2/lilv/-/merge_requests/2
ps. maybe you (and others) may help and up-vote the GL ticket? please?
Here is another piece of code that is taking the "wrong" mapPath:
and here's the code that mitigates and most probably resolves to the right feature being picked up:
const LV2_State_Map_Path* mapPath = nullptr;
const LV2_State_Free_Path* freePath = nullptr;
for (int i=0; features[i] != nullptr; ++i)
{
if (mapPath == nullptr
&& std::strcmp(features[i]->URI, LV2_STATE__mapPath) == 0)
mapPath = (const LV2_State_Map_Path*)features[i]->data;
else
if (freePath == nullptr
&& std::strcmp(features[i]->URI, LV2_STATE__freePath) == 0)
freePath = (const LV2_State_Free_Path*)features[i]->data;
}
No, its not simply a matter of lilv using the first one. It passes the array with both LV2_STATE__mapPath features to the LV2 plugin that is being saved. For my SpectMorph plugin, it would use the second one:
in your case this would suffice:
LV2_State_Map_Path *map_path = nullptr;
#ifdef LV2_STATE__freePath
LV2_State_Free_Path *free_path = nullptr;
#endif
for (int i = 0; features[i]; i++)
{
if (!map_path && !strcmp (features[i]->URI, LV2_STATE__mapPath))
{
map_path = (LV2_State_Map_Path *)features[i]->data;
}
#ifdef LV2_STATE__freePath
else if (!free_path && !strcmp (features[i]->URI, LV2_STATE__freePath))
{
free_path = (LV2_State_Free_Path *)features[i]->data;
}
#endif
}
To convert the state of an instance to a string, this code is used as a first step
If the LV2 plugin stores paths, you probably want the plugin to use the LV2_STATE__mapPath feature to translate the paths during generating the state. So you pass
nullptr
for all the dirs, but providem_lv2_features
so that the plugin uses your path translation.However,
lilv_state_new_from_instance
unconditionally adds its own LV2_STATE__mapPath feature like this:So now there are two instances of the LV2_STATE__mapPath feature, and unless there is a rule about which the plugin should use in this case (which I am not aware of), it might use the first one or the second one, which means that maybe it would use your version or not.
Not sure how to fix this; maybe it could also be fixed in lilv.