Cabbage Logo
Back to Cabbage Site

Bye-bye `identchannel()` Maybe? PLEASE READ!

TLDR; New system brings massive performance gain when using lots of widgets…

One aspect that has really bugged me about the current design of Cabbage is how inefficient it is to send data from Csound to Cabbage. Cabbage to Csound is fine. We move a slider, and it updates Csound. The other way around is pretty awful because Cabbage has to poll every single widget for a channel change on each k-boundary1. The more widgets you have the more of a bottle neck you encounter. And it doesn’t even matter if you are updating things or not. And it’s worse for identifier channels, because they have to handle strings.

So in the past few days I’ve thrown together an experimental cabbageSet opcode that will update a Cabbage widget directly. This removes the need for Cabbage to poll every widget’s identifier channel on each k-cycle in the hope that one has changed. The opcode syntax currently looks like this:

kSucces cabbageSet kTrig, SChannel, SIdentifer, xArg1, xArg2, etc…

Typical use of this new opcode would be something like this:

kSuccess cabbageSet metro(20), "image1", "bounds", random:k(0, 260), random:k(0, 290), random:k(0, 10), random:k(0, 40)

or

kSuccess cabbageSet metro(20), "button1", "text", "start", "stop"

This will cause the "image1" widget to update 20 times a second. When I tested did, I created 1000 checkbox widgets and set their positions randomly on each k-cycle (don’t try this at home :rofl: ). Needless to say it caused a large spike in my CPU, and when I tested with the current mechanism I saw only a small gain in performance. But here’s the rub. I tested the current identchannel() system again, only this time I commented out the chnset stuff. Although it wasn’t updating anything in Cabbage, it was still running at max CPU. In contrast, the new system uses no processing power UNLESS a widget is updated. To put it in perspective, my CPU goes from 114% to 6% when I swap to the new system.

So this got me thinking about regular channels too. They are also being polled on each k-cycle. It’s more efficient because there is less string allocation happening, but it still polls every widget with a channel assigned, on each k-cycle. So I added support for cabbageSet to set the value of a widget also:

kSuccess cabbageSet metro(20), "freqSlider", "value", random:k(0, 1000)

This again is much faster than the current system.

And finally. I’ve never been a fan of having two different channel identifiers. I always thought it would be better to have a single identifier that can be used for two-way communication. This is possible with this new system. Instead of declaring an identchannel(), we can just use the channel() identifier. I also plan to wrote a complimentary opcode called cababgeGet, although chnget will still work fine.

kValue cabbageGet SChannel, SIdentifier

And in use:

kFreq cabbageGet "slider1", "value"

The cabbageGet opcode will work with the current system, but for cabbageSet to be most effective, we have to disable the old polling system. The only way to do this without breaking backwards compatibility is to add polling() identifier, that is set to true unless explicitly told otherwise. So all new Cabbage code should have polling(0) in the form declaration.

Negatives

One drawback to the new system is you can’t set a load of identifiers in one go:

SMessage sprintfk "pos(%d, %d), colour(%d,%d,%d)", 24, 234, 23, 23, 234
chnset SMessage, "image1" 

Another drawback is you will need to use cabageSet if you want to update the value of any widgets from your Csound code. Because the older polling methods will be disabled, the following simple line of code will no longer cause Cabbage to update the value of the slider “gain1”

chnset k(1), "gain1"

I’m curious to know your thoughts. Or if you have any suggestions. One added bonus to this system if we can potentially query the state of any widget identifier. For example, we can easily find it’s current position:

kPos[] cabbageGet "slider1", "pos"

Or bounds()

kBounds[] cabbageGet "slider1", "bounds"

etc. I know some of you will find that useful.

THE END :rofl:


1 You can use the guirefresh() identifier to increase the number of k-cycles between each update. This identifier was added because of the bottleneck in updates.

3 Likes

A quick follow-up. I’ve now added the following opcodes (note the set opcodes have no outputs):


cabbageSet kTrig, SChannel, SIdentifer, xArg1, xArg2, etc…


Will set an identifier for a widget identifier. kTrig is a trigger signal. Updates will only occur when it is one. SChannel is the channel name, SIdentifier is the identifier, i.e, “size”, “colour”, etc. xArg1, aArg2, etc, are arguments to the identifier. These can be strings or number values. They must match the number of argument given for an identifier.

Note: If SIdentifier is empty, Cabbage will expect a line of Cabbage code to parse. For example:

    SMessage sprintfk "pos(%d, %d), colour(%d,%d,%d)", 12, 43, 56, 75, 23
    cabbageSet metro(2), "image1", "", SMessage 

This way you can continue to update lots of identifiers in a single go, if you wish, but not it will be marginally slower.


cabbageSetValue SChannel, kValue, [kTrig]


Set a widget value, for example the current value of a slider, combobox, button, etc. SChannel is the channel name and kValue is the value you want to send. kTrig is optional and default to 1, which means it will send the value on each k-cycle.


kVal cabbageGetValue SChannel
iVal cabbageGetValue SChannel
SVal cabbageGetValue SChannel


Gets the current value of a Cabbage widget. Works exactly like chnget, but offers better continuity with the other opcodes.


kVal cabbageGet SChannel, SIdentifier
iVal cabbageGet SChannel, SIdentifier
SVal cabbageGet SChannel, SIdentifier
SVal[] cabbageGet SChannel, SIdentifier
kVal[] cabbageGet SChannel, SIdentifier
iVal[] cabbageGet SChannel, SIdentifier


These opcodes will allow you to grab the current state of any identifier associated with a Cabbage widget. If the identifier has multiple arguments, they will be returned as an array, otherwise they will return a single value. This is the first time we have been to able to access all Cabbage identifiers within Csound. :exploding_head: I’m can’t way to see what to see what kinds of ways this feature get (ab)used :rofl:

p.s., I’ll let you know when this is ready for public testing…

This is very interesting… it sounds like projects with a large numbers of widgets could benefit greatly from doing it this way, which is a category I definitely fall into!

First question that comes to mind: Will identchannel support actually be going away, or will it be a supported but not really recommended method?

A big benefit of the identchannel method that I see is it’s more portable. While the code itself doesn’t “function” per-se, it also does cause any errors or problems, so I can literally copy paste the code into a cli csound, blue, or (probably) QT to use as a jumping off point.

This new method I expect will cause problems in that process… but they will be easy to find and correct errors all the same :wink:

Converting projects like mine that have lots of shared include files and imported widget files to this new method will probably be a nightmare, tho worth it in the long run. I dug myself into that hole tho :face_with_symbols_over_mouth:

I don’t really know a whole lot about modern “performance profiling”, but do you think the gains would be enough to bother trying to quantify with numbers on some really large and complicated examples, if nothing else for bragging rights?

I’ll keep churning this idea over in my head tho… I can’t even imagine what people might think to do with this yet. :exploding_head:

That’s why I pinged you :laughing:

It won’t go away anywhere. There is no performance cost in it being there but not being used, so I’m happy to keep supporting it.

Yes, this is the reason I choose to do all of this with channels in the first instance. I like how frontend agnostic the actual Csound code is. It’s a drawback for sure, as it makes any code that uses these opcodes very much rooted to Cabbage.

You can always stick with identchannels(), but are you not now wondering if plants and imported plants could all be managed in pure Csound code? I am :thinking: It should be possible to dynamically generate widgets when they are needed, but hosts won’t register them as parameters unless they are declared at startup. Still, I’m already thinking of ways around this. Wouldn’t this be cool:

instr 1
   iCnt = 0
   while iCnt < 10
      SCab sprintf "rslider bounds(%d 10, 100, 100) channel(\"freq%d\)", iCnt*100, iCnt
      cabbageCreateWidget Scab
   od
endin

I could do an init-time pass of the orchestra before creating any parameters in Cabbage. You could program your entire GUI in Csound. :astonished:

I thought my 1000 widget GUI example was pretty complicated?!? But I wouldn’t be surprised if you have several orchestra lying around that top it :laughing: I’ve seen your instruments!!

I know. I’m excited and worried at the same time!

I agree that doing away with two channel names for handling information to and from a widget will be an improvement.

It seems that cabbageSet will do away with the need to create strings every time we need to send some sort of a message to a widget.

Harvesting arrays of information from widgets using cabbageGet is a nice idea, so initialisation of the widget attributes is done in the widget code and this can then be grabbed by Csound.

It’s a relief that identchannel will still work in the meantime.

It’s a bit of a shame to have to introduce Cabbage-specific opcode, but I guess there’s no other way unless you want to market it as a generic alternative to chnset/invalue that might have uses for other potential frontends.

Overall :+1:

Hi Iain. I’m very happy to get your thoughts on this, even though neither of us seem to be using Cabbage all that much these days :laughing: It’s still my main Csound IDE, but these days I find myself writing Csound code in Cabbage that will be used elsewhere. I don’t think I have used an identchannel() in many years!

Yes, but the option the to pass long strings to it is still available. But it’s certainly nice to have the option to avoid this now.

Great. I’ll take that. Having this kind of access to each widget’s data structure is pretty powerful. I’m still thinking of new ways to use it. Still work to be done, but the basic opcodes are working fine. I just need to make sure they can handle all identifiers. That’s going to be pretty boring work :yawning_face:

Hi Rory. I have a few composers using some new Cabbage instruments and effects as end-users and they seem quite happy in that role, describing themselves as Csound-users. I’m also slowly going through everything I’ve created previously to improve and update so I my eyebrows raised when I thought I might have to update everything that involved an identchannel!

1 Like

Another interesting side-effect of this new approach is that it is now possible to define custom identifiers. The following works fine:

<Cabbage>
(...)
label bounds(10, 10, 95, 36), channel("label"), myNewIdent("this is a label")
</Cabbage> 

This can be picked up in Csound by simply calling cabbageGet

SChan cabbageGet "label", "newIdent"
prints SChan

Pretty neat. We can also pass as many elements we like. They will be parsed as arrays. The only danger is that people will use an identifier that might some day be a valid Cabbage identifier. Perhaps custom identifiers should always be capitalised, i.e, INFO(…), NAME(…), etc.?

1 Like

I’m jut thinking that I might provide versions of the cabbageGet opcodes that also provide a trigger value when the opcode’s value has changed.

kValue, kChannged cabbageGet SChannel, SIdentifier

So we can avoid having to endlessly use the changed() opcode. I don’t know, it might be a nice option. And it’s easy to overload the opcodes so that it’s optional.

Hey Rory, seems very intresting, especially when an instrument uses many widgets :wink: What I love i that you can now use the channel name for the CabbageSet. So ne need to add an extra identChannel name, that’s nice !

I’m a little bit worried about something tho :

So is the old chnset “deprecated and not recommended” or “not supported anymore = will no longer work”
Exemple, if a code use chnset "visible(0)", "GROUP_ABOUT" will it still work ?
Thanks in advance,

That will still work so long as you are using the older system. I don’t plan to change that code at all as it would break people’s instruments. I’d like to ensure that older code still runs fine :+1:

1 Like

Hi, sorry for coming late to the party. Just wanted to chime in that I think this sounds great. Thanks for caring, Rory! As always, one tiny bit concerned about backwards compatibility, so: how do we choose to use the old or the new system? I see that the use of cabbageGet/Set is a clear difference, but in an old csd, how will Cabbage know that it should always poll all the chn channels?
Otherwise, old csd that are built to expect automatic chn polling will not update the gui anymore?

Hi @Oeyvind. Good to hear from you! You will need to explicitly enable guimode("queue") to disable the older system and use the newer one, so all older Cabbage .csds will continue to work away just fine. At some point I will add guimode(“queue”) to the template code that Cabbage generates when you create a new instrument. I can also put it in some warnings about using identchannels() but they can get a little annoying, so I will use them sparingly!

Perfect. It is a good solution.

1 Like