Closed unfa closed 3 years ago
Hi!
OK you are the first report we have to it. Can I ask a couple of rudimentary questions?
Are you using the VST2, VST3 or LV2? Are you using Surge 1.6.5? Do you have the ability to build surge from source if we have a debugging build? Do you have a DAW file for some easily available DAW which shows this?
We have recently been adding automated tests and stream/unstream invariance is one of the things we partly test. But some more details could help us loo for sure.
Thanks
Oh also: I’ve used surge for a year now (mac AU and VST3) as havre many of our users and I haven’t seen this behavior once. So step one is pinning down what code path you are on which we are not so we can look there,hence my block and tackle questions above.
Thanks!
One last thought: The activity you describe seems like some sort of race condition between the DAW restoring the state and the internal state of the synth. In the VST2/3/AU implementations we don’t have this problem because the synth is the sole master of the values, but I understand in the LV2 there is a question about whether the patch is mastered by the LV2 or by the engine.
Honestly I don’t understand that statement all that well. @jpcima and @falkTX wrote the LV2 implementation. But the behavior you describe sounds like some or all of the values are being reset to an init value, rather than the value the synth has.
So I’m going to make a wild guess that
So if you are using the LV2 confirming that would be good.
I’ll note the VST3 is also free software and is way better tested and exercised, although I understand Linux DAW support for it is, charitably, spotty still.
Best!
I encounter the same bug with the lv2 version (v 1.6.4 here) in Ardour. I've already talked about it with @jpcima on IRC, it's a known bug. No problem with the vst3 version in Reaper (native linux). Presets are recalled nicely. Unfortunately, vst3's support is limited or doesn't exist in floss daws, so, a fix for lv2 would be appreciated.
I have definitely experienced this using LV2 in Ardour, Carla and Zrythm. I am usually building the latest git version using AUR (I'm working on Manjaro Linux).
I think I had the same issue with VST2, but I need to check it again before I can be sure. If you have a debug build I could use that to provide more information.
@baconpaul, thank you for your help!
FYI, lv2 works fine in Qtractor, at least, patch's recalls works fine.
OK a quick poll over in the facebook group seems to show that folks in VST2/3/AU land dont’ see this so I am going to go with my hunch, which apparently @jpcima has confirmed, that this is an order of operations in the LV2 default restoring world.
@sub26nico did @jpcima say if it was a known bug in LV2, SurgeLV2, or the DAW?
I’ve never run Ardour or Zrythm and have only used Carla as the standalone linux vst2 launcher not as a daw, and I didn’t write any of the LV2 code, so it would be super hard for me to debug this. If you are chatting with @jpcima in IRC pointing him at this issue would be appreciated and maybe we can get a plan of attack?
@sub26nico did @jpcima say if it was a known bug in LV2, SurgeLV2, or the DAW?
an Surge lv2 version iirc
If you are chatting with @jpcima in IRC pointing him at this issue would be appreciated and maybe we can get a plan of attack?
Already done, I'm waiting his answer
Alright thanks both @unfa and @sub26nico !!
Thank you! I'd be perfectly happy with LV2 (I actually prefer that plugin format as a fully open and rooted in FOSS culture). So if this can be fixed in LV2 I'm going to be very happy, and I will most likely start making videos about Surge.
Yeah the behavior you describe is exactly consistent with surge getting someone sending it default values onto the engine from the outside
Assume I have never used any of these daws. How do you do even basic stuff? Do you have a video on that somewhere in case I do end up looking
But full disclosure I am heads down on a skinning engine for the ui now so would be way way better if the original authors could look.
Ok, turns out I didn't probably ever use VST3 on Linux, because the AUR PKBUILD I used to install installs LV2 and VST3, adn Ardour probably didn't ever load VST3.
Hello, The state restoring trouble is LV2-only, I think.
It arises from some particular restrictions, which are part of LV2 specifications, precisely:
This did not fit too well with the core Surge, which I think modeled approximately after VST. As I remember, for instance, LV2 Surge was disallowed to report a parameter to host when it is manipulated by MIDI, because of point 1. above.
(I recall this from memory, forgive if I'm wrong.)
About the plan on how to fix it:
setParameter
s which are part of the patch must occur from GUI side (even if redundant).This will let the host be noticed of the parameter changes, which lets them save and restore correctly. Otherwise, the parameters known to the host will remain these before the patch change, which means it would save the values which are out of date.
On this, I don't think there is a defined order in which the parameters and the blob will arrive to plugin, which makes it perhaps more complicated.
I can dedicate some time to fix this, also I'll take some suggestions if you have some. Thanks :)
Ok I merged your change but let me look at your comment more carefully and respond
OK so I read your note. I am surprised any VI can meet those constraints! So let me step back for one second.
I think you all have a version of dexed running as an LV2 yeah? How did you deal with the engine loading sysex? The cartridge manager seems to do exactly the same thing as the surge patch manager, and from being in the code, seems to do it in a very similar way. How did that work?
As to surge's architecture it works like this
So that means the job of the DAW is as follows
When constructing a surge anew, scan the surge instance you created for the value of all parameters using the get_f01 mechanism. You can see this in the VST2/3/AU code pretty clearly.
When constructing a surge from a saved state, do #1 then restore the saved blob which the prior surge gave you using the loadRaw and loadStateFromDAW methods on the synth
At a later date, if you open the UI, the UI will build itself from the surge patch. Opening the UI should do nothing with the parameters
Persisting any state other than the blob sent from saveRaw will give you an inconsistency. Initializing the parameters by any mechanism other than either initialize / scan synth or initialize / load raw / scan synth will give you an inconsistency
I'm not sure what that means for LV2 but that's how surge works in VST2, VST3, AU, Headless.
If you want to use a subset of the synth there are other things you can do to manipulate the internal data structures directly (that's how rack and the FX works) but I strongly recommend against that for a full-synth build, since rack and FX don't have things like the modulation matrix.
Hope that helps!
Thanks for writing this explanation.
Maybe I can quote LV2 State, which can explain some things better than I do.
From https://lv2plug.in/ns/ext/state/state.html :
This extension defines a simple mechanism which allows hosts to save and restore a plugin instance's state. The goal is for an instance's state to be completely described by port values (as with all LV2 plugins) and a simple dictionary.
With "port values" being synonym of parameters, and state "dictionary" being the raw file. LV2 enforces by specification the separation of these as 2 distinct elements.
I think you all have a version of dexed running as an LV2 yeah? How did you deal with the engine loading sysex? The cartridge manager seems to do exactly the same thing as the surge patch manager, and from being in the code, seems to do it in a very similar way. How did that work?
In typical LV2, I think preset storage would be entirely managed in UI, it will only dispatch to DSP the parameters and state items which compose a chosen preset. As I understand, LV2 favors program management on host side, and on plugin side is quite severely restricted.
On the PR which has been merged, it's a way to force Surge somewhat into operating in similar logic.
When constructing a surge from a saved state, do #1 then restore the saved blob which the prior surge gave you using the loadRaw and loadStateFromDAW methods on the synth
The steps are pretty much that which are followed, with this exception being mandated in LV2: parameters will be always persisted separate from the Raw (duplicated), and resent on next load.
I remember falktx has once mentioned me an edge case, where the two will not be necessarily identical, but the memory of this escapes me at the moment.
For now, I have not certainty whether this peculiar of way state loading may lead or not to inconsistency, but I have not experienced it in testing. (it could be Parameters and then Raw, or the reverse)
Gotcha. That idea of storing parameters as distinct from the streaming provided by the DAW is kind of unique to LV2. (It’s sort of what JUCE encourages you to do also but doesn’t require and juce then consistently maps it into the underlying single-store DAW state). It is also surely where the problem is coming from. It’s also a bold design choice to be a requirement (as opposed to an option). I suppose an LV2 could also advertise “ignore my params; I’ve got this” and then just hand you a blob as a mode. Then it would look just like the other flavors. but that’s not a surge issue.
But what @unfa described would happen if, say, some default values (which I know you dump into the ttl) get sent to the synth after you restore the DAW state. I’m imagining something (naively) like
Thread 1 A restore parameters to values B send parameters to synth C restore daw state
Thread 2 A make parameters with defaults B send parameters to synth
So clearly if the order is 2A 2B 1A 2B 1C we are fine. If the order is 2A 1A 1B 1C 2B we will get the state (but not the modulation) reset to the init default. If the order is 2A 1A 1C (1B/2B mixed) then we would get exactly what @unfa reports.
Can anyone tell me or point me at a youtube video or anything about how to even run one of these LV2 daws on linux and save a state? You say Carla is breaking - I’ve not really used it. How is it breaking?
Finally, if the LV2 is doing what you say it should be doing properly, then the assertion should be true that restore params / restore state will leave the params unchanged, since the streaming of the state contains the value of the params. That’s something we could assert in the LV2 code to catch the bug. Basically snap the .val.f of every ptr before you do a loadState and after in a throwaway array. If any change then you have a problem. (This constraint is NOT true in the VST2/3/AU since the only persistence of state in those environments is the DAW XML).
Can anyone tell me or point me at a youtube video or anything about how to even run one of these LV2 daws on linux and save a state?
It's more easy for me to explain in text, how to get a working dev workflow.
Prerequisite:
Installing:
/usr/share/Surge
Host:
patchbay
tab, hook up Surge outputs to system
~/MySurgeSession.carxp
)From this point on, you can write following in the Surge source dir and get one-line build+restore.
./build-linux.sh build -p lv2 && carla ~/MySurgeSession.carxp
(except restore not working at the moment, but you get the idea..)
Very useful thanks. I was able to follow these steps and see the bug. Not fix it, but see it.
Hey next question (sorry for all of these)
When I run carla and put a std::cout in the code, I don't see stdout in the terminal. Clearly that is being routed somewhere else. Do you or @falkTX know where?
The logs tab in the GUI. The first page in the settings allows you to turn that off as needed.
Thanks!!!!
Alright I have a fix to this.
Basically carla calls connectPort before restoreState and then you check if a value has changed vs a cache, but you build that cache after connectPort and before restoreState so it gets the default values, not the streamed values.
If, in restoreState, you write onto the float * which are each of the port values, Carla restores properly.
Let me put my diff up here in a branch for you guys to review and test, OK?
I have tested Surge again in Ardour 5.12 yesterday and I had patch corruption happening without closing ad opening the UI.
I would rewind the transport to play a part in my timeline again and LFO and insert effect settings would change on their own. I have video proof of this: https://youtu.be/Rvf0Ihe8_zg
Yeah the state management is still not right. I’m working on it with talk and jp
Ok @falkTX here is what is happening in Carla
I save a carlxp file following directions above. I save it with parameter 118 (pitch) set to .8 and the default is .5
When I restore I get the following in order
So: what is the mechanism by which Carla restores values of the ports before run is called and what is surge doing to not get the ports restored properly even though the xml is?
Like do I need to cal super::save state in save state or some such?
Do you see 0.8 as the pitch value in the carla state file? If not, what carla is doing seems correct to me.
Note that the first restore
is to startup the plugin in a good default state.
Could be ignored when loading a project I guess, but should not affect things here.
The connection_port call needs to be irrelevant.
Only state restore
and run
matter.
Yeah right so the carla state file contains the 0.8 and I think that I have found the problem
On the first restore - when presumably I get the settings - @jpcima is returning LV2_BAD_CHUNK if there's no custom so we drop the values
So: what should restoreState do with that first chunk? That's clearly the problem. Even if I return success the first chunk which contains the params is not being applied.
OK I pushed to baconpaul/lv2-1577
a version which shows the error.
Build with that, load surge, open a preset, modify OSC1 pitch slider up some, save. In the carlxp you will see that param 118 (which is that OSC1 pitch slider) has a value.
Then restore that carlxp. You will se that port 118 never gets the value set onto it, and is 0.5, and that 0.5 restores over the synth state.
So the problem is: The port never gets restored from the carlxp file.
I think it is because of the first call to restoreState and something the code is missing in that if block
something is odd regarding carla and handling state, from what I see..
I agree with that! But is it Carla surge specific or just Carla and anyone who implements the lv2 State?
ok I see the issue, it is surge after all. some of the port symbols are duplicated, so carla ends up setting the port "Osc1_Pitch" twice.
the generated ttl has this:
a lv2:InputPort, lv2:ControlPort ;
lv2:index 118 ;
lv2:symbol "Osc1_Pitch" ;
lv2:name "Osc1 Pitch" ;
lv2:default 0.5 ;
lv2:minimum 0 ;
lv2:maximum 1 ;
and then a bit later:
a lv2:InputPort, lv2:ControlPort ;
lv2:index 389 ;
lv2:symbol "Osc1_Pitch" ;
lv2:name "Osc1 Pitch" ;
lv2:default 0.5 ;
lv2:minimum 0 ;
lv2:maximum 1 ;
that makes it impossible to save the state in a proper way. so all session files made with surge-lv2 until now are all broken :(
Oh that’s super easy to fi! I just need to chang @jpcima ttl generator to use the streaming name rather than the display name.
Presumably name must be unique and symbol need not be?
(I am out seeing a show tonight but I can do this tomorrow)
Also is there an lv2 linter or something we should run? I use the au and vst3 validation to check those versions
symbol must be unique, name does not have to be. what is this streaming name?
there is an lv2lint tool yes. it would have caught this issue I guess..
Surge has a parameter name which is stable and unique for streaming and a display name. Seems we use display for both symbol and name. I will modify symbol to use the unique name
The lv2 code is using getParameterName
though..
In any case, I can get a fix soon
Here is a quick&dirty patch that works here:
diff --git a/src/lv2/SurgeLv2Export.cpp b/src/lv2/SurgeLv2Export.cpp
index f5dc871..0a4fa12 100644
--- a/src/lv2/SurgeLv2Export.cpp
+++ b/src/lv2/SurgeLv2Export.cpp
@@ -1,5 +1,7 @@
#include "SurgeLv2Wrapper.h"
+#include <algorithm>
#include <fstream>
+#include <vector>
#if defined _WIN32
#define LV2_DLL_SUFFIX ".dll"
@@ -81,7 +83,9 @@ void lv2_generate_ttl(const char* baseName)
(c >= '0' && c <= '9');
};
if (name.empty())
+ {
name = '_';
+ }
else
{
if (!isLeadChar(name[0]))
@@ -92,6 +96,21 @@ void lv2_generate_ttl(const char* baseName)
name[i] = '_';
}
}
+
+ // Do not allow identical symbols
+ static std::vector<std::string> usedSymbols;
+
+ if (std::find(usedSymbols.begin(), usedSymbols.end(), name) != usedSymbols.end())
+ {
+ name += "_2";
+
+ if (std::find(usedSymbols.begin(), usedSymbols.end(), name) != usedSymbols.end())
+ {
+ throw "parameter names are not unique enough";
+ }
+ }
+ usedSymbols.push_back(name);
+
return name;
})(pName);
cleaned the patch just a bit.
btw, off-topic, but c++ std is pretty awful. I was trying to do the same I had with juce code, but damn there is no string "replace", array/vector "contains" errrr
btw, I am combining that with your branch changes. those are needed too
not sure if this helps but the output of ./lv2lint https://surge-synthesizer.github.io/lv2/surge
./lv2lint 0.5.7
Copyright (c) 2016-2020 Hanspeter Portner (dev@open-music-kontrollers.ch)
Released under Artistic License 2.0 by Open Music Kontrollers
<https://surge-synthesizer.github.io/lv2/surge>
[FAIL] Symbols
binary exports superfluous globally visible symbols:
* _ZGVcN4v___log_finite
* _ZNKSt12experimental10filesystem2v17__cxx114path17_M_find_extensionEv
* _ZNSt10filesystem10equivalentERKNS_7__cxx114pathES3_
* _ZNSt10filesystem7__cxx114path15_M_add_root_dirEm
* _ZNSt12experimental10filesystem2v17__cxx114path14_S_convert_locEPKcS5_RKSt6locale
* _ZNSt12experimental10filesystem2v16statusERKNS1_7__cxx114pathE
* _ZNSt10filesystem9proximateERKNS_7__cxx114pathES3_RSt10error_code
* _ZNSt10filesystem7__cxx1116filesystem_errorD0Ev
* _ZNKSt12experimental10filesystem2v17__cxx114path18has_root_directoryEv
* _ZNSt10filesystem5spaceERKNS_7__cxx114pathE
* ... there is more, but the rest is being truncated
seeAlso: <http://lv2plug.in/ns/lv2core#binary>
[WARN] Author Email
foaf:email not found
seeAlso: <http://lv2plug.in/ns/lv2core#project>
<https://surge-synthesizer.github.io/lv2/surge#UI>
[FAIL] Symbols
binary exports superfluous globally visible symbols:
* _ZGVcN4v___log_finite
* _ZNKSt12experimental10filesystem2v17__cxx114path17_M_find_extensionEv
* _ZNSt10filesystem10equivalentERKNS_7__cxx114pathES3_
* _ZNSt10filesystem7__cxx114path15_M_add_root_dirEm
* _ZNSt12experimental10filesystem2v17__cxx114path14_S_convert_locEPKcS5_RKSt6locale
* _ZNSt12experimental10filesystem2v16statusERKNS1_7__cxx114pathE
* _ZNSt10filesystem9proximateERKNS_7__cxx114pathES3_RSt10error_code
* _ZNSt10filesystem7__cxx1116filesystem_errorD0Ev
* _ZNKSt12experimental10filesystem2v17__cxx114path18has_root_directoryEv
* _ZNSt10filesystem5spaceERKNS_7__cxx114pathE
* ... there is more, but the rest is being truncated
seeAlso: <http://lv2plug.in/ns/lv2core#binary>
[WARN] Instance Access
usage of instance-access is highly discouraged
seeAlso: <http://lv2plug.in/ns/ext/instance-access>
[WARN] Mixed DSP/UI
mixing DSP and UI code in same binary is discouraged
seeAlso: <http://lv2plug.in/ns/extensions/ui#>
Please note that LV2 state relates to port symbols. (LV2 does not use parameter indexes for state) So once we release, we cannot change them. not without breaking compatibility with everyone's saved presets/projects.
Cool and glad that works but I’ll merge a version which uses our built in unique name to match the xml. Will make patch tomorrow - thanks folks!!!
Oh and the name I will use is the same name we use in our xml blob so if it isn’t stable way more than lv2 will break! Thanks for the help here
Ha OK when I replace the symbol with the streaming name everything works. Will take me a wee bit to get a PR together this morning but should be a fix by lunchtime.
Alright PR #1596 is the answer. If you can look @falkTX that would be awesome! It now works perfectly for me in carla and uses proper unique names for every parameter (the same names we use to save the .fxp)
OK @unfa I have pushed a fix based on the conversation @falkTX and I had. In Carla it now streams and unstraps properly and works with automation. We're both confident this is correct.
The problem was that LV2 ports were mis-labeled. So our fix means everything works. Bit it also means that saved sessions from the buggy state won't reload properly. Sorry - but there's no other way to fix the problem. Since those sessions were buggy it's not that bad.
I want to mark the release as 1.6.6 tomorrow morning. Is it possible for you to build from master and test today? The git commit for the corrected version is 8dc0cf88a1537. I'm not sure how you are acquiring surge but that git commit checkout and a build or the pre-built .deb nightly which will be on our website in about 30 minutes should both contain the fix.
Thanks!
I've been trying to get into Surge for a good while now.
My platform of choice is Linux, and until recently that's the only OS group I've been running Surge on. However recently I had something similar happen to me under Windows 10.
If I close the Surge window (GUI) and later open it up again in my DAW - at that moment the patch gets corrupted, usually in very specific ways.
Most often the oscillators all get reset to the default Sawtooth. Often LFO settings are corrupted too. Sometimes the patch is completely reset to Init state.
If I store my patch first before closing the GUI I can just reload it immediately after it gets corrupted, but it'll break again after I reopen the Surge window for another time.
As you can expect this is a big problem and prevents me from being able to do anything more than fiddling with Surge, and it's a big loss for me, as I see a great potential for amazing sounds in it.
I've had this issue for about a year now (basically since the first time I tried Surge on Linux). This happens to me no matter the host: Ardour, Carla or Zrythm.
However this week I had a similar problem happened on a Windows 10 system where I so far was able to use Sure with success with Ardour 5.12.
one of my patches got reset to Init when loading a project created a few days earlier. I have saved and loaded it before without problems, and other patches were fine. I don't know if this could be related.
Please let me know what can I do to help you fix this problem. I know I'm not the only Linux user suffering from it.