Cabbage Logo
Back to Cabbage Site

Automate/Animate the XY Pad GUI?

Hey all. I’m building a formant synthesiser and i’m looking to automate/animate the XY Pad.

I’m using an XYPad to interpolate smoothly between different formant sounds ‘oo’ ‘ee’ ‘aa’ etc… this works great! I also implemented a button called ‘AutoVowel’ that when clicked, should override the manual setting of XYPad and have the little circle parameter of XYPad move in a circle within the pad bounds.

The effect itself is actually working and toggling fine, I can hear all the interpolations between phonemes occurring smoothly when AutoVowel is activated, but I can’t see the parameter moving, I haven’t been able to trigger the GUI update.

One issue I foresee, upon reading https://cabbageaudio.com/docs/identchannels/, is that i’m not using Ident Channels. This is only because I can’t use them at the same time as guiMode(“queue”), which i’m using. Is that a deal breaker or is there a workaround?

Here is the instrument for the XYPad:

instr XYPad

kx chnget "xCoordinate"
ky chnget "yCoordinate"

kTune chnget "tune"
kFineTune chnget "fineTune"
kNormTune = (kTune + 24) / 48

; Define formant values for MidRange vowels
iF1_i_Mid = 270
iF2_i_Mid = 2300
iF3_i_Mid = 3000

iF1_u_Mid = 300
iF2_u_Mid = 870
iF3_u_Mid = 2240

iF1_e_Mid = 390
iF2_e_Mid = 2000
iF3_e_Mid = 2800

iF1_o_Mid = 460
iF2_o_Mid = 920
iF3_o_Mid = 2420

iF1_a_Mid = 730
iF2_a_Mid = 1090
iF3_a_Mid = 2440

; Calculate the mean frequency for all vowels to derive the central, or "Mid", formant frequencies
iF1_Mid = (iF1_i_Mid + iF1_u_Mid + iF1_e_Mid + iF1_o_Mid + iF1_a_Mid) / 5
iF2_Mid = (iF2_i_Mid + iF2_u_Mid + iF2_e_Mid + iF2_o_Mid + iF2_a_Mid) / 5
iF3_Mid = (iF3_i_Mid + iF3_u_Mid + iF3_e_Mid + iF3_o_Mid + iF3_a_Mid) / 5

; Adjust formants based on normalized tuning. This interpolates between the Mid value and the specific vowel formant
kF1_i = iF1_i_Mid + (iF1_Mid - iF1_i_Mid) * kNormTune
kF2_i = iF2_i_Mid + (iF2_Mid - iF2_i_Mid) * kNormTune
kF3_i = iF3_i_Mid + (iF3_Mid - iF3_i_Mid) * kNormTune

kF1_u = iF1_u_Mid + (iF1_Mid - iF1_u_Mid) * kNormTune
kF2_u = iF2_u_Mid + (iF2_Mid - iF2_u_Mid) * kNormTune
kF3_u = iF3_u_Mid + (iF3_Mid - iF3_u_Mid) * kNormTune

kF1_e = iF1_e_Mid + (iF1_Mid - iF1_e_Mid) * kNormTune
kF2_e = iF2_e_Mid + (iF2_Mid - iF2_e_Mid) * kNormTune
kF3_e = iF3_e_Mid + (iF3_Mid - iF3_e_Mid) * kNormTune

kF1_o = iF1_o_Mid + (iF1_Mid - iF1_o_Mid) * kNormTune
kF2_o = iF2_o_Mid + (iF2_Mid - iF2_o_Mid) * kNormTune
kF3_o = iF3_o_Mid + (iF3_Mid - iF3_o_Mid) * kNormTune

; 2D Interpolation: Interpolate formants based on ky (Y coordinate) between 'i' and 'u' and between 'e' and 'o'
kF1_i_u = kF1_i + (kF1_u - kF1_i) * ky
kF2_i_u = kF2_i + (kF2_u - kF2_i) * ky
kF3_i_u = kF3_i + (kF3_u - kF3_i) * ky

kF1_e_o = kF1_e + (kF1_o - kF1_e) * ky
kF2_e_o = kF2_e + (kF2_o - kF2_e) * ky
kF3_e_o = kF3_e + (kF3_o - kF3_e) * ky

; Interpolate between the two previously interpolated sets based on kx (X coordinate)
kF1 = kF1_i_u + (kF1_e_o - kF1_i_u) * kx + (iF1_a_Mid - kF1_i_u) * (1-abs(2*kx-1)) * (1-abs(2*ky-1))
kF2 = kF2_i_u + (kF2_e_o - kF2_i_u) * kx + (iF2_a_Mid - kF2_i_u) * (1-abs(2*kx-1)) * (1-abs(2*ky-1))
kF3 = kF3_i_u + (kF3_e_o - kF3_i_u) * kx + (iF3_a_Mid - kF3_i_u) * (1-abs(2*kx-1)) * (1-abs(2*ky-1))

kF1 = kF1 * pow(2, kTune/12 + kFineTune/100)
kF2 = kF2 * pow(2, kTune/12 + kFineTune/100)
kF3 = kF3 * pow(2, kTune/12 + kFineTune/100)

cabbageSetValue "kFreq1", kF1
cabbageSetValue "kFreq2", kF2
cabbageSetValue "kFreq3", kF3

; Get the status of the automation toggle
kActivate chnget "autoVowel"

if kActivate == 1 then
    ; Circular motion for X and Y coordinates 
    kTheta phasor 0.1
    kx = 0.5 + 0.5*cos(kTheta*2*$M_PI)
    ky = 0.5 + 0.5*sin(kTheta*2*$M_PI)

    ; Update X and Y coordinate channels with newly computed values
    chnset kx, "xCoordinate"
    chnset ky, "yCoordinate"
else
    ; If not in automation mode, simply retrieve the manually set X and Y coordinates
    kx chnget "xCoordinate"
    ky chnget "yCoordinate"
endif

endin

Here are the relevant Cabbage elements:

button bounds(325, 140, 80, 25), channel(“autoVowel”), text(“AutoVowel”)

plant(“XYPad”) {
groupbox bounds(315, 170, 220, 190), text(“XYPad”), colour(200, 200, 200)
xypad bounds(325, 190, 200, 160), channel(“xCoordinate”, “yCoordinate”)
}

Here is the CsScore:
The only relevant one is i"XYPad" 0 z, the rest are for other functionalities

f0 z
f 10 0 4096 10 1
f 1 0 4096 10 1
i"UpdateGUI" 0 z
i"Chorus" 0 z 0.8
i"MIDI_Listener" 0 z
i"UpdateDisplay" 0 z
i"XYPad" 0 z

Thank you for your time!

Yes, this will work in ‘polling’ mode with chnset, but not in ‘queue’ mode. Perhaps there isn’t a big reason to use ‘queue’ mode anyway. An alternative is to simply build your own XY pad using image widgets and mouse tracking.

N.B.: each time you ask Rory about xypad, his hair turns a little greyer.

Iain is right, in this instance, using the old style polling mode should work fine and won’t result in too much CPU overhead in comparison to the queue mode. For what it’s worth, you can automate in queue mode like this:

<Cabbage>
form caption("Stretch") size(520, 500), guiMode("queue"), pluginId("1287"), colour (0,100,0)
xypad bounds(8, 8, 500, 420) colour("black"), channel("xyPadChan", "yChan") rangeX(0, 1, 0) rangeY(0, 1, 0)
button bounds(8, 432, 80, 32) channel("automate"), text("Automate")
</Cabbage>
<CsoundSynthesizer>
<CsOptions>
-n -d -+rtmidi=NULL -M0 --midi-key-cps=4 --midi-velocity-amp=5
</CsOptions>
<CsInstruments>
; Initialize the global variables. 
ksmps = 32
nchnls = 2
0dbfs = 1


instr 1
    kAuto, kAutoTrig cabbageGetValue "automate"

    if kAuto == 1 then
        k1 oscili 1, .1
        k2 oscili 1, -.1, -1, .5
        cabbageSet metro(20), "xyPadChan", "valueX", abs(k1)
        cabbageSet metro(20), "xyPadChan", "valueY", abs(k2)
    endif
endin

</CsInstruments>
<CsScore>
f0 z
i1 0 z
</CsScore>
</CsoundSynthesizer>

The xypad example also shows how you can enable automation to the xypad :slight_smile:

Hey both, sorry for the late response! Thank you for your responses, they solved the issue. I stopped using queue mode and used chnset for the x and y coords just as Iain suggested. Here’s what worked (Example highlights the relevant code):

instr XYPad

kx chnget "xCoordinate"
ky chnget "yCoordinate"

kActivate chnget "autoVowel"
kAutoSpeed chnget "autoSpeed"
kAutoRadius chnget "autoRadius" 
kAutoRandom chnget "autoRandom"

if kActivate == 1 then

    ; Circle motion 
    kTheta phasor kAutoSpeed 
    kx = 0.5 + kAutoRadius*cos(kTheta*2*$M_PI)
    ky = 0.5 + kAutoRadius*sin(kTheta*2*$M_PI)

    ; Randomness
    kRandomX randi kAutoRandom * (kx - 0.5), kAutoSpeed 
    kRandomY randi kAutoRandom * (ky - 0.5), kAutoSpeed 
    kx = kx + kRandomX
    ky = ky + kRandomY

    ; Ensure kx and ky values remain within [0, 1]
    kx limit kx, 0, 1
    ky limit ky, 0, 1

    ; Autovowel XY coordinates
    chnset kx, "xCoordinate"
    chnset ky, "yCoordinate"
else
    ; If not autovowel, manually set X and Y coordinates
    kx chnget "xCoordinate"
    ky chnget "yCoordinate"
endif

endin

And here is the plant I used:

plant(“XYPad”) {
groupbox bounds(315, 170, 220, 190), text(“XYPad”), colour(200, 200, 200)
xypad bounds(325, 190, 200, 160), channel(“xCoordinate”, “yCoordinate”)
}

Thanks again!!

1 Like