Cabbage Logo
Back to Cabbage Site

cabbageGetValue init behaviour changed

Hi,
I just wanted to check regarding the init behaviour of the cabbageGetValue and possibly other widget opcodes. I upgraded from v. 2.9.45 to 2.9.137 and I see that in:

kValue, kValueChanged cabbageGetValue "ChannelName"

kValueChanged is now 0 at the init time while before it was 1. This broke some of my instruments in cases when I’m calculating new k-rate variables (depending on multiple widgets) only when any of the widgets change.

I can surely work around that but I’m wondering if this is how the widget opcode behaviour is going to remain in the future or was this change not intended?

It seems a bit clumsy to need initializing perhaps like:
kValue init cabbageGetValue:i("ChannelName")
or is there a better way of going about?

The discussion came up here. There was no init method before, but there is one now. I understand that this might have caught some people out but I think going forward this matches better what people might expect. However, as I was doing this I thought that it might be an idea to add an optional parameter to this opcode that would causes a trigger on init. This would help you or?

Thanks! I think it would be good to add the option to trigger at init, particularly if there is no elegant solution to what I am trying to do (example below).

Please (un)comment the cases below. The case 1 doesn’t init as I’d like, so case 2 and 3 might be some kind of ugly workarounds. I find it inconsistent looking at case 3, using cabbageChanged, which does trigger at init. Would you suggest doing this in some other way? Or maybe the “new magic option” could save the day?

<Cabbage>
form caption("test cabbageGetValue") size(370, 280) colour(58, 110, 182) pluginId("tcgv") guiMode("queue")

rslider bounds(50, 50, 60, 60) range(0, 1, 0.5, 1, .01) channel("Slider1") _neededAtInit(1)
rslider bounds(150, 50, 60, 60) range(0, 1, 0.5, 1, .01) channel("Slider2") _neededAtInit(1)


</Cabbage>
<CsoundSynthesizer>
<CsOptions>
-n -d -m0d -+rtmidi=NULL 
</CsOptions>
<CsInstruments>
ksmps = 32
nchnls = 2
0dbfs = 1

instr 1

kSlider1, kSlider1Changed cabbageGetValue "Slider1"
kSlider2, kSlider2Changed cabbageGetValue "Slider2"

// case 1 - missing init of kVal
/*
if kSlider1Changed + kSlider2Changed > 0 then 
    kVal = kSlider1 + kSlider2  
endif
*/

// case 2 
/*
kInit init 1
if kSlider1Changed + kSlider2Changed + kInit > 0 then    
    kVal = kSlider1 + kSlider2  
    kInit = 0
endif
*/

// case 3

SChannels[] cabbageGetWidgetChannels "_neededAtInit(1)"
kIndex, kTrig cabbageChanged SChannels

if kTrig == 1 then
    kVal = kSlider1 + kSlider2  
endif
printk2 kTrig



//-----------
printk2 kVal


endin

</CsInstruments>
<CsScore>
i1 0 z
i 1 0 -1
</CsScore>
</CsoundSynthesizer>

Thanks for the .csd, I’ll take a look tomorrow and see what might work best. An optional parameter sounds reasonable.

I wanted to send another example, which worked before but no longer. Here I’m updating text only when values change. It works without the condition, but not with. Try to comment out the lines marked with “// --------”.

Another thing. Besides comparing with the behaviour of cabbageChanged (above), I find it inconsistent also with change:k("channelName"), which also initializes to 1. Could you please reconsider the behaviour of cabbageGet? It seems to be causing significant issues and is inconsistent - or I cannot make sense of it.

<Cabbage> bounds(0, 0, 0, 0)
form caption("test cabbageSet in v. 2.9.137") size(300, 200), guiMode("queue"), colour(0,0,0) pluginId("tcs1")

label bounds(18, 20, 25, 14) channel("Display1") text("|") align("left") fontColour(255, 155, 90, 255) _Display(1)
label bounds(58, 20, 25, 14) channel("Display2") text("|") align("left") fontColour(255, 155, 90, 255) _Display(1)
label bounds(98, 20, 25, 14) channel("Display3") text("|") align("left") fontColour(255, 155, 90, 255) _Display(1)

rslider bounds(50, 50, 36, 36) range(1, 23, 12, 1, 1) trackerCentre(0.5) channel("Slider") colour(255, 155, 90, 255) trackerColour(255, 255, 220, 255) markerColour(0, 0, 0, 255) popupText("0") 


</Cabbage>
<CsoundSynthesizer>
<CsOptions>
-n -d -+rtmidi=NULL -M0 -m0d
</CsOptions>
<CsInstruments>

instr 1

S_Display[] cabbageGetWidgetChannels "_Display(1)"
kSlider, kSliderChange cabbageGetValue "Slider"
//printk2 kSliderChange


kArray[] init 3 


if kSliderChange == 1 then // --------

kind = 0
until kind == 3 do

kArray[kind] = kSlider + kind

if kArray[kind] < 10 then
    Sval sprintfk "fontColour(255, 155, 90, 100) text(|%d)", kArray[kind]
else
    Sval sprintfk "fontColour(255, 255, 0, 100) text(|%d)", kArray[kind]
endif

cabbageSet 1, S_Display[kind], Sval

kind += 1
od

endif // --------


endin

</CsInstruments>
<CsScore>
f0 z
i 1 0 -1
</CsScore>
</CsoundSynthesizer>

This is high on my list of priorities. I’ll try to get time to look into these questions tomorrow.

1 Like

In the example above I could use changed:k(kSlider) == 1 instead of kSliderChange == 1 to achieve the desired behaviour at init time, while kSliderChange == 1 rather corresponds to changed2:k(kSlider) == 1, which excludes the i-rate check.

So maybe we could have the two variants of cabbageGetValue and cabbageGetValue2 corresponding to changed and changed2 - just an idea.

One more weird behaviour. Using the example above I realized that now and then it does not update the widgets. I have to restart the csd maybe 10x to catch that behaviour. Here is a video when I got into the weird territory while printing what was sent to the widget. You’ll see that when I restarted csd it again functioned well. I found this in a larger csd first where the chance of weird territory seems much higher, possibly due to much higher widget count overall and maybe also due to larger count of widgets to update. There seems something broken with cabbageSet in this version of Cabbage.

Ok, I’ve been looking over this. I think what you want is not a trigger on i-time, which will be missed by any k-rate opcodes/tests, but a trigger on the first k-cycle. For example, the following would fail on an init pass even if I set the trigger value to 1 on init becase a k-rate test does not operate at i-time

k1, kTrig cabbageGetValue "slider"
if kTrig == 1 then
    //code will never be hit at i-time, no matter what value kTrig is
    //because test only works at k-time..
endif

On the other hand, something like this, with an optional trigger on first k-cycle parameter would work:

k1, kTrig cabbageGetValue "slider", iTrigOnPerfPass
if kTrig == 1 then
    //code will be hit as soon as instrument starts
endif

I had another look over the discussion Iain and I had about this opcode. In Iain’s case he was making the point at the value should always be set at i-time. I think this is correct but there doesn’t seem to be much point in setting a k-rate trigger value at i-time, when triggers can only happen at k-time. Let me add something like that and see how it works…

Consider this:

Your second example is a lot complex. For some reason the S_Display[kind] is not known to cabbageSet at i-time. I’m trying to figure out why…

1 Like

would this correspond to how changed vs changed2 work?

My examples worked fine before in an older version (2.9.45).

Yes, exactly. I changed the behaviour because a changed trigger shouldn’t happen unless a channel changes. I still think expecting a changed trigger when no channel has changed is a strange thing to design an instrument around, but I blame myself for putting it in there in the first place :rofl:

As for your second instrument. I’m still looking into that…

Thanks! Maybe you could then just introduce cabbageGetValue2 for consistency?
The reason I need a trigger is that I want to set some channels, which are functions of widget values. I could just init those, but they don’t generally have straightforward values and if I change default widget values then I’d need to change all the init values. Or use some ugly workaround as I mentioned before. I need a simple way to initialize the whole system based on the initial configuration of widgets, which worked before. Still don’t understand why the first k-pass (or init) trigger is “allowed” on changed but not on cabbageGetValue. And the changed opcode has a good reason to exist.

I hate adding a number to the end of an opcode. I wish they never adopted that convention in Csound :sob:

It is now, if you enable it. I’ve never understood why the changed opcode would send a trigger on init. But anyway, I’m not here to pull apart Csound.

And on to your second example. This wasn’t working because S_Display[kind] is not known at i-time. Which is when cabbageSet does it’s internal assignments. I’ve fixed this now and it works again. I changed this to optimise performance, but I overlooked k-rate string arrays. I just need to to update the other stringy opcodes and I’ll push for testing :slight_smile:

Thank you so much! :partying_face:

Isn’t my case above indicating one possible reason? I’m open to suggestions how I could do this better.

It certainly is! I stand corrected. It’s nice now to have the option of both :slight_smile:

I’ve just trigger a new build. Let me know how it goes. :+1:

Thanks for adding this! But shouldn’t it be the default behaviour to trigger at first pass (current iFlag = 1)?
This would make it consistent with changed (changed2 correspond to setting the non default flag in cabbageGetValue) and it would not break older instruments after upgrading Cabbage.

And how is it with

There could also be a flag to NOT trigger. Or this doesn’t make sense? I’m just trying to understand and longing for consistency.

Hmm, I’m not inclined to, because why would a changed event be triggered when no change has happened? It’s a false positive, surely that would lead to more confusion. I know it will potentially break older code, but

So it should inform users there was a change to the underlying channel even though their wasn’t one? And what if the user wants to know when the first change takes place, they have to write some extra logic because the opcodes doesn’t actually do what it states it does in the docs, i.e, return a value of 1 whenever the channel changes value. I don’t know, it seems like this would lead to even more confusion. I know it will might break some old instrument, but I think in the long term if makes the opcodes better.