Cabbage Logo
Back to Cabbage Site

New cabbageChanged opcode

Sounds great! Looking forward to start implementing the new trigger.

I suppose this will trigger also for sliders when they cross the target value but in both directions (from higher to lower and lower to higher), right?

Yes, correct. Let me think about this. We could add an optional trigger-esque mode parameter?

That’s what I had in mind all along! :slight_smile:

I’ve updated this so the additional parameters behave like the trigger opcode. I modified the original post here so that it contains a description of the most current version. Need testing, but seem to mimic the Csound trigger opcode now, albeit with channel array input.

1 Like

Fantastic! I think this addition will be very useful! And it now beats the Csound (non) alternatives even by a larger margin. Thanks! I’ll get on with it tonight. :slight_smile:

[edit] I hope the stew will be ready soon …

MacOS stew is available, just click into the pipeline. Steinberg are doing maintenance on their servers at the moment which means the ASIO SKD is not currently available. This is stopping the Windows builds from completing for now. I hope it will be sorted shortly.

I did some tests and it doesn’t seem to work as I expected. Or maybe I am doing something wrong?

  • the new trigger-esque options don’t seem to have any effect at all, e.g. sliders are triggering all the time, tried with i-rate and k-rate option values (how are they supposed to be set?)

  • radioGroup-ed buttons don’t trigger when going back and forth between any two buttons in a group

Here is my testing playground (please comment/uncomment parts of it).

<Cabbage>
form caption("Cabbage Changed") size(430, 290) pluginId("tl01") guiMode("queue")

rslider bounds(10, 10, 100, 100), channel("slider1") _isSlider(1)
rslider bounds(110, 10, 100, 100), channel("slider2") _isSlider(1)
rslider bounds(210, 10, 100, 100), channel("slider3") _isSlider(1)
rslider bounds(310, 10, 100, 100), channel("slider4") _isSlider(1)

combobox bounds(20, 118, 80, 20) channel("combo1"), channelType("string") _isCombo(1)
combobox bounds(120, 118, 80, 20) channel("combo2"), channelType("string") _isCombo(1)
combobox bounds(220, 118, 80, 20) channel("combo3"), channelType("string") _isCombo(1)
combobox bounds(320, 118, 80, 20) channel("combo4"), channelType("string") value("1") _isCombo(1)

label bounds(8, 158, 412, 21) channel("label1"), align("left"), fontColour(0, 0, 0, 255) text("Most recently changed widget:")

image bounds(6, 234, 412, 41) channel("image15")
button bounds(20, 246, 80, 20)   channel("RG1") text("1", "1") radioGroup("RG") colour:1(255, 255, 0, 255) fontColour:1(160, 0, 0, 255) _isGrouped(1)
button bounds(120, 246, 80, 20)   channel("RG2") text("2", "2") radioGroup("RG") colour:1(255, 255, 0, 255) fontColour:1(160, 0, 0, 255) _isGrouped(1)
button bounds(220, 246, 80, 20)   channel("RG3") text("3", "3") radioGroup("RG") colour:1(255, 255, 0, 255) fontColour:1(160, 0, 0, 255) _isGrouped(1)
button bounds(320, 246, 80, 20)  channel("RG4") text("4", "4") radioGroup("RG") colour:1(255, 255, 0, 255) fontColour:1(160, 0, 0, 255) _isGrouped(1)

button bounds(20, 198, 80, 20)  channel("NonLatched1") text("X", "X") latched(0) colour:1(255, 255, 0, 255) fontColour:1(160, 0, 0, 255) _latched(0)
button bounds(120, 198, 80, 20)  channel("NonLatched2") text("Y", "Y") latched(0) colour:1(255, 255, 0, 255) fontColour:1(160, 0, 0, 255) _latched(0)
button bounds(220, 198, 80, 20)  channel("NonLatched3") text("Z", "Z") latched(0) colour:1(255, 255, 0, 255) fontColour:1(160, 0, 0, 255) _latched(0)
button bounds(320, 198, 80, 20)  channel("NonLatched4") text("W", "W") latched(0) colour:1(255, 255, 0, 255) fontColour:1(160, 0, 0, 255) _latched(0)

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

instr 1

;SWidgetChannels[] cabbageGetWidgetChannels "_isSlider(1)" 
;SWidgetChannels[] cabbageGetWidgetChannels "_latched(0)"
SWidgetChannels[] cabbageGetWidgetChannels "_isGrouped(1)"

;prints SWidgetChannels[0]

;SUpdatedChannel, kTrig cabbageChanged SWidgetChannels
;cabbageSet kTrig, "label1", sprintfk("text(\"Last updated widget: %s\n\")", SUpdatedChannel)

;kIndex, kTrig cabbageChanged SWidgetChannels
;cabbageSet kTrig, "label1", sprintfk("text(\"Last updated widget: %s\n\")", SWidgetChannels[kIndex])
;printk2 kIndex

kIndex, kTrig cabbageChanged SWidgetChannels, k(0.5), k(0)
if kTrig == 1 then
    printk 0, kIndex
endif  

endin

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

I’d like it to do something like this (comment one of the fillarray blocks)

kON[] fillarray \
trigger(cabbageGetValue:k("NonLatched1"),0.5,0),\
trigger(cabbageGetValue:k("NonLatched2"),0.5,0),\
trigger(cabbageGetValue:k("NonLatched3"),0.5,0),\
trigger(cabbageGetValue:k("NonLatched4"),0.5,0)

kON[] fillarray \
trigger(cabbageGetValue:k("RG1"),0.5,0),\
trigger(cabbageGetValue:k("RG2"),0.5,0),\
trigger(cabbageGetValue:k("RG3"),0.5,0),\
trigger(cabbageGetValue:k("RG4"),0.5,0)

kind = 0
until kind == 4 do
    if kON[kind] == 1 then
        printk 0, kind
    endif
kind += 1
od

My bad, seems I only added it to the version:

   SChannel, kTrig cabbageChanged ...

I’ll update the

   kIndex, kTrig cabbageChanged ...

now. Give me a sec…

Try this build when it’s cooked. Your code works fine for me here.

The slider now works 50%. Could you please check my code for all 3 types of widgets.

  • the kIndex doesn’t change?

  • radioGroup doesn’t work - still seems the same as I noted before

Maybe you could consider replicating this behaviour if possible - instead of printing in the until loop I’d of course do more interesting things with the index.

It’s working for me 100% of the time. I tested with the trigger opcode in Csound and they both seem to behave the same?

Sorry, my fault :grimacing: That’s fixed now

The problem is that when clicking on a radio group, two buttons change state at the same time, or thereabouts. This means that we intermittently miss one. Looks like there is little we can do in this case because it’s not a single channel changing but two.

One possible way around this would be to build your own radiogroup using a slider and some images. Each time you update a button(image) in the group it would send an update to the slider. That way you only have to keep track of the slider position? Here is n example:

<Cabbage>
form caption("Cabbage Changed") size(430, 390) pluginId("tl01") guiMode("queue")
image bounds(10, 296, 90, 30) channel("radioImage1"), value(1), colour("black"), _isGrouped(1)
image bounds(110, 296, 90, 30) channel("radioImage2"), value(1), _isGrouped(1)
image bounds(210, 296, 90, 30) channel("radioImage3"), value(1), _isGrouped(1)
image bounds(310, 296, 90, 30) channel("radioImage4"), value(1), _isGrouped(1)
hslider bounds(-1000, 310, 90, 20), alpha(0), popupText(0), channel("radioGroup1"), range(0, 3, 0, 1, 1)

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

opcode Radio, 0,SS[]i
    SSliderChannel, SChannels[], iIndex xin
    kIndex, kTrig cabbageChanged SChannels
    kCnt = 0
    printf "Changed:%d", kTrig, kIndex 
    if kTrig == 1 then
        while kCnt < 4 do
            cabbageSet kTrig, SChannels[kCnt], (kCnt == kIndex ? "colour(255, 0, 0)" : "colour(255, 255, 255)") 
            kCnt+=1
        od
        cabbageSetValue SSliderChannel, kIndex
    endif
endop

instr 1
    SWidgetChannels[] cabbageGetWidgetChannels "_isGrouped(1)"
    Radio "RadioGroup1", SWidgetChannels, 1
    kIndex, kTrig cabbageGetValue "RadioGroup1"
    printf "RadioGroupIndex:%d", kTrig, kIndex
endin

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

You might want to make each one of those image interactive(0) so they don’t confuse users in a DAW. In fact, I wonder can you just use a slider with your current setup too? Let me take a look…

I guess you could do this:

kIndex, kTrig cabbageChanged SWidgetChannels, k(0.5), k(0)
kCurrentRadio init 0
if kTrig == 1 then
    kCnt = 0
    while kCnt < 4 do
        if cabbageGetValue:k(SWidgetChannels[kCnt]) == 1 then
            kCurrentRadio = kCnt
        endif
        kCnt += 1
    od 
endif  

printk2 kCurrentRadio

Until radioGroups behave like a single object I think we might be stuck with some kind of hybrid solution :frowning:

Thank you very much for explanations and examples! Very helpful!

Not sure what you mean, but a slider would constrain changes to be monotonic? What about a sequence of jumps like 1 4 2 5 1?

The idea was to make it less taxing on CPU. I’m wandering if this isn’t going to be more expensive?

I was thinking that the index would determine the position of the slider? Even still, you could keep the sequence in an array and use the slider position to index them?

I still think this might be less taxing than the trigger/cabbageGetValue solution? But it would need testing…

Right, because cabbageGetValue is only invoked if triggered by cabbageChanged and the latter one is super efficient (?). Taken to extreme, one could wrap all the widgets cabbageGetValues like this, and it would be interesting to see an efficiency curve as a function of number of widgets - but this is my physicist brain waking up now :wink:

That’s my reading of it. In your example you are calling lots of opcodes on every k-cycle. In this hybrid solution it only happens when something changes. In fact, you could guard your fillarray code with a trigger from the cabbageChanged opcode too. That way you don’t have to make whole changes to your code, but will still prevent it from being hit on each k-cycle :thinking:

Claver idea! Many ways of making an efficient hybrid :slight_smile:

I think my confusion with this comes from assuming that cabbageGetValue would only operate when widget changes value, maybe that is true when it stands outside array, loop, or is it ever true? I guess at least my understanding of cabbageSet is true, which updates widgets only when a new k-value appears on its doors?

cabbageGetValue will query a channel on every k-cycle UNLESS you use the optional trigger input, i.e,

k1 cabbageGetValue "slider", metro(1)

Yes, the k-rate version will not do anything unless you trigger it with a k-value of 1. The i-rate version will trigger at init time and never again.