Cabbage Logo
Back to Cabbage Site

Ideas for saving generic data in DAW sessions

hmm, I still can’t get it to work consistently. Maybe I’m not seeing it, but it seems kind of random how it works sometimes and sometimes not. I had it working pre Cabbage 2.6/new guimode times, but that code clearly doesn’t work anymore., which is too bad.
As I’d like to move further I’m about to let go of the idea of storing whole arrays and just store everything individually. That way it’s safe that my data doesn’t get “initialised away”.
I have one question though:
Do you think you could implement an optional second output for the cabbageGetStateValue opcode that flags if the requested Json identifier exists in the state data or not?
It would be helpful in terms of setting values that haven’t been set yet to their init value when loading the plugin. Otherwise I’d have to store a flag for every value change separately, like e.g. a volume-slider-no(x)-has-been-touched-identifier, which would be kind of annoying, because I have lot’s of sliders (in tabs) by now.

That was actually just a quick way of loading the stored data back into my arrays while I’m printing the state data. It doesn’t affect the outcome for me if I take those out. I think the place where the json data gets overwritten happens during init here:

    kcabbageGet     cabbageGetValue "cabbageGet"
    if(changed(kcabbageGet) == 1)then
        gkcabbageGetArray[kNumber] = kcabbageGet
        cabbageSetStateValue "xcabbageGet", gkcabbageGetArray
    endif

Sorry for the confusing var names, just wanted to try the different opcodes and actually wanted to be clear which ones I use with which array. :thinking: (where’s the cabbage emoji?)
But it’s also inconsistent. I had it working and then not then changed it back and it still didn’t work, did something else and it worked again for a second. I can’t see the pattern yet. For the sake of time, I might just use single identifiers…let me know if you can add the “json identifier exists” feature.

The example I posted works fine for me here. I’ve tested it a few times. Maybe I’m missing a step? I can record a screencast tomorrow.

Aha, you’re using the trigger from the new cabbageGetValue opcode. I wonder if that does the trick.

Tried your script and it didn’t work for me neither in Studio One nor in Reaper. Well…tomorrow is another day…good night

@Stefan I’m going to take another look at this now. I’ll get back to you in a bit…

[edit] I think we have a chicken and egg situation going on in this example. When the instrument first loads it creates an empty array. We then fill that array and save to state. This works fine. When we next open the instrument our state data is still there, up to the moment we try to update it, then it gets wiped. I’ll find a solution…

I’ve pushed a fix for this. And I’ve attached an example that works for me.
GetSetStateOpcodes.csd (3.4 KB)

The highlights are:

instr 1
    ;get array from state
    kSlider1StateArray[] cabbageGetStateValue "Slider1"
    kSlider2StateArray[] cabbageGetStateValue "Slider2"

    ;we need to now get size at i-time so we can init our local k-arrays...
    iStateArray1[] cabbageGetStateValue "Slider1"
    iStateArray2[] cabbageGetStateValue "Slider2"
    print lenarray:i(iStateArray1)

    ; if first time running instrument, there will be no array data in state
    ; in which case initialise an empty array of size 10. Otherwise assign local array
    ; the one from state 
    if lenarray:i(iStateArray1) != 10 then
        kSlider1Array[] init 10
    else
        kSlider1Array = kSlider1StateArray
    endif
    if lenarray:i(iStateArray2) != 10 then
        kSlider2Array[] init 10
    else
        kSlider2Array = kSlider2StateArray
    endif

After that it’s pretty much the same as you had it. The problem however was due to the fact that Cabbage performs a single k-cycle just after Csound gets compiled1. This was causing the state opcodes to run before the DAW pushed the state from memory to the plugins state blob. And so we got this inconsistent behaviour.

1 I’m removing this process of running a single k-cycle straight after compilation as I’m pretty sure it is no longer needed.

Thank you Rory for sticking with this.
Actually at first it didn’t work. I used the example that I have used earlier and the data still got removed after loading the DAW session again. Lots of zeros from cabbageReadStateData with this(changed the var and array names for sanities sake :dizzy_face:):

gkslider1Array[]    init 10 
gkslider2Array[]    init 10                   

instr 1
    kNumber init 0
    
    kNumber = (changed(chnget:k("next")) == 1 && kNumber < 9)? kNumber + 1 : kNumber        // change active index 
    kNumber = (changed(chnget:k("prev")) == 1 && kNumber > 0)? kNumber - 1 : kNumber        // with buttons
   
    if( changed(kNumber) == 1 ) then 
        String          sprintfk    "text(\"%d\")",kNumber              // index
        cabbageSet      1,    "number", String                                                
        cabbageSetValue "slider1",gkslider1Array[kNumber]
        cabbageSetValue "slider2",gkslider2Array[kNumber]
    endif
    
    kslider1     cabbageGetValue "slider1"
    if(changed(kslider1) == 1)then
        gkslider1Array[kNumber] = kslider1
        cabbageSetStateValue "dataSlider1", gkslider1Array
    endif
    
    kslider2     cabbageGetValue "slider2"
    if(changed(kslider2) == 1)then
        gkslider2Array[kNumber] = kslider2
        cabbageSetStateValue "dataSlider2", gkslider2Array
    endif
    
    if( changed(chnget:k("print")) == 1) then                                               // call instrument to print the stored data
        event "i","printStoredData",0,0
    endif

endin

instr   printStoredData
    prints "\n\nPrinting using readStateData\n"
    prints cabbageReadStateData:S() 
endin

So I made an experiment to really pinpoint where that happens. I used the above script, loaded it as a vst2 in S1 and made some settings to generate some state data. Then I quit S1, but didn’t load the session again. Instead I edited the script and took everything out except the function that prints the state data and then loaded the session again, e voilá the data actually got passed. Then I carefully activated the code bit by bit to see where the problem occurs. The data stayed intact until I reintroduced the slider-changed-query with the SetStateValue inside. Did it only for slider1 which made very clear where the problem was. The data for the slider1 array was set to 0 while the one for slider2 was still intact. So now I could make it work like this:

strset 1,"dummy"

kslider1     cabbageGetValue "slider1"
if(changed(kslider1) == 1)then
    gkslider1Array[kNumber] = kslider1
    
    SIdent  sprintfk "%s", "dataSlider1"
    SIdent  strget 1
    
    cabbageSetStateValue SIdent, gkslider1Array
endif

The array for Slider1 gets loaded correctly / doesn’t get overwritten on init and in addition I see an array “dummy” filled with the zeros from the initialised slider1 array.

I guess that’s where your method of only initialising the array only if it’s not there already parsed from the GetStateValue opcode works as well. For the 50+ arrays of my plugin I’d try to spare myself from that way.

Maybe it could also work to load the state data back into the arrays in a separate instrument and have the instrument that does the slider-query start a bit later so the init run wouldn’t write the initialised array to the state data, but the already loaded one.

I’m having hopes I can make it work somehow in my 2500+ lines of barely commented plugin code. I’ll let you know if more difficulties occur.
Thanks for the great support!

I thought of that too, but I think the same issue would persist? I guess the real issue here is we are dealing with fixed sized arrays in Csound. If they could dynamically be resized we would be laughing.

I think we can say it works, but when dealing with arrays one must be careful. :grimacing:

How is it with the trigger from the cabbageGetValue opcode? Any chance to have the trigger not be triggered on init? I guess the code inside the if-then-endif gets initialised anyway, right?
Well, I am very grateful for the strset strget opcodes. They help big time with these init issues.

The k-rate version of that opcode runs at i and k rate. You can try using a k-rate trigger that is set to 0 at init time, and then changes to 0. It might work, but you’d need to test it.

That might work. My understanding of the init run is still limited. There were times where I had the impression that every bit of code runs once no matter what. So I just went silly and tried this:

if(0 == 1)then
    gkslider2Array[kNumber] = kslider2
    cabbageSetStateValue "dataSlider2", gkslider2Array
endif

And it “works”!
dataSlider2 stays intact like this.

It depends on the opcode, and the person who wrote it :wink:

But this code will never be hit. This i-rate test will always be false.

Well that’s a relieve! :laughing:

1 Like

Good news, stuff’s working!
Just one quick question:
Are multi-dimensional arrays supported?

Hmm, no. I never thought about adding support for them. Might be best to let the dust settle on the 1d arrays first :rofl:

@Stefan I’ve had to revert this change as it broke some other things. :confounded: I’ll try later to come up with a fix. Maybe forcing the k rate versions to run only at k time might work? I’ll run some tests and let you know.

Ugh, that’s too bad. Did the same experiment with the newest Beta , where I’m generating state data and then removing everything from the plugin except the function for printing the state data before I reopen the DAW session again.

They json array data still gets parsed to the plugin correctly. It’s just that init now overwrites it if I have the cabbageSetStateValue code present that stores the array in the first place.

I guess that one cabbage k-cycle at compile time ignores the strset and strget opcodes, right? Otherwise this should still work then.

So is that a cabbage init cycle that runs in addition to the cSound init cycle?
Maybe you could come up with something similar in Cabbage that does the same thing that the strset and strget opcodes do for the cSound init cycle?

My only alternative is to store the content of my arrays with single identifiers for each index.
Could that be better for performance anyway, as I’m not storing a whole array of 30 settings every time I make a single change?

That solution is going to be a lot of work though. Do you think you can offer a different solution for this anytime soon?

Well yeah, of course. I want to get it back to how we had it a few days ago obviously. I just need to do it a different way.

I just get Csound to perform the first k-cycle so that we can initialise all widgets and make sure the DAW has this info before it starts playback. Changing it causes issues in DAW when projects were being reopened.