Cabbage Logo
Back to Cabbage Site

New cabbageChanged opcode

Cool! But it seem unconventional? Or maybe it is more common then I’d like it to be?

In the sense that it is overloaded? What would you rather?

I was just wandering about consistency, and now it actually makes good sense like that since we probably won’t need both the index and the name at the same time. I’m drifting out of phase after a long day here, but at the moment it seem ideal to have kTrig, [kIndex, SChannel], latter two as optional, or kTrig would not be even needed if kIndex would be negative whenever is not changed.

just because you would need to use 3 outputs if you want to access the channel name. Seems clunky to me, but tbh, we’re probably just splitting hairs at the meta level now! It would be best to admit that we are bought absolutely 100% correct in syntactic assessment, and leave it at that :rofl:

btw, my tendency to overload opcodes might cause issues at some point, especially with strings. The cabbageSetValue opcodes that handle strings at k-rate need to be explicitly told the string are k-rate. Therefore the strings have to be wrapped in a sprintfk opcode. The alternative was to create cabbageSetValuek and cabbageSetValuei opcodes which I find rather ugly. The problem is that all strings are actually i-rate by definition, but they can be updated at k-rate using k-rate string opcodes. :grimacing:

Nice.

1 Like

@Samo, I’ve added an optional argument to the cabbageChanged opcode. If added, a trigger will only be output each time the channel hits this target value. In the example below, the triggers only fire when a button is turned on, as opposed to firing whenever it changes between on and off,

<Cabbage>
form caption("Radio") size(400, 300), guiMode("queue") pluginId("def1")
button bounds(10, 10, 80, 30), latched(1) channel("button1"), radioGroup(10)
button bounds(100, 10, 80, 30), latched(1) channel("button2"), radioGroup(10)
button bounds(200, 10, 80, 30), latched(1) channel("button3"), radioGroup(10)
button bounds(300, 10, 80, 30), latched(1) channel("button4"), radioGroup(10)
</Cabbage>
<CsoundSynthesizer>
<CsOptions>
-n -d -+rtmidi=NULL -M0 -m0d 
</CsOptions>
<CsInstruments>
; Initialize the global variables. 
ksmps = 32
nchnls = 2
0dbfs = 1

instr 1
    SChans[] cabbageGetWidgetChannels "radioGroup(10)"
    SChannel, kTrig cabbageChanged SChans, 1
    printf SChannel, kTrig
endin

</CsInstruments>
<CsScore>
i1 0 [60*60*24*7] 
</CsScore>
</CsoundSynthesizer>

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?