Cabbage Logo
Back to Cabbage Site

cabbageGetValue in a k-rate loop

I’m getting widget channel names with cabbageGetWidgetChannels and I’d like to update a table only when any of the widget values change. This works find if I manually detect value change by spelling out all the widgets like:
kVal, kTrig cabbageGetValue "channelName",
but if I try to loop through the channels, cabbageGetValue triggers also when value is not changed, presumably due to “banging” it with a k-rate index. I get a similar issue if I use changed:k(kVal) instead of kTrig. I am puzzled why changed detects a change while kVal is constant?

Is there a way to scan through the widgets in a loop like below without getting value change triggers all the time?

Example:

    <Cabbage>
    form caption("test loop cabbageGetValue") size(330, 290) pluginId("cgv1") colour(0, 0, 0) guiMode("queue")

    rslider bounds(8, 8, 40, 70) range(0, 10, 1, 1, 0.0001) channel("par1") text("Par1") popupText("0") _getMe(1)
    rslider bounds(46, 8, 40, 70) range(0, 10, 2, 1, 0.0001) channel("par2") text("Par2") popupText("0") _getMe(1)
    rslider bounds(86, 8, 40, 70) range(0, 10, 3, 1, 0.0001) channel("par3") text("Par3") popupText("0") _getMe(1)

    </Cabbage>
    <CsoundSynthesizer>
    <CsOptions>

    -n -d -m0d -+rtmidi=NULL 

    </CsOptions>
    <CsInstruments>

    ksmps   = 32 

    instr 1
    Sliders[] cabbageGetWidgetChannels "_getMe(1)"
    printarray Sliders, 1

    iN = lenarray(Sliders)
    print iN

    kind = 0
    until kind == iN do
       kVal, kTrig cabbageGetValue Sliders[kind]
    
       if kTrig == 1 then
          printk 1, kVal
       endif

       kind += 1
    od

    endin

    </CsInstruments>
    <CsScore>
    f0 z
    i 1 0 -1
    </CsScore>
    </CsoundSynthesizer>

You can call cabbageGetValue without the trigger arg, it’s optional?

[edit] just reading this a little more closely now. Yes, I am not sure if one can loop through the opcodes like this for changes. If the same problem happens with a changed opcode, I think you may need a different approach. Let me know try some things…

Yeah, it’s as I thought, if you try this one would expect the kCombinedTrig to be above 0 each time one changes a slider, but that’s not the case :thinking:

instr 1
    Sliders[] cabbageGetWidgetChannels "_getMe(1)"
    printarray Sliders, 1

    iN = lenarray(Sliders)
    print iN

    kind = 0
    kCombinedTrigger = 0
    until kind == iN do
       kVal, kTrig cabbageGetValue Sliders[kind]
       kCombinedTrigger+=kTrig
       kind += 1
    od

    printk2 kCombinedTrigger
   
endin

Ah yes, this is the reason I wrote the array variants of chnget/chnset for Csound. They will do the job fine here. Here is an example:

instr 1
    Sliders[] cabbageGetWidgetChannels "_getMe(1)"
    printarray Sliders, 1

    iN = lenarray(Sliders)
    print iN

    kValues[] chngetk Sliders
    
    if changed2(kValues) == 1 then
        printks "Hello\n", 1
    endif
endin

Might be worth adding the same to the cabbageGet opcodes. I have a good example to test with at least :wink:

Interesting, I was not aware of the chngetk opcode. Doesn’t seem to be documented?
I could update the entire table with this.

But how could I detect which slider was changed?
The example below seems to have the same issue of constantly detecting changes.

kind = 0
until kind == iN do
if changed2:k(kValues[kind])==1 then
printk 1, kind
endif
kind += 1
od

[edit]
I actually came across this issue before and realized that I have to do something like below instead of looping - to have triggers in an array ready to use in a loop:

kSaveONTrig[] fillarray \
trigger(cabbageGetValue:k("IO_SavePar1"),0.5,0),\ ;0->1
trigger(cabbageGetValue:k("IO_SavePar2"),0.5,0),\
trigger(cabbageGetValue:k("IO_SavePar3"),0.5,0),\
trigger(cabbageGetValue:k("IO_SavePar4"),0.5,0),\
trigger(cabbageGetValue:k("IO_SavePar5"),0.5,0),\
trigger(cabbageGetValue:k("IO_SavePar6"),0.5,0),\
trigger(cabbageGetValue:k("IO_SavePar7"),0.5,0),\
trigger(cabbageGetValue:k("IO_SavePar8"),0.5,0),\
trigger(cabbageGetValue:k("IO_SavePar9"),0.5,0),\
trigger(cabbageGetValue:k("IO_SavePar10"),0.5,0),\
trigger(cabbageGetValue:k("IO_SavePar11"),0.5,0),\
trigger(cabbageGetValue:k("IO_SavePar12"),0.5,0),\
trigger(cabbageGetValue:k("IO_SavePar13"),0.5,0),\
trigger(cabbageGetValue:k("IO_SavePar14"),0.5,0),\
trigger(cabbageGetValue:k("IO_SavePar15"),0.5,0),\
trigger(cabbageGetValue:k("IO_SavePar16"),0.5,0)

It would be great if there are other opcodes to deal with triggers which I don’t know yet?

Why not something like this:

ksmps   = 32 

opcode IndexOfChanged, k,k[]
    kIncomingValues[] xin
    kCurrentValues[] init lenarray(kIncomingValues)
    kIndex = 0
    kChangedIndex init 0
    while kIndex < lenarray(kIncomingValues) do
        if (kIncomingValues[kIndex] != kCurrentValues[kIndex]) then
            kChangedIndex = kIndex
        endif
        kIndex += 1
    od

    kCurrentValues = kIncomingValues

    xout kChangedIndex
endop


instr 1
    Sliders[] cabbageGetWidgetChannels "_getMe(1)"
    printarray Sliders, 1
    iN = lenarray(Sliders)
    kValues[] chngetk Sliders    
    k1 IndexOfChanged kValues
    printk2 k1
endin

Those opcodes are documented, but on the same page as the regular channel opcodes.

That is useful! Thanks!
But seems a bit expensive?
I could live with that though :slight_smile:

It shouldn’t be too expensive in this instance. But with dozens of channels it might get that way. I could write an opcode that accepts an array of channels, and returns the channel that has changed, or an array of channels that have changed :thinking:

1 Like

Of course I have many channels :slight_smile:
I think such opcodes could be useful. Both index or sorted array could be useful - Matlab style :slight_smile:

In combo with the new cabbageGetWidgetChannels this could be very useful, making bookkeeping really elegant.

Another issue I have is with triggering when crossing threshold value. Maybe I got into a blind road, but I’d like to have a trigger type opcode which could scan an array, but that is probably more of a Csound wish and I don’t know if it is feasible. I tried modifying your code above with the condition:
kCurrentValues[kind] <= iThresh && kIncomingValues[kind] > iThresh
to detect crossing over iThresh value, but I’d also need it to send out a “reset” message (maybe a negative index) kind of like the trigger which return to 0 after the k-cycle. Hmmmm… maybe index is not enough and a “bang” message is needed as well…?

Just a comment on CPU load. Testing with 100 sliders, it seems that

kValues[] chngetk Sliders

is notably faster than

kValues[] init iN
kind = 0
until kind == iN do
kValues[kind] = cabbageGetValue:k(Sliders[kind])
kind += 1
od

which is probably not surprising due to lower level background code…?
So for my purpose, it might be cheaper to copy the entire array to function table when any widget changes instead of copying selected widget value to table:

iTable ftgen 0, 0, -iN, -2, 0
copya2ftab kValues, iTable

I guess there is no other more direct way of getting channel data into function tables?

Yes, it’s not at all surprising. I could add an array variant of cabbageGetValue, something like

kValues[] cabbageGetValue SChannels[]

That might be useful too.

This is all starting to sound a little complex :thinking: I wonder if you couldn’t simplify things?

1 Like

Yes, I could simplify it :slight_smile: As usually, it helps to have a chat with a friend here :smile:
I’m saving channel values to a subset of a table depending on the trigger button (think of it like saving slots). The entire table is then my collection of presets. I know you have introduced cool new ways of saving presets, but I haven’t tried it all out yet and I like to do it this way because I can then change presets (sub-presets) on the fly with a MIDI controller mapped to buttons (preset slots). One use case could be to have elements (modes) of a generative composition all saved in one preset collection (one song) and traverse those presets in an improvised setting.

However, I often use triggers on momentary buttons, I find them useful to detect threshold crossings. But they don’t work in a loop.

That’s great! Thanks!

Will this also include a set of Triggers?

kValues[], kTrig[] cabbageGetValue SChannels[]

I won’t get a chance to write this for a few days, but I guess it could, should?

Thanks.

If we can’t detect a change in a loop (as discussed above), it might be very useful. And maybe it can be more efficient than the UDO with internal copy of the array?

My dream would be to get even a Cabbage trigger opcode for arrays, but since there is no (?) trigger for arrays in Csound it might be unreasonable to wish something like that in Cabbage?

Au contraire. If it’s not in Csound, it might as well be in Cabbage :+1:

1 Like

Very exciting! :yum: