Cabbage Logo
Back to Cabbage Site

Two buttons that do the same thing

I have buttons that control the same variable. One is a “mute” button, the other is a button on an audio routing matrix. I’d like the user to be able to click on either and to have the non-clicked button update with the new state. Is that possible?

Hi,

This is something I’ve done in my projects, you can try the code below and see if it suits your needs!

<Cabbage>
form caption("Untitled") size(400, 300), guiMode("queue") pluginId("def1")
 
button bounds(246, 100, 100, 100), channel("button1") colour:1(163, 134, 255, 255)
button bounds(30, 98, 100, 100), channel("button2") colour:1(163, 134, 255, 255)

</Cabbage>
<CsoundSynthesizer>
<CsOptions>
-n -d
</CsOptions>
<CsInstruments>
; Initialize the global variables. 
ksmps = 32
nchnls = 2
0dbfs = 1


instr 1

kButton1, kButton1Trigger cabbageGetValue "button1"
kButton2, kButton2Trigger cabbageGetValue "button2"

; Updating button1 updates button2
if kButton1Trigger==1 then
        cabbageSet 1, "button2", "value", kButton1
endif

; Updating button2 updates button1
if kButton2Trigger==1 then
        cabbageSet 1, "button1", "value", kButton2
endif

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>
1 Like

Thank you! I implemented the following, which works except at initialization. The buttons that I initialize to have a value of 1 are visually switched off. Functionally, they are still set as I wanted.

; update switches to be consistent
if kTrigTr1_Output == 1 then
	if kTr1_Output == 0 then
		cabbageSet 1, "mute1", "value", 1
	elseif kTr1_Output == 1 then
		cabbageSet 1, "mute1", "value", 0
	endif
endif
if kTrigButton_mute1 == 1 then
	if kButton_mute1 == 0 then
		cabbageSet 1, "button10", "value", 1
	elseif kButton_mute1 == 1 then
		cabbageSet 1, "button10", "value", 0
	endif
endif

The default button settings are button10 is on and mute1 is off. I’m ending up with both buttons appearing off initially. I’m not sure how to resolve that.

Also, if there is a more compact way to write this, I’d love to know. I’ll have several of these logic blocks, so it will become a bit sprawling.

Can you post a simple full example so I can take a look?

I usually put the core logic into a single instrument, and then start N instances of it with a unique channel Id. For example:

; update switches to be consistent
SMuteChannel sprint "mute%d", p4
if kTrigTr1_Output == 1 then
	if kTr1_Output == 0 then
		cabbageSet 1, SMuteChannel, "value", 1
	elseif kTr1_Output == 1 then
		cabbageSet 1, SMuteChannel, "value", 0
	endif
endif
SButtonChannel sprint "mute%d", p4
if kTrigButton_mute1 == 1 then
	if kButton_mute1 == 0 then
		cabbageSet 1, SButtonChannel, "value", 1
	elseif kButton_mute1 == 1 then
		cabbageSet 1, SButtonChannel, "value", 0
	endif
endif

Also, would something like this would reduce that line count:

cabbageSet kTrigButton_mute1, SButtonChannel, "value", kButton_mute1==0 ? 1 : 1

Keep in mind that setting values should probably be done with a cabbageSetValue:

cabbageSetValue SButtonChannel, kButton_mute1==0 ? 1 : 1, kTrigButton_mute1

This is where I ended up. It works well and isn’t too crazy to maintain/debug. I’m sure there are some more efficient ways to do parts of this.

<Cabbage>
form caption("Untitled") size(800, 400), guiMode("queue") pluginId("def1")
rslider bounds(700, 0, 100, 100), channel("gain"), range(0, 1, 0, 1, .01), text("Gain"), trackerColour("lime"), outlineColour(0, 0, 0, 50), textColour("black")


#define BUTTON_GREEN    colour:0(0, 0, 0, 255) colour:1(0, 255, 0, 255) corners(10) outlineColour(0, 0, 0, 50) outlineThickness(1) text()  value(1)
#define BUTTON_YELLOW    colour:0(0, 0, 0, 255) colour:1(255, 255, 0, 255) corners(10) outlineColour(0, 0, 0, 50) outlineThickness(1) text()  value(0)


#define BUTTON_REC    colour:0(0, 0, 0, 255) colour:1(200, 0, 0, 255) text("Rec", "Rec") corners(10) outlineColour(0, 0, 0, 50) outlineThickness(1)  value(1)
#define BUTTON_MUTE    colour:0(0, 0, 0, 255) colour:1(200, 0, 200, 255) text("Mute", "Mute") corners(10) outlineColour(0, 0, 0, 50) outlineThickness(1) value(0)
#define BUTTON_FEEDBACK    colour:0(0, 0, 0, 255) colour:1(0, 200, 200, 255) text("Fdbk", "Fdbk") corners(10) outlineColour(0, 0, 0, 50) outlineThickness(1) value(1)



/* The Matrix */
groupbox bounds(400, 0, 100, 100) colour(255, 255, 255, 0)  outlineThickness(0) channel("matrix") lineThickness(0)
{
    /* Row 1*/
    button bounds(0, 0, 15, 15) channel("button1") $BUTTON_GREEN 
    button bounds(20, 0, 15, 15) channel("button2") $BUTTON_GREEN 
    button bounds(40, 0, 15, 15) channel("button3") $BUTTON_GREEN 
    button bounds(60, 0, 15, 15) channel("button4") $BUTTON_GREEN 
    button bounds(80, 0, 15, 15) channel("button5") $BUTTON_GREEN 
    /* Row 2*/
    button bounds(0, 20, 15, 15) channel("button6") $BUTTON_GREEN 
    button bounds(20, 20, 15, 15) channel("button7") $BUTTON_YELLOW 
    button bounds(40, 20, 15, 15) channel("button8") $BUTTON_YELLOW 
    button bounds(60, 20, 15, 15) channel("button9") $BUTTON_YELLOW 
    button bounds(80, 20, 15, 15) channel("button10") $BUTTON_GREEN 
    /* Row 3*/
    button bounds(0, 40, 15, 15) channel("button11") $BUTTON_YELLOW 
    button bounds(20, 40, 15, 15) channel("button12") $BUTTON_GREEN 
    button bounds(40, 40, 15, 15) channel("button13") $BUTTON_YELLOW 
    button bounds(60, 40, 15, 15) channel("button14") $BUTTON_YELLOW
    button bounds(80, 40, 15, 15) channel("button15") $BUTTON_GREEN 
    /* Row 4*/
    button bounds(0, 60, 15, 15) channel("button16") $BUTTON_YELLOW 
    button bounds(20, 60, 15, 15) channel("button17") $BUTTON_YELLOW 
    button bounds(40, 60, 15, 15) channel("button18") $BUTTON_GREEN 
    button bounds(60, 60, 15, 15) channel("button19") $BUTTON_YELLOW 
    button bounds(80, 60, 15, 15) channel("button20") $BUTTON_GREEN 
    /* Row 5*/
    button bounds(0, 80, 15, 15) channel("button21") $BUTTON_YELLOW 
    button bounds(20, 80, 15, 15) channel("button22") $BUTTON_YELLOW 
    button bounds(40, 80, 15, 15) channel("button23") $BUTTON_YELLOW 
    button bounds(60, 80, 15, 15) channel("button24") $BUTTON_GREEN 
    button bounds(80, 80, 15, 15) channel("button25") $BUTTON_GREEN 
}

/* Track rec */
groupbox bounds(20, 0, 240, 40) colour(255, 255, 255, 0)  outlineThickness(0) channel("rec") lineThickness(0)
{
	button bounds(80, 0, 40, 40) channel("button_recTr1") $BUTTON_REC 
	button bounds(120, 0, 40, 40) channel("button_recTr2") $BUTTON_REC 
	button bounds(160, 0, 40, 40) channel("button_recTr3") $BUTTON_REC 
	button bounds(200, 0, 40, 40) channel("button_recTr4") $BUTTON_REC 
}

/* Track feedback */
groupbox bounds(20, 40, 240, 40) colour(255, 255, 255, 0)  outlineThickness(0) channel("feedback") lineThickness(0)
{
	button bounds(80, 0, 40, 40) channel("button_feedbackTr1") $BUTTON_FEEDBACK 
	button bounds(120, 0, 40, 40) channel("button_feedbackTr2") $BUTTON_FEEDBACK 
	button bounds(160, 0, 40, 40) channel("button_feedbackTr3") $BUTTON_FEEDBACK 
	button bounds(200, 0, 40, 40) channel("button_feedbackTr4") $BUTTON_FEEDBACK 
}

/* Output mutes */
groupbox bounds(20, 100, 240, 40) colour(255, 255, 255, 0)  outlineThickness(0) channel("mute") lineThickness(0)
{
	button bounds(0, 0, 40, 40) channel("button_mutedry") $BUTTON_MUTE 
	button bounds(80, 0, 40, 40) channel("button_muteTr1") $BUTTON_MUTE
	button bounds(120, 0, 40, 40) channel("button_muteTr2") $BUTTON_MUTE 
	button bounds(160, 0, 40, 40) channel("button_muteTr3") $BUTTON_MUTE 
	button bounds(200, 0, 40, 40) channel("button_muteTr4") $BUTTON_MUTE 		
}

</Cabbage>
<CsoundSynthesizer>
<CsOptions>
-n -d
</CsOptions>
<CsInstruments>
; Initialize the global variables. 
ksmps = 32
nchnls = 2
0dbfs = 1


instr 1
kGain cabbageGetValue "gain"

/* Input routing*/
kInput_Tr1, kTrigInput_Tr1  cabbageGetValue "button1" ; rec to tr1
kInput_Tr2, kTrigInput_Tr2  cabbageGetValue "button2" ; rec to tr2
kInput_Tr3, kTrigInput_Tr3  cabbageGetValue "button3" ; rec to tr3
kInput_Tr4, kTrigInput_Tr4  cabbageGetValue "button4" ; rec to tr4
kInput_Output, kTrigInput_Output cabbageGetValue "button5" ; output for dry

/* Track 1 routing */
kTr1_Tr1, kTrigTr1_Tr1  cabbageGetValue "button6"	; feedback for tr1
kTr1_Tr2  cabbageGetValue "button7"
kTr1_Tr3  cabbageGetValue "button8"
kTr1_Tr4  cabbageGetValue "button9"
kTr1_Output, kTrigTr1_Output cabbageGetValue "button10" ; output for tr1

/* Track 2 routing */
kTr2_Tr1  cabbageGetValue "button11"
kTr2_Tr2, kTrigTr2_Tr2  cabbageGetValue "button12" ; feddback for tr2
kTr2_Tr3  cabbageGetValue "button13"
kTr2_Tr4  cabbageGetValue "button14"
kTr2_Output, kTrigTr2_Output cabbageGetValue "button15" ; output for tr2

/* Track 3 routing */
kTr3_Tr1  cabbageGetValue "button16"
kTr3_Tr2  cabbageGetValue "button17"
kTr3_Tr3, kTrigTr3_Tr3  cabbageGetValue "button18" ; feedback for tr3
kTr3_Tr4  cabbageGetValue "button19"
kTr3_Output, kTrigTr3_Output cabbageGetValue "button20" ; output for tr3

/* Track 4 routing */
kTr4_Tr1  cabbageGetValue "button21"
kTr4_Tr2  cabbageGetValue "button22"
kTr4_Tr3  cabbageGetValue "button23"
kTr4_Tr4, kTrigTr4_Tr4  cabbageGetValue "button24" ; feedback for tr4
kTr4_Output, kTrigTr4_Output cabbageGetValue "button25" ; output for tr4


/* Track rec (Input to Tr1, Input to Tr2, ...*/
kButton_rec1, kTrigButton_rec1 cabbageGetValue "button_recTr1"
kButton_rec2, kTrigButton_rec2 cabbageGetValue "button_recTr2"
kButton_rec3, kTrigButton_rec3 cabbageGetValue "button_recTr3"
kButton_rec4, kTrigButton_rec4 cabbageGetValue "button_recTr4"

/* Feedback paths (Tr1 to Tr1, Tr2 to Tr2, Tr3 to Tr3, Tr4 to Tr4) */
kButton_feedback1, kTrigButton_feedback1 cabbageGetValue "button_feedbackTr1"
kButton_feedback2, kTrigButton_feedback2 cabbageGetValue "button_feedbackTr2"
kButton_feedback3, kTrigButton_feedback3 cabbageGetValue "button_feedbackTr3"
kButton_feedback4, kTrigButton_feedback4 cabbageGetValue "button_feedbackTr4"

/* Output mutes (Input, Tr1, Tr2, Tr3, Tr4) */
kButton_mutedry, kTrigButton_mutedry cabbageGet "button_mutedry"
kButton_mute1, kTrigButton_mute1 cabbageGetValue "button_muteTr1"
kButton_mute2, kTrigButton_mute2 cabbageGetValue "button_muteTr2"
kButton_mute3, kTrigButton_mute3 cabbageGetValue "button_muteTr3"
kButton_mute4, kTrigButton_mute4 cabbageGetValue "button_muteTr4"



; update switches to be consistent - track record
; first direction
cabbageSetValue "button1", kButton_rec1==0 ? 0 : 1, kTrigButton_rec1
cabbageSetValue "button2", kButton_rec2==0 ? 0 : 1, kTrigButton_rec2
cabbageSetValue "button3", kButton_rec3==0 ? 0 : 1, kTrigButton_rec3
cabbageSetValue "button4", kButton_rec4==0 ? 0 : 1, kTrigButton_rec4

; second direction
cabbageSetValue "button_recTr1", kInput_Tr1==0 ? 0 : 1, kTrigInput_Tr1
cabbageSetValue "button_recTr2", kInput_Tr2==0 ? 0 : 1, kTrigInput_Tr2
cabbageSetValue "button_recTr3", kInput_Tr3==0 ? 0 : 1, kTrigInput_Tr3
cabbageSetValue "button_recTr4", kInput_Tr4==0 ? 0 : 1, kTrigInput_Tr4


; update switches to be consistent - track feedback
; first direction
cabbageSetValue "button6", 	kButton_feedback1==0 ? 0 : 1, kTrigButton_feedback1
cabbageSetValue "button12", kButton_feedback2==0 ? 0 : 1, kTrigButton_feedback2
cabbageSetValue "button18", kButton_feedback3==0 ? 0 : 1, kTrigButton_feedback3
cabbageSetValue "button24", kButton_feedback4==0 ? 0 : 1, kTrigButton_feedback4

; second direction
cabbageSetValue "button_feedbackTr1", kTr1_Tr1==0 ? 0 : 1, kTrigTr1_Tr1
cabbageSetValue "button_feedbackTr2", kTr2_Tr2==0 ? 0 : 1, kTrigTr2_Tr2
cabbageSetValue "button_feedbackTr3", kTr3_Tr3==0 ? 0 : 1, kTrigTr3_Tr3
cabbageSetValue "button_feedbackTr4", kTr4_Tr4==0 ? 0 : 1, kTrigTr4_Tr4


; update switches to be consistent - output mutes
; first direction
cabbageSetValue "button5", kButton_mutedry==0 ? 1 : 0, kTrigButton_mutedry
cabbageSetValue "button10", kButton_mute1==0 ? 1 : 0, kTrigButton_mute1
cabbageSetValue "button15", kButton_mute2==0 ? 1 : 0, kTrigButton_mute2
cabbageSetValue "button20", kButton_mute3==0 ? 1 : 0, kTrigButton_mute3
cabbageSetValue "button25", kButton_mute4==0 ? 1 : 0, kTrigButton_mute4

; second direction
cabbageSetValue "button_mutedry", kInput_Output==0 ? 1 : 0, kTrigInput_Output
cabbageSetValue "button_muteTr1", kTr1_Output==0 ? 1 : 0, kTrigTr1_Output
cabbageSetValue "button_muteTr2", kTr2_Output==0 ? 1 : 0, kTrigTr2_Output
cabbageSetValue "button_muteTr3", kTr3_Output==0 ? 1 : 0, kTrigTr3_Output
cabbageSetValue "button_muteTr4", kTr4_Output==0 ? 1 : 0, kTrigTr4_Output


a1 inch 1
a2 inch 2

outs a1*kGain, a2*kGain
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>

It doesn’t look too bad. I still see an opportunity for modularity if you dynamically set the channel names at init time. And start 4 instances of a simpler instrument. For example all of this:

/* Track rec (Input to Tr1, Input to Tr2, ...*/
kButton_rec1, kTrigButton_rec1 cabbageGetValue "button_recTr1"
kButton_rec2, kTrigButton_rec2 cabbageGetValue "button_recTr2"
kButton_rec3, kTrigButton_rec3 cabbageGetValue "button_recTr3"
kButton_rec4, kTrigButton_rec4 cabbageGetValue "button_recTr4"

/* Feedback paths (Tr1 to Tr1, Tr2 to Tr2, Tr3 to Tr3, Tr4 to Tr4) */
kButton_feedback1, kTrigButton_feedback1 cabbageGetValue "button_feedbackTr1"
kButton_feedback2, kTrigButton_feedback2 cabbageGetValue "button_feedbackTr2"
kButton_feedback3, kTrigButton_feedback3 cabbageGetValue "button_feedbackTr3"
kButton_feedback4, kTrigButton_feedback4 cabbageGetValue "button_feedbackTr4"

/* Output mutes (Input, Tr1, Tr2, Tr3, Tr4) */
kButton_mutedry, kTrigButton_mutedry cabbageGet "button_mutedry"
kButton_mute1, kTrigButton_mute1 cabbageGetValue "button_muteTr1"
kButton_mute2, kTrigButton_mute2 cabbageGetValue "button_muteTr2"
kButton_mute3, kTrigButton_mute3 cabbageGetValue "button_muteTr3"
kButton_mute4, kTrigButton_mute4 cabbageGetValue "button_muteTr4"

Could easily become:

/* Track rec (Input to Tr1, Input to Tr2, ...*/
SRecChannel sprintf "button_recTr", p4
kButton_rec1, kTrigButton_rec1 cabbageGetValue SRecChannel

/* Feedback paths (Tr1 to Tr1, Tr2 to Tr2, Tr3 to Tr3, Tr4 to Tr4) */
SFeedbackChannel sprintf "button_feedbackTr", p4
kButton_feedback1, kTrigButton_feedback1 cabbageGetValue SFeedbackChannel

/* Output mutes (Input, Tr1, Tr2, Tr3, Tr4) */
SMuteChannel sprintf "button_muteTr%d", p4
kButton_mute1, kTrigButton_mute1 cabbageGetValue SMuteChannel

Whereby you would start each instance in the score with a unique p4 that matches the channel number:

i1 0 [60*60*24*7] 1
i1 0 [60*60*24*7] 2
i1 0 [60*60*24*7] 3
i1 0 [60*60*24*7] 4

What I like most about this particular approach is you can work on a single instance to make sure everything functions as expected, and the instantiate as many of these are you like. You can also add new features without have to duplicate so much code. My last super looper instrument was structured this way and it was a real time saver in the end.

I do some things where the delay lines impact each other. Bouncing audio from one delay to another, modulating the amplitude of one delay with the envelope from another, etc. Does that change if I should think about one instrument vs four?

In this case I would set up channels to communicate between instances. You can follow the same naming convention. It will probably take some time to get this set up but in the end you have a very robust module that can be instantiate as many times as you like.

Do you know of any example code that does that? In my mind, it seems feasible … but it changes how I think about the development. I’ve been going Cabbage widget -> Csound instrument, but this turns that around.

And thank you! I’m learning a ton.

No, that still remains the same. But now you’re also using channels to route signals internally. I’m not sure exactly how you are doing things so it’s hard to give a proper overview. But to route audio out of an instance to a named channel you just use chnset:

instr 1
SChannel sprintf "chan%d", p4
a1 delay ...
//send audio to named channel
chnset a1, SChannel
...
//get audio from other channels
aInDel1 chnget "chan2"

It could be challenging to set up, but should be possible.

Sorry, I had two things on my mind. One is the routing across instruments, the other is the GUI design.

For the latter, I’ve been very GUI -> Csound in my thinking. Being able to have different numbers of instruments will mean shifting the creation of the widgets to the Csound code. I’m trying to wrap my head around that.

Should I really be thinking about Csound “instruments” as function-specific audio-processing modules? A simple version with two delays might be:

Instrument 1 - audio in
Instrument 2 - bus for routing to delay 1
Instrument 3 - bus for routing to delay 2
Instrument 4 - delay line 1 via UDO
Instrument 5 - delay line 2 via UDO
Instrument 6 - audio bus for mixing instruments 1, 4, 5 and outputting

Possibly, but it would be more like this no:

Instrument 1 - audio in
Instrument 2 - bus for routing to delays
Instrument 3 - delay lines via UDO
Instrument 4 - audio bus for mixing instruments and outputting

then in your score:

i1 0 z    //audio input
i2 0 z 1 //bus for delay 1
i2 0 z 2 //bus for delay 2
i2 0 z 3 //bus for delay 3
i2 0 z 4 //bus for delay 4
i3 0 z 1 //delay line 1
i3 0 z 2 //delay line 2
i3 0 z 3 //delay line 3
i3 0 z 4 //delay line 4
i4 0 z    //audio bus / mixing / output

The idea is to write just one delay line instrument, one routing instrument, etc. It’s quite an object orientated approach, where each instrument becomes a class, and each instance an object. The routing between instrument might be tricky as I said earlier, but I’m sure their will be a way to do it.

That makes a lot of sense to me. Is the idea then that all of the instrument instances are created when the plug-in is launched? Put differently, can Cabbage add/remove instrument instances while the plug-in is running?

The create/destroy thing could be a real challenge for the GUI. I’m not sure it would be a desired feature, but I’d need to plan for it.

Yeah. Once they are launched they will access the relevant UI channels using a unique Id. Csound can add/remove instruments at run time, Cabbage can show/hide UIs at run-time, but you can’t dynamically create plugin parameters at run time. This is true of all plugin formats as it would mess up the host. There are some hacks that can be done to give the illusion that parameters are being added dynamically, but this are beyond the scope of Cabbage.

It would and isn’t supported (although it can be done). The better solution to define all the parameters you will need access to in your host, and then show/hide them as you wish.

Another option will be to do a lite mode of the plug-in with a few delay lines and an expert mode with many. If I do the GUI and objects right, the only real difference will be the orchestra and the resulting system demands.

Can’t wait to dive into this!

I made a lot of progress in making it more of an object-oriented approach.

The final mixing and output instruments will only be called once and need to have some dynamic code to handle how many loops are running. That seems doable … but I’m fighting with the string functions and getting Csound to evaluate them.

Here is an attempt at generating some code for four loops (I’ll eventually make the “4” a global variable):

; Initialize the string for the assignment statement
SassignL init "amix_outL = adry_in1"
SassignR init "amix_outR = adry_in1"

; Append to the string in the loop
iIndex = 1
while iIndex <= 4 do
    ; Add looped inputs to the strings
    Sname sprintf " + aloop_in%d", iIndex
    SassignL strcat SassignL, Sname
    SassignR strcat SassignR, Sname
    iIndex += 1  ; Increment the loop counter
od

; Print the assignment statements for debugging
prints "Generated string for SassignL: %s\n", SassignL
prints "Generated string for SassignR: %s\n", SassignR

; Perform the assignment using eval
; evalstr SassignL
; evalstr SassignR

The strings it generates look OK:

Generated string for SassignL: amix_outL = adry_in1 + aloop_in1 + aloop_in2 + aloop_in3 + aloop_in4
Generated string for SassignR: amix_outR = adry_in1 + aloop_in1 + aloop_in2 + aloop_in3 + aloop_in4

But how do I get Csound to execute that generated code? evalstr throws errors.

I never used evalstr but I think you only need to assign it to a value:

iL evalstr SassignL
iR evalstr SassignR

D’oh!

I was so focused on building the entire line I didn’t think that that was too much.

I’m not sure about this approach :thinking: I think it would be better to use channels and mix the signals that way.