Cabbage Logo
Back to Cabbage Site

Cannot get cabbageSetStateValue to work outside main instrument

Hi, I’ve recently discovered cabbage and I have been creating a synth. I’m loving how easy it is but I came across a strange issue when trying to use cabbageSetStateValue to save some data across DAW sessions. Basically, I can only get cabbageSetStateValue to work if used in the main instrument (which only executes when a note plays). If I try to use the opcode in another instrument to save some data at some other point (e.g. when a button is pressed or upon opening the synth) it doesn’t seem to work. I’m using cabbage 2.8.0, Windows 10, and trying to use the Synth in Bitwig 2.2.2.

I managed to write a simple script that reproduces the issue without the rest of the synth stuff:

<Cabbage>
form caption("Untitled") size(400, 300), guiMode("queue"), pluginId("def1")
keyboard bounds(8, 5, 381, 95)
csoundoutput bounds(5, 120, 390, 170) fontSize(16), scrollbars(1), wrap(1),  fontColour("black"), colour("tan")
</Cabbage>
<CsoundSynthesizer>
<CsOptions>
-n -d -+rtmidi=NULL -M0 -m0d --midi-key-cps=4 --midi-velocity-amp=5
</CsOptions>
<CsInstruments>
; Initialize the global variables. 
ksmps = 32
nchnls = 2
0dbfs = 1

;instrument will be triggered by keyboard widget
instr 1
    kEnv madsr .1, .2, .6, .4
    aOut vco2 p5, p4
    outs aOut*kEnv, aOut*kEnv
endin

instr 2
    prints "reading data:\n"
    prints cabbageReadStateData()
    kNoSmps init 0
    if kNoSmps == 0 then
        event "i", "SaveData", 0, 0
    endif
    kNoSmps += 1
endin

instr SaveData
    cabbageSetStateValue "Data", "SomeSavedData"
    prints "data just written:\n"
    prints cabbageReadStateData()
endin

</CsInstruments>
<CsScore>
;causes Csound to run for about 7000 years...
f0 z
i2 0 z
</CsScore>
</CsoundSynthesizer>

When you first open the synth within a DAW instrument 2 simply prints all saved data, saves some data and then prints all saved data again. To reproduce the issue, first export the above script as a VST and load it into a DAW. You should see from the csoundoutput widget that the synth attempts to print all saved data using cabbageReadStateData and outputs a “No Data…” message since this is the first time the synth is opened. Then on the first k cycle it saves some data using cabbageSetStateValue and then prints all saved data successfully in the csoundoutput. Now save the session, close the DAW and reopen the DAW session and synth. Since the synth’s first job is to print all saved data, I would expect it to print the saved data from the previous session. But instead, I get the “No Data…” message again at the start. If I move the code from instrument 2 into instrument 1 then the saved data is printed successfully before and after the data is saved, but only when a note is received.

It seems counterintuitive to only enable state saving/loading logic to execute whilst playing audio so this seems like a bug to me. But I am new so perhaps I’m simply misunderstanding how to use cabbageSetStateValue or csound/cabbage generally. Any ideas?

Hi @jonayoung2003, for someone who just discovered Cabbage, I’m impressed that you’re already trying out these opcodes! They are still relatively raw…

I agree, this kind of limitation certainly appears odd and it obviously not part of the specs! What happens if, after saving the session, you remove the following from instr 1 and try again:

if kNoSmps == 0 then
        event "i", "SaveData", 0, 0
endif

I’m just wondering if that initial save on the first k-pass is messing things up. Let me know if that changes anything. It’s not a fix, but might help me locate the problem :wink:

Hi @rorywalsh, thanks for the speedy reply! So I exported to VST, opened in DAW, saved the session and closed. Then I commented out those lines of code from instrument 2, exported again (overwriting the .dll etc.) and opened the previously saved DAW session. But I still get the “No Data…” message upon printing cabbageReadStateData.

You always get a speedy reply around here :stuck_out_tongue:

I’ll take a look at this later and see what’s going on :+1:

I think the cabbageReadData() opcode is not working properly. It seems to be working at k-rate, so has nothing in it at i-time, hence the No data warning. I don’t have time to investigate much further now, but I did prepare an example that works using cabbageGetStateValue:S(). Let me know if you hit any more strangeness!

<Cabbage>
form caption("Untitled") size(400, 400), guiMode("queue"), pluginId("def1")
button bounds(10, 110, 100, 30), channel("save"), text("Save")
button bounds(120, 110, 100, 30), channel("read"), text("Read")
keyboard bounds(8, 5, 381, 95)
csoundoutput bounds(5, 150, 390, 240) fontSize(16), scrollbars(1), wrap(1),  fontColour("black"), colour("tan")
</Cabbage>
<CsoundSynthesizer>
<CsOptions>
-n -d -+rtmidi=NULL -M0 --midi-key-cps=4 --midi-velocity-amp=5
</CsOptions>
<CsInstruments>
; Initialize the global variables. 
ksmps = 32
nchnls = 2
0dbfs = 1

;instrument will be triggered by keyboard widget
instr 1
    kEnv madsr .1, .2, .6, .4
    aOut vco2 p5, p4
    outs aOut*kEnv, aOut*kEnv
endin

instr 2
    prints "Previously saved data:\n"
    prints cabbageGetStateValue:S("Data")

    
    kSave, kTrigSave cabbageGetValue "save" 
    if kTrigSave == 1 then
        event "i", "SaveData", 0, 0
    endif

    kRead, kTrigRead cabbageGetValue "read" 
    if kTrigRead == 1 then
        event "i", "ReadData", 0, 0
    endif
endin

instr SaveData
    cabbageSetStateValue "Data", "SomeSavedData"
endin

instr ReadData
    prints cabbageGetStateValue:S("Data")
endin


</CsInstruments>
<CsScore>
;causes Csound to run for about 7000 years...
f0 z
i2 1 z
</CsScore>
</CsoundSynthesizer>

Hi @rorywalsh thanks for that example! After messing about with this and eventually finding a workaround for my specific case I realised that the key to getting it to work (for me at least) is by introducing a delay when using cabbageGetStateValue. This is shown in the score of your example (line i2 1 z). If I have time I might make a little example that demonstrates this and post. But just thought I would mention it in case anyone else has the same problem and sees this.

1 Like

@jonayoung2003, @chronopolis @Stefan. I’m pinging you all because I know you’ve been using these opcodes and this might be relevant to your work. I did a little more digging on the issue described above and it’s clear now why this little delay was needed (I should have foreseen the issue!).

The plugin state data is saved and recalled by the host, but it might not be called until after Csound has compiled and run its first k-cycle. It seems to be quite inconsistent across hosts. And what’s worse, in my tests, the data was becoming available in the middle of a k-cycle, so some opcodes were getting it before others.

There are two possible solutions:

  1. Make ALL cabbageGet/Set/Data opcodes k-rate only. That way no one can fall prey to this issue again. While the state data might not be there on the first k-cycle it will show up in the next few cycles for sure.

  2. Make a note about this issue in the manual and make sure people are aware of the problem.

I’m leaning toward solution 1 as it means future users can’t hit this issue by accident, but it might result in some breaking changes to people’s existing instrument. On the other hand, I’m not sure that many people are using these opcodes. Any thoughts?

That seems consistent with some of the issues I’m having.

Solution 1 seems fine to me. Perhaps slightly off topic, my use-case is that I need to test if data is even available — to load a default state or the saved state — and I haven’t yet figured that out. From what I can tell, if no data is available then there’s nothing to return to the conditional (eg a string or integer etc). So perhaps my issue is another thread…

Ah - OK, so maybe a value or non-value will be returned if I use a delay? I will try that…

Let me push a build with the changes I mentioned and you can try them out. I just need to run a few more tests first.

Just pushed a change now. I’ve been testing with this example.

<Cabbage>
form caption("Test") size(350, 300), guiMode("queue"), colour(58, 110, 182), pluginId("sfi1")
rslider bounds(20, 8, 60, 60) range(0, 1, 0, 1, 0.001), channel("gain"), text("Gain")
button bounds(100, 10, 80, 30), channel("saveButton"), text("Save"), latched(0)
button bounds(200, 10, 80, 30), channel("showButton"), text("Show"), latched(0)
csoundoutput bounds(14, 60, 300, 200) channel("csoundoutput1"), fontColour(147, 210, 0)
</Cabbage>
<CsoundSynthesizer>
<CsOptions>
-n -d -+rtmidi=NULL -M0 -m0d 
</CsOptions>
<CsInstruments>
; Initialize the global variables. 
ksmps = 32
nchnls = 2
0dbfs = 1
 
/* I'm using latched buttons here, if I use normal buttons their value could be
1 when the session is saved, as their default value is 0 it will cause a new state
value to be written when the session loads, thus overwriting the previous one! */ 


instr 1
    kHaveState = cabbageHasStateData()  
  
    if changed:k(kHaveState) == 1 then 
            printks "State found", 0
    endif
 
    kButtonSave, kTrig1 cabbageGetValue "saveButton"   
    kButtonShow, kTrig2 cabbageGetValue "showButton" 

    if kTrig1 == 1 && kButtonSave == 1 then
        cabbageSetStateValue "key1", cabbageGetValue:k("gain")
    endif

    if kTrig2 == 1 && kButtonShow == 1 then
        kData cabbageGetStateValue "key1" 
        printk 0, kData
    endif
endin

</CsInstruments>
<CsScore>
i1 0 z
</CsScore>
</CsoundSynthesizer>

Note I have added a cabbageHasState opcode that will return 0 is no state is found, or 1 if state data is present. It will always be 0 on the first k-cycle, and is k-rate only. If people are Ok with these changes I will update the manual. I have to say these opcodes present some unique chicken and egg type scenarios :rofl: But we are at the mercy of the hosts and when they choose to call restore the plugin’s state after recalling a session.

Tested and working great! :grin: Possible to get Pro versions of this?

Builds underway :+1:

…but won’t be available till later tonight… sorry…

Hmmm I think cabbageHasStateData() is causing a big spike for me… I think I can get around it by just running it once but see what happens in the activity monitor when data is written vs commenting it out: https://recordit.co/uAnBoQ9eAr

EDIT - got around it in the meantime

kinitState init 0
kinitState delayk kinitState, 0.05
if kinitState < 1 then
    kHaveState = cabbageHasStateData()
    kinitState = 1 
endif

Thanks @chronopolis I’ll take a look. :+1:

Just pushed a fix for this. In fact, i was able to optimise the other opcodes too now that you brought it to my attention :slight_smile: I will try to get a windows and mac build for you this evening at some point.