Cabbage Logo
Back to Cabbage Site

New cabbageChanged opcode

cabbageChanged Opcode

(updated 3rd August 2021 to include optional arguments)

This opcode takes an array of channel names and listens for a change. It reports a trigger value along with the name or index of the channel that changed. An Additional input parameters can cause this to act like a threshold trigger where it will only fire a trigger signal when a threshold is crossed.

The channels can hold strings or numbers, but only numeric channels work with the optional threshold arguments.

Added in Cabbage v2.7.12


SChannel, kTrig cabbageChanged SChannels[], [kThreshold, [kMode]]
kIndex, kTrig cabbageChanged SChannels[], [kThreshold, [kMode]]


  • SChannels[] – an array of all the channels you wish to monitor for changes.


  • SChannel – The channel that was most recently changed
  • kIndex – The index of the channel that was changed in relation to the input array
  • kTrig – will output a trigger value of 1 whenever a channel changes, or a threshold has been crossed.
  • kThreshold – [optional] if added, will output a trigger value of 1 whenever a channel changes to this value.
  • kMode == [optional] Defaults to 2. There are three modes:

kMode = 0 - (down-up) kTrig outputs a 1 when current channel value is higher than kThreshold, while old channel value was equal to or lower than kThreshold.

kMode = 1 - (up-down) kTrig outputs a 1 when current channel value is lower than kThreshold while old channel value was equal or higher than kThreshold.

kMode = 2 - (both) kTrig outputs a 1 in both the two previous cases.


form caption("Cabbage Changed") size(430, 290) pluginId("tl01") guiMode("queue")
rslider bounds(10, 10, 100, 100), channel("slider1")
rslider bounds(110, 10, 100, 100), channel("slider2")
rslider bounds(210, 10, 100, 100), channel("slider3")
rslider bounds(310, 10, 100, 100), channel("slider4")
combobox bounds(20, 118, 80, 20) channel("combo1"), channelType("string")
combobox bounds(120, 118, 80, 20) channel("combo2"), channelType("string")
combobox bounds(220, 118, 80, 20) channel("combo3"), channelType("string")
combobox bounds(320, 118, 80, 20) channel("combo4"), channelType("string")
label bounds(8, 158, 412, 21) channel("label1"), align("left"), fontColour(0, 0, 0, 255) text("Most recently changed widget:")
-n -d -m0d -+rtmidi=NULL 
ksmps   = 32  

instr 1
    ;grab all widget channels
    SWidgetChannels[] cabbageGetWidgetChannels
    ;this version will return the name of the channel
    SUpdatedChannel, kTrig cabbageChanged SWidgetChannels

    ;this version will return the index of the channel
    kIndex, kTrig cabbageChanged SWidgetChannels
    ;update label with info
    cabbageSet kTrig, "label1", sprintfk("text(\"Last updated widget: %s - Index:%d\")", SUpdatedChannel, kIndex)

i1 0 z

Very useful! Thanks!

This is awesome - saves a lot of extra lines of code. Would be cool to return index number too from the array. My use-case is that I have correlating controls. I move one and I want it to affect another with the same index number.


idx, kTrig  cabbageChanged  SChannels[]
cabbageSetValue sprintfk("%s", rslider[idx]), numbox[idx]*0.75, 1

I can add that as an optional output.

Having the index would be very useful, actually more useful than the string since we already have strings in the SWidgetChannels. I’d prioritize the index and rather have the string as an optional output.

I’ve added an index variant and updated the description and example above :+1:


So it detects whether to output a string or the index? Or is there something wrong in the syntax description?

Correct. Depending on the output argument types you pass it.

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:


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,

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)
-n -d -+rtmidi=NULL -M0 -m0d 
; Initialize the global variables. 
ksmps = 32
nchnls = 2
0dbfs = 1

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

i1 0 [60*60*24*7] 

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.