Cabbage Logo
Back to Cabbage Site

New Cabbage widget opcodes

I’ve pushed through the latest changes. They are available now in Azure. Note that in order to use these you should add guimode("queue") to your form definition. Below is a quick rundown of new opcodes, and here is a short video overview of them in action.


cabbageSet kTrig, SChannel, SIdentifier, XArgs
cabbageSet kTrig, SChannel, SIdentifierString
cabbageSet SChannel, SIdentifierString

Sets/updates widget identifiers whenever kTrig is 1, or if you leave out the kTrig, it will set it at i-time. Identifiers can be updated by passing arguments to a single identifier, as show below. You can also set multiple identifiers as a single string.

Example:

cabbageSet metro(1), "image1", "colour", random:k(0, 255), random:k(0, 255), random:k(0, 255)
cabbageSet metro(1), "image1", "colour(255, 0, 0), bounds(10, 10, 100, 100)"

cabbageSetValue SChannel, kValue [, kTrig]

Set the value of a widget, for example the value of a slider, or combobox. kTrig is optional. If left out, this will update on every k-cycle.

Example:

cabbageSetValue "tempo", random:k(1, 100)

Let me know how you get on. I’ve not tested these thoroughly yet. Some identifiers might not work yet, but just let know and I will sort them out asap.


kValue cabbageGetValue SChannel
SValue cabbageGetValue SChannel
iValue cabbageGetValue SChannel
kValue [, kTrig] cabbageGetValue SChannel
SValue [, kTrig] cabbageGetValue SChannel

Gets the current value of a widget, for example the current value of a slider, combobox, button, etc. You can optional an a trigger output variables, which will send a trigger signal of 1 whenever the value has changed.

Example:

kButtonValue, kTrig cabbageGetValue "mute"

kIdent cabbageGet SChannel, SIdentifier
SIdent cabbageGet SChannel, SIdentifier
kIdent[] cabbageGet SChannel, SIdentifier
SIdent[] cabbageGet SChannel, SIdentifier
kIdent [, kTrig] cabbageGet SChannel, SIdentifier
SIdent [, kTrig] cabbageGet SChannel, SIdentifier

kChannelValue [, kTrig] cabbageGet SChannel
kChannelValue cabbageGet SChannel
SChannelValue [, kTrig] cabbageGet SChannel
SChannelValue cabbageGet SChannel

Use to get the current value of an identifier. For example, the button text for a particular widget, or the colour of a widget. Widget must have a channel name specified. Where only a single value is passed to this widget, it will query a plain old channel. For example, you use it with the CURRENT_WIDGET reserved channel to find out which widget is currently in focus:

SChannel, kTrig cabbageGet "CURRENT_WIDGET"
printf SChannel, kTrig

cabbageCreate SCabbageCodeString

Used to create a Cabbage widget at init-time. (only works at init-time!)

Example:

cabbageCreate SWidget "rslider bounds(10, 10, 100, 100) channel(\"slider1\")"

If you create your GUI using this opcode, you will no longer have any control over them using the Cabbage GUI editor.

1 Like

Awesome Rory! I’m not going to get a chance to start playing with this until tomorrow… but I had a quick question:

Does claiming this mode disable regular identchannels, or can both be used in a “hybrid” sense?

It’s one or the other I’m afraid. Well, it would be possible to have both enabled, but I think that it would need to be strictly for debugging and testing purposes? I could add a ‘hybrid’ option, but it’s not something I’d be putting in the docs!

Well, especially with regards to imported widgets… I see a real benefit in having the behavior potentially be hybrid. For example, if I make an importable widget that uses a gentable, I’ll be forced to use the new method… but that prevents anyone from easily importing into existing projects, at least without a complete overhaul.

Plus… with a massive project with tons of includes and imports (sound familiar? :innocent:) a hybrid behavior would allow incremental conversion and testing, rather than just going all in and hoping for the best (and struggling to find which change I messed up)

Just my two cents on it at least.

I can add a custom option then, but it will not be very efficient and will be a waste a CPU. But if you only ever plan to use it for testing it should be fine. Btw, have you thought at all about replacing any of the custom plant stuff with UDOs? I must try this myself and see how feasible it is. I like the idea of having all the code centralised.

Well, I definitely think it’s worth having it, even if it’s documented as being functional but inefficient and not recommended for most cases.

But yes… that’s an incredibly interesting thought that hadn’t crossed my mind at first, until the through the looking glass example. Unfortunately I’ve just been getting caught up with other things the past few days and still haven’t had a chance to get a new beta, nevermind actually run it and test stuff.

Another use case I wanted to look into, detecting the total cabbage window size? I’m guessing that would be a possibility here, right?

Yes, no issues there.

Let me take one of the numerous examples we shared from way back and see how it runs as a UDO. I’ll let you know :slight_smile:

Yeah, it works quite well.

Hmm. This is quite nice. At long last we can combine the GUI stuff directly with the DSP stuff. It would be trivial to write a UDO that would create a generic UI for an orchestra. That could be useful for prototyping.

This does look quite impressive! Tomorrow I’m finally going to update and play with a few simple examples and ideas to try to get myself used to it.

What’s the final word on a hybrid behavior? I can’t even imagine trying to convert my entire project from identchannels in one go… :cold_sweat:

Sorry, I will try to add that asap…

I’ve added this now. just don’t forget about the new camelCase restrictions. You need to do
guiMode("hybrid")
Btw, I have tested this, but it should work as all mechanisms get called in this instance. :+1:

Hello Rory and congrats on the new features. I think it’s great. One thing, what can we do with texteditor widgets now?

cabbageSetValue “myTextEditorChn”, SmyString

does that work to fill a text editor and what about

cabbageSet metro:k(1), “myTextEditorChn”, “file”, “read-100.txt”

Also i don’t get cabbageCreate yet, but I wanted that feature. How many input arguments does the opcode take on the right?

best to do it like this (i-time - I just added this now, will be available in Azure shortly…):

cabbageSet "myTextEditorChn", "text", "post this to the text editor"

or (k-time)

cabbageSet metro(1), "myTextEditorChn", "text", "post this to the text editor"

This is now possible too, if you use the latest beta, which is building as I type…

Just the one. Here’s a few different ways of using it. The method using {{'s is handy because you don’t have to escape quotation marks:

instr 1
    //using {{'s
    cabbageCreate {{ 
    rslider bounds(10, 10, 100, 100), channel("test")
    }}

    //using sprintf to dynamically create the Cabbage code
    SCode = sprintf("rslider bounds(%d, %d, 100, 100)", 50, 50)
    cabbageCreate SCode

    //using "s
    cabbageCreate "rslider bounds(200, 200, 100, 100)"
endin

I’m trying to learn and translate some code to the new guiMode("queue") and I have a few questions (for now).

Having these widgets:

<Cabbage>
form caption("test CabbageSetGet") size(300, 250) colour(58, 110, 182) pluginId("tcsg") guiMode("queue") 
rslider bounds(4, 28, 120, 120) range(1, 2000, 200, 1, 1) channel("Frq") popupText("0") colour(200, 200, 100, 255) trackerColour(200, 200, 100, 255)
button bounds(126, 20, 65, 65) latched(0) channel("X2")  text("x2","x2") value(0)  corners(3) colour:0(255, 0, 0, 255) colour:1(255, 255, 0, 255) fontColour:0(0, 0, 0, 255) fontColour:1(160, 0, 0, 255)
button bounds(126, 88, 65, 65) latched(0) channel("X05") text("/2","/2")  value(0)  corners(3) colour:0(0, 255, 0, 255) colour:1(255, 255, 0, 255) fontColour:0(0, 0, 0, 255) fontColour:1(160, 0, 0, 255)
<Cabbage>

This works fine:

kTrig2 = trigger(cabbageGetValue:k("X2"), .5, 0)
cabbageSet kTrig2, "Frq", "trackerColour(255, 0, 0, 255), markerColour(255, 0, 0, 255)"

but not these:
cabbageSet kTrig2, "Frq", "markerColour", 100, 0, 0, 255
nor
cabbageSet kTrig2, "Frq", "trackerColour", 0, 100, 0, 255

Having these widgets:

filebutton bounds(154, 190, 70, 14) channel("SaveAs")  mode("save") value(0) text("SAVE AS", "SAVE AS")  corners(3) colour:0(100, 0, 0, 255) colour:1(255, 255, 0, 255)   fontColour:1(160, 0, 0, 255)
filebutton bounds(76, 190, 70, 14)  channel("Browse") value(0) text("BROWSE", "BROWSE")  corners(3) colour:0(140, 200, 30, 255) colour:1(255, 255, 0, 255) fontColour:0(160, 0, 0, 255)

I used to:

S_SaveFileName chnget "IO_SaveAs"
S_LoadFileName chnget "IO_Browse"

but how do I get the strings now? What is the SIdentifier in this case?
S_SaveFileName cabbageGet "IO_Browse", SIdentifier?

S_SaveFileName cabbageGetValue "IO_Browse" doesn’t work.

Another thing I just wanted to check if I got it right: If I use the two buttons above to change the Frq value and update the slider, I shall do something like this:

kFrq = cabbageGetValue:k("Frq")
kTrig2 = trigger(cabbageGetValue:k("X2"), .5, 0)
kTrig05 = trigger(cabbageGetValue:k("X05"), .5, 0)

if kTrig2 == 1 then
    kFrq *= 2   
    cabbageSetValue  "Frq", kFrq
elseif kTrig05 == 1 then
    kFrq *= 0.5   
    cabbageSetValue  "Frq", kFrq
endif
chnset  kFrq, "Frq"

or

if kTrig2 == 1 then
    chnset  kFrq*2, "Frq"
elseif kTrig05 == 1 then
    chnset  kFrq*0.5, "Frq"
endif
cabbageSetValue  "Frq", kFrq, changed:k(kFrq)

So, I need to send a message to the channel and the widget. Is this the optimal way to deal with this (if I want to update both) or would you have other suggestions?

Fixed in git.

You are using different channel names. If I do this it works as expected:

S_SaveFileName chnget "SaveAs"
printf S_SaveFileName, changed(S_SaveFileName)

Using the new widgets you can do something like this:

S_SaveFileName, kSaveTrig cabbageGet "SaveAs"
printf S_SaveFileName, kSaveTrig

In this case there is very little difference between the chnget way of doing things and the new cabbageGet opcode.

You only need to either set the values using chset, or cabbageSetValue, not both. I think both versions of code should work so long as you remove the final call to the “frq” channel. On the other hand, it can be written more succinctly with the trigger version of the cabbageSetValue opcode:

kX2, kX2Trig cabbageGetValue "X2" 
k05, k05Trig cabbageGetValue "X05" 
cabbageSetValue "Frq", kFrq * 2, kX2Trig
cabbageSetValue "Frq", kFrq * .5, k05Trig

Thanks!

A silly mistake, sorry!
I see that cabbageGetValue also gets the string from filebutton

You only need to either set the values using chset , or cabbageSetValue , not both

But if I don’t use chnset, the kFrq doesn’t get updated, so pressing e.g. X2 repeatedly only moves the slider one time, since subsequent triggers use the old (unchanged) kFrq value. But if I only use chnset, then the widget doesn’t update (as expected in “queue” mode). So it seems I need to use both. Am I missing something?

In this case, i don’t think I follow what you are trying to do? Here’s my test, based on what I thought you were trying to do…

<Cabbage>
form caption("test CabbageSetGet") size(300, 250) colour(58, 110, 182) pluginId("tcsg") guiMode("queue") 
rslider bounds(4, 28, 120, 120) valueTextBox(1) range(1, 2000, 200, 1, 1) channel("Frq") popupText("0") colour(200, 200, 100, 255) trackerColour(200, 200, 100, 255)
button bounds(126, 20, 65, 65) latched(0) channel("X2")  text("x2","x2") value(0)  corners(3) colour:0(255, 0, 0, 255) colour:1(255, 255, 0, 255) fontColour:0(0, 0, 0, 255) fontColour:1(160, 0, 0, 255)
button bounds(126, 88, 65, 65) latched(0) channel("X05") text("/2","/2")  value(0)  corners(3) colour:0(0, 255, 0, 255) colour:1(255, 255, 0, 255) fontColour:0(0, 0, 0, 255) fontColour:1(160, 0, 0, 255)
filebutton bounds(154, 190, 70, 14) channel("SaveAs")  mode("save") value(0) text("SAVE AS", "SAVE AS")  corners(3) colour:0(100, 0, 0, 255) colour:1(255, 255, 0, 255)   fontColour:1(160, 0, 0, 255)
filebutton bounds(76, 190, 70, 14)  channel("Browse") value(0) text("BROWSE", "BROWSE")  corners(3) colour:0(140, 200, 30, 255) colour:1(255, 255, 0, 255) fontColour:0(160, 0, 0, 255)
</Cabbage>
<CsoundSynthesizer>
<CsOptions>
-n -d -+rtmidi=NULL -M0 -m0d 
</CsOptions>
<CsInstruments>
; Initialize the global variables. 
ksmps = 32
nchnls = 2
0dbfs = 1

instr 1
kTrig2 = trigger(cabbageGetValue:k("X2"), .5, 0)
cabbageSet kTrig2, "Frq", "markerColour", 255, 0, 0, 255
cabbageSet kTrig2, "Frq", "trackerColour", 0, 100, 0, 255


S_SaveFileName, kSaveTrig cabbageGet "SaveAs"
printf S_SaveFileName, kSaveTrig

kFrq = cabbageGetValue:k("Frq")
kX2, kX2Trig cabbageGetValue "X2" 
k05, k05Trig cabbageGetValue "X05" 

cabbageSetValue "Frq", kFrq * 2, kX2Trig
cabbageSetValue "Frq", kFrq * .5, k05Trig



endin

</CsInstruments>
<CsScore>
;causes Csound to run for about 7000 years...
f0 z
;starts instrument 1 and runs it for a week
i1 0 [60*60*24*7] 
</CsScore>
</CsoundSynthesizer>

In the example below, the iCase = 1 and 2 are what I want, but iCase = 3 is not sufficient. I am wondering if there are any alternatives?

I’m using a similar system to save and load presets from table “slots”. From non-latched buttons I need in this case only triggers on 0->1 not on change and button state is not needed. Perhaps something like “CabbageGetTrigger” with parameters similar to the trigger opcode could be useful (probably overkill)?

<Cabbage>
form caption("test CabbageSetGet") size(300, 250) colour(58, 110, 182) pluginId("tcsg") guiMode("queue") 
rslider bounds(4, 28, 120, 120)  range(1, 2000, 200, 1, 1) channel("Frq") popupText("0") colour(200, 200, 100, 255) trackerColour(200, 200, 100, 255) valueTextBox(1)
button bounds(126, 20, 65, 65) latched(0) channel("X2")  text("x2","x2") value(0)  corners(3) colour:0(255, 0, 0, 255) colour:1(255, 255, 0, 255) fontColour:0(0, 0, 0, 255) fontColour:1(160, 0, 0, 255)
button bounds(126, 88, 65, 65) latched(0) channel("X05") text("/2","/2")  value(0)  corners(3) colour:0(0, 255, 0, 255) colour:1(255, 255, 0, 255) fontColour:0(0, 0, 0, 255) fontColour:1(160, 0, 0, 255)

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

instr 1

; 1 - I need something like this
; 2 - or this
; 3 - this is not sufficient 
iCase = 1

kFrq cabbageGetValue "Frq"
; need trigger only for button push (0->1)  
kX2Trig = trigger(cabbageGetValue:k("X2"), .5, 0)
kX05Trig = trigger(cabbageGetValue:k("X05"), .5, 0)

; these trigger on change (0->1 and 1->0)
; kX2, kX2Trig cabbageGetValue "X2" 
; X05, kX05Trig cabbageGetValue "X05" 


if (iCase == 1) then 
   
    if kX2Trig == 1 then
        chnset  kFrq*2, "Frq"
    elseif kX05Trig == 1 then
        chnset  kFrq*0.5, "Frq"
    endif
    cabbageSetValue  "Frq", kFrq, changed:k(kFrq)
       
elseif (iCase == 2) then      
  
    if kX2Trig == 1 then
        kFrq *= 2   
        cabbageSetValue  "Frq", kFrq
    elseif kX05Trig == 1 then
        kFrq *= 0.5   
        cabbageSetValue  "Frq", kFrq
    endif
    chnset  kFrq, "Frq"
       
elseif (iCase == 3) then   

    cabbageSetValue "Frq", kFrq * 2, kX2Trig
    cabbageSetValue "Frq", kFrq * .5, kX05Trig
    
endif

printk2 kFrq
endin

</CsInstruments>
<CsScore>
;causes Csound to run for about 7000 years...
f0 z
;starts instrument 1 and runs it for a week
i1 0 [60*60*24*7] 
</CsScore>
</CsoundSynthesizer>

Can you check the value of the button at the same time? So if the button has changed AND its value is 1:

if kX2Trig == 1 && cabbageGetValue:k("X2") == 1 then

Wouldn’t that work?

Sure that works, but requires handling two variables.

So, I guess you have no other suggestions regarding the need to set both channel and widget (cases 1 & 2 vs case 1 above)?

If the button are non latched you can just check their value? They will send a 1 for only a single k cycle. No need for triggers at all 🤷

kX2 = cabbageGetValue:k("X2")
kX05 = cabbageGetValue:k("X05")

cabbageSetValue  "Frq", kFrq*2, kX2
cabbageSetValue  "Frq", kFrq*0.5, kX05