## Introduction

This article will take two well known examples of visual curiosities and consider their application as event generating modules in Csound. The visual kinetic systems considered are noteworthy on account of the unexpected patterns that arise during their activity. The aim is to construct musical analogues of these systems that can similarly evoke unexpected patterns. The examples provided will make use of the Csound front-end "Cabbage" [1] to confirm the mathematical rendering of the visual analogues and also to better facilitate user interaction and exploration, but these Cabbage visualisations and interfaces could easily be detached, making the examples front-end independent. All of the examples can be downloaded as a zip archive here.

## I. The Stroboscopic Effect

### Description

Analogue moving film works by taking a sequence of still images, which when played back at the same rate as that when taken, fool the eye and the brain into thinking that it is observing a moving image. In the early days of Hollywood the standard of 24 frames per second was established, not only because this rate is just about adequate for sustaining the illusion, but also as going much higher would have placed excessive technical strains on the equipment as well as increasing the amount, and therefore cost, of film stock required.

As Hollywood began its fascination with recreating the old West of the 1800s, a problem quickly became apparent when trying to film revolving wagon wheels in that they would appear to be moving too slowly, not at all, or even backwards. This popularly became known as "the wagon wheel effect"[2], but more generically should be referred to as the "stroboscopic effect". To understand the reason for this, first imagine a wagon wheel with a single spoke rotating about its centre once every second (Figure 1). If we tried to film this movement at a single frame per second each image would be identical therefore no movement would be captured. If we increased the rate very slightly, then the object would not quite have completed its rotation by the time of the second image, and so on continuing into the subsequent frames. In this case the object would appear to be rotating very slowly in the opposite direction to that of its actual travel. Decreasing the frame rate would result is very slow movement in the actual direction of travel. Of course film does not run at 1 frame per second but neither does a wagon wheel have to rotate a full 360 degrees in order to exhibit similarity. A wheel with just 2 spokes (Figure 1) repeats its appearance twice within every full rotation, or every 180 degrees; a wheel with 4 spokes repeats 4 times or every 90 degrees; 8 spokes repeats 8 times or every 45 degrees and so on. Increasing the number of instances of visual similarity in a rotation allows us to increase the frame rate while achieving the same anomaly. To derive the frame rate required to freeze motion we can apply the following equation:

where F is frames-per-second, R is the rate of rotation and N is the number of equally spaced spokes. From this we can deduce that a wheel with 16 spokes filmed at 24 fps will only need to rotate at 1.5 Hz in order to appear motionless (or multiples thereof: 3 Hz, 4.5 Hz etc.). An 8 spoked wheel will require a rotation of 3 Hz (or 6 Hz, 9 Hz etc.).

Figure 1. Wheels with varying numbers of equally spaced spokes.

Example01.csd employs the Csound front-end Cabbage to permit the user to explore the visual consequences of various rotational speeds and frame rates upon a spinning 8 spoked wheel. It should be noted that Cabbage is not really intended for the creation of animations of this sort so we are pushing it to its limit in this respect. For the best results give `guirefresh()` in the Cabbage code block a low value and also keep `ksmps` low. "Frames" are created using a `metro`, the output of which dictates when the Cabbage widgets for the spokes will be redrawn. The rotation of the wheel derives from a single `phasor`, the phases (and therefore rotations) of the various spokes being derived from this single `phasor`. Spokes are created using diameter chords and are therefore created in pairs. This example produces no sound.

```; Example01.csd

<Cabbage>
form size(300,200), guirefresh(8), text("Wagon Wheel")
image bounds(100, 40,100,100), shape("ellipse"), colour(0,0,0,0), outlinethickness(3)
image bounds(100,90,100,4), identchannel("spoke1"), shape("rounded")
image bounds(100,90,100,4), identchannel("spoke2"), shape("rounded")
image bounds(100,90,100,4), identchannel("spoke3"), shape("rounded")
image bounds(100,90,100,4), identchannel("spoke4"), shape("rounded")
numberbox bounds( 0,170,150,30), text("Freq. of Rotation"), range(0,5,1,1,0.01), channel("freq")
numberbox bounds(150,170,150,30), text("Frames Per Second"), range(1,64,8.5,1,0.01), channel("FPS")
</Cabbage>

<CsoundSynthesizer>

<CsOptions>
-n
</CsOptions>

<CsInstruments>

sr      =       44100
ksmps   =       16
nchnls  =       2
0dbfs   =       1

instr   1
kfreq  chnget  "freq"        ; read in Cabbage widgets
kFPS   chnget  "FPS"
gkRefresh      metro   kFPS  ; refresh rate
kphs1  phasor  kfreq         ; phase vector defining the rotation of the wheel
if gkRefresh==1 then         ; only update with a gkRefresh trigger, i.e. a frame
kphs2 wrap    kphs1+0.125,0,1  ; second radius
kphs3 wrap    kphs1+0.25,0,1   ; third radius
kphs4 wrap    kphs1+0.375,0,1  ; fourth radius

Smsg  sprintfk        "rotate(%f,50,2)",2 * \$M_PI * kphs1 ; create string
chnset          Smsg, "spoke1"                      ; send string to widget
Smsg  sprintfk        "rotate(%f,50,2)",2 * \$M_PI * kphs2
chnset          Smsg, "spoke2"
Smsg  sprintfk        "rotate(%f,50,2)",2 * \$M_PI * kphs3
chnset          Smsg, "spoke3"
Smsg  sprintfk        "rotate(%f,50,2)",2 * \$M_PI * kphs4
chnset          Smsg, "spoke4"
endin

</CsInstruments>

<CsScore>
i 1 0 3600
</CsScore>

</CsoundSynthesizer>
```

For a given rotational frequency, the maximum rate of frames per second that will freeze motion will be eight times the rotation frequency. For a rotational frequency of 1 Hz, 8 frames per second will freeze motion as will halves of that value thereof (4, 2, 1 etc.). Transferring these visual results to (or mirroring them in) the creation of sound within Csound should prove relatively straightforward.

If we jettison the visual implementation and consider a purely sonic approach, we can periodically sample the phase vector and hold this value until the next sample is taken; this technique is commonly referred to as "sample-and-hold". This is something that is easy to construct from basic elements in Csound, but for which there exists a ready-made opcode called `samphold`. The following example takes the phase vector of the wagon wheel and interprets it as the pitch of an oscillator. This vector undergoes a reiterating sample-and-hold procedure and the result is used in defining the frequency of the oscillator. Just two controls are used: "Phase Rate", the frequency of the phase vector which is equivalent to the "Freq. of Rotation" from the previous example, and "S&H Rate" which is equivalent to "Frames per Second". Values can be changed by either spinning the encoders or by typing directly into the number boxes. Notions of freezing or reversing motion are transmuted into holding the pitch of the oscillator steady or forcing it to exhibit a descending scale but we will probably become more interested in other, less linear patterns that can be found.

```; Example02.csd

<Cabbage>
form size(350,100), caption("Basic Stroboscopic Effect")
numberbox bounds( 10, 5,70, 30), range(-1000,1000,15.777-,1,0.0001), channel("PhsRate"), text("Phase Rate")
numberbox bounds( 90, 5,70, 30), range(0,1000,6,1,0.0001), channel("SHrate"), text("S&H.Rate")
</Cabbage>

<CsoundSynthesizer>

<CsOptions>
-n -dm0
</CsOptions>

<CsInstruments>

sr = 44100
ksmps = 16
nchnls = 2
0dbfs = 1

instr   1
kPhsRate chnget    "PhsRate"
kSHrate  chnget    "SHrate"
kSHTrig  metro     kSHrate           ; train of triggers
aphs     phasor    kPhsRate          ; a phase vector moving from 0 - 1
aoct     samphold  aphs + 7, kSHTrig ; repeatedly sample-and-hold phasor
asig     poscil    0.2, cpsoct(aoct) ; sonify samp&hold signal
outs      asig, asig
endin

</CsInstruments>

<CsScore>
i 1 0 3600
</CsScore>

</CsoundSynthesizer>
```

Figure 2 below illustrates the basic transformation of a frequency phase vector with a negative frequency. State (i) represents the untransformed function, or a rate of sample-and-hold equal to the sample rate. States (ii) and (iii) illustrate the increasing quantisation resulting from lower rates of sample-and-hold. As both the frequencies of the phasor and of the sample-and-hold share a common factor, simple periodicity is exhibited at the same frequency as the phasor. If the frequency of the sample-and-hold was lower than that of the phasor (but still with a shared common factor), then the resultant periodicity would be equal to the frequency of the sample-and-hold.

Figure 2. Sample and hold.

If the two frequencies do not share a common factor, then precise periodicity will not occur in the resultant function, as illustrated by Figure 3.

Figure 3. Non integer ratio sample-and-hold.

We can refine the sound produced somewhat by enveloping each step of the sample-and-hold process so that they become more like notes than mere temporal quantisations of a continuous function. We do not need to limit ourselves to using an input function that describes the cyclical phase vector of a rotation, we can explore other waveforms. The following example builds on the previous one by adding these refinements. The user can select between a sawtooth, a sine and a half-sine as input functions. The input function can also be DC shifted and its range can be scaled. The phase vector is now referred to as an input LFO.

```; Example03.csd

<Cabbage>
form size(350,100), caption("Stroboscopic Effect")
button    bounds( 10, 5,60, 30), text("Off","On"), channel("OnOff")
line      bounds( 78, 0, 1,100)
numberbox bounds( 90, 5,70, 30), range(-1000,1000,15.777-,1,0.0001), channel("LFOrate"), text("LFO.Rate")
combobox  bounds(165,18,70, 20), text("saw","sine","half-sine"), channel("wave")
numberbox bounds( 90,50,70, 30), range(0,6,1,1,0.01), channel("LFOrange"), text("LFO.Range")
numberbox bounds(165,50,70, 30), range(4,12,8,1,0.01), channel("LFOoffset"), text("LFO.Offset")
line      bounds(243, 0, 1,100)
numberbox bounds(255, 5,70, 30), range(0,1000,6,1,0.0001), channel("SHrate"), text("S&H.Rate")
</Cabbage>

<CsoundSynthesizer>

<CsOptions>
-n -dm0
</CsOptions>

<CsInstruments>

sr = 44100
ksmps = 16
nchnls = 2
0dbfs = 1

giwave       ftgen    0,0,4097,10,1,0,0.2,0,0,0.05,0,0,0,0,0.01,0,0,0,0,0.001
gilfo1       ftgen    0,0,4097,7,0,4096,1   ; sawtooth up
gilfo2       ftgen    0,0,4097,19,1,1,0,1   ; sine wave shifted into the positive domain only
gilfo3       ftgen    0,0,4097,9,0.5,1,0    ; half-sine

instr   1
kOnOff      chnget   "OnOff"
gkLFOrate   chnget   "LFOrate"
gkLFOrange  chnget   "LFOrange"
gkLFOoffset chnget   "LFOoffset"
gkSHrate    chnget   "SHrate"
gkwave      chnget   "wave"

; turn instr 2 on and off
if trigger(kOnOff,0.5,0)==1 then
event "i",2,0,-1
elseif trigger(kOnOff,0.5,1)==1 then
turnoff2  2,0,0
endif
endin

instr   2   ;SAMPLE AND HOLD NOTE SEQUENCER
aoct        oscilikt gkLFOrange, gkLFOrate, gilfo1+gkwave-1
kSHTrig     metro    gkSHrate
if kSHTrig==1 then
reinit  RESTART_ENVELOPE
endif
RESTART_ENVELOPE:
aenv        expseg   1,0.7,0.001,1,0.001
rireturn
aenv        butlp    aenv, 200                  ; smooth envelope interruptions
aoct        samphold aoct + gkLFOoffset, kSHTrig
asig        poscil   0.2*aenv, cpsoct(aoct), giwave
asig        butlp    asig,8000*aenv
outs     asig, asig
endin

</CsInstruments>

<CsScore>
i 1 0 3600
</CsScore>

</CsoundSynthesizer>
```

This technique’s ability to generate unexpected patterns—that can themselves be continually evolving depending on the LFO rate and sample-and-hold rates we choose—tempts us to explore the results when sonified using different non-pitched sounds. We can replace our continuous function with a sequence of drums sounds—each of the spokes of the wagon wheel representing a different drum sound if you like—and thereby create a drum rhythm generator. This is implemented in the next example which also combines a second LFO input which is used to define amplitude. This means that an amplitude emphasis pattern is created which cycles independently to the one used to define what sound is to be played. The first LFO (triangle wave shaped) when sampled will choose one from six possible sounds: kick drum, rim-shot, snare, closed hi-hat, open hi-hat and tambourine. The methods used to produce these sounds are not the subject of this article so are not discussed here. The second LFO, used to define the amplitude emphasis pattern, employs a simple square wave meaning that dynamics are binary: sounds are either accented or unaccented. This accenting LFO, if set to a different frequency than that of the sound selecting LFO, can undermine the repetitiveness of the basic pattern by adding subtle aperiodic variations in emphasis. By adding additional LFO functions—each with a different frequency value—to control additional sound attributes, we can add further layers of aperiodicity while the underlying periodicity of the sound selection LFO remains constant.

```; Example04.csd

<Cabbage>
form size(350,100), caption("Strobe Drum Patterns")
button    bounds( 10,10,60,30), text("Off","On"), channel("OnOff")
numberbox bounds( 80, 5,70,30), range(0,1000,8,1,0.0001), channel("SHrate"), text("S&H.Rate")
numberbox bounds(160, 5,70,30), range(0,1000,11.125,1,0.0001), channel("LFOrate"), text("LFO.Rate")
numberbox bounds(160,40,70,30), range(0,1000,17,1,0.0001), channel("LFOrate2"), text("LFO.Rate 2")
</Cabbage>

<CsoundSynthesizer>

<CsOptions>
-n -dm0
</CsOptions>

<CsInstruments>

sr = 44100
ksmps = 16
nchnls = 2
0dbfs = 1

giSawDn     ftgen   0,0,4097,7,1,4096,0
giTri       ftgen   0,0,4097,7,0,2048,1,2048,0
giSqu       ftgen   0,0,4097,7,1,2048,1,0,0,2048,0

instr   1
kOnOff     chnget  "OnOff"
gkSHrate   chnget  "SHrate"
gkLFOrate  chnget  "LFOrate"
gkLFOrate2 chnget  "LFOrate2"
; turn instr 2 on and off
if trigger(kOnOff,0.5,0)==1 then
event "i",2,0,-1
elseif trigger(kOnOff,0.5,1)==1 then
turnoff2  2,0,0
endif
endin

instr   2   ; sample-and-hold drum pattern generator
ktrig  metro       gkSHrate
anum   poscil3     5.999, gkLFOrate, giTri
anum   samphold    anum + 3, ktrig
knum   downsamp    anum

aamp   poscil3     1, gkLFOrate2, giSqu
aamp   samphold    (aamp*0.7) + 0.3, ktrig
kamp   downsamp    aamp

schedkwhen ktrig,0,0,int(knum),0,0.1,kamp
endin

; kick drum
gicos       ftgen   0,0,131072,11,1
instr   3
kmul    transeg 0.2,p3*0.5,-15,0.01, p3*0.5,0,0
kbend   transeg 0.5,1.2,-4, 0,1,0,0
asig    gbuzz   0.5,50*semitone(kbend),20,1,kmul,gicos
aenv    transeg 1,p3-0.004,-6,0
aatt    linseg  0,0.004,1
asig    =   asig*aenv*aatt
aenv    linseg  1,0.07,0
acps    expsega 400,0.07,0.001,1,0.001
aimp    oscili  aenv,acps*octave(0.25)
aBD     =       ((asig*0.5)+(aimp*0.35))*p4
outs    aBD, aBD
endin

; rim shot
giTR808RimShot  ftgenonce   0,0,1024,10,    0.971,0.269,0.041,0.054,0.011,0.013,0.08,0.0065,0.005,0.004,0.003,0.003,0.002,0.002,0.002,0.002,0.002,0.001,0.001,0.001,0.001,0.001,0.002,0.001,0.001   ;WAVEFORM FOR TR808 RIMSHOT
instr   4
idur    =       0.027
p3      limit   idur,0.1,10
aenv1   expsega 1,idur,0.001,1,0.001
ifrq1   =       1700
aring   oscili  1,ifrq1,giTR808RimShot,0
aring   butbp   aring,ifrq1,ifrq1*8
aring   =       aring*(aenv1-0.001)*0.5
anoise  noise   1,0
aenv2   expsega 1, 0.002, 0.8, 0.005, 0.5, idur-0.002-0.005, 0.0001, 1, 0.0001
anoise  buthp   anoise,800
kcf     expseg  4000,p3,20
anoise  butlp   anoise,kcf
anoise  =       anoise*(aenv2-0.001)
amix    =       (aring+anoise)*0.8*p4
outs    amix,amix
endin

; snare
instr   5
ifrq    =       342
iNseDur =       0.3
iPchDur =       0.1
p3      =       iNseDur
aenv1   expseg  1,iPchDur,0.0001,p3-iPchDur,0.0001
apitch1 oscili  1,ifrq
apitch2 oscili  0.25,ifrq*0.5
apitch  =       (apitch1+apitch2)*0.75
aenv2   expon   1,p3,0.0005
anoise  noise   0.75,0
anoise  butbp   anoise,10000,10000
anoise  buthp   anoise,1000
kcf expseg      5000,0.1,3000,p3-0.2,3000
anoise  butlp   anoise,kcf
amix    =       ((apitch*aenv1)+(anoise*aenv2))*p4
outs    amix,amix
endin

; closed hi-hat
instr   6
iactive active  p1+1
if iactive>0 then
turnoff2   p1+1,0,0
endif
kFrq1   =       296
kFrq2   =       285
kFrq3   =       365
kFrq4   =       348
kFrq5   =       420
kFrq6   =       835
idur    =       0.088
p3      limit   idur,0.1,10
aenv    expsega 1,idur,0.001,1,0.001
ipw     =       0.25
a1      vco2    0.5,kFrq1,2,ipw
a2      vco2    0.5,kFrq2,2,ipw
a3      vco2    0.5,kFrq3,2,ipw
a4      vco2    0.5,kFrq4,2,ipw
a5      vco2    0.5,kFrq5,2,ipw
a6      vco2    0.5,kFrq6,2,ipw
amix    sum a1,a2,a3,a4,a5,a6
amix    reson   amix,5000,5000,1
amix    buthp   amix,5000
amix    buthp   amix,5000
amix    =       amix*aenv
anoise  noise   0.8,0
aenv    expsega 1,idur,0.001,1,0.001
kcf     expseg  20000,0.7,9000,idur-0.1,9000
anoise  butlp   anoise,kcf
anoise  buthp   anoise,8000
anoise  =       anoise*aenv
amix    =       (amix+anoise)*0.55*p4
outs    amix,amix
endin

; open hi-hat
instr   7
kFrq1   =       296
kFrq2   =       285
kFrq3   =       365
kFrq4   =       348
kFrq5   =       420
kFrq6   =       835
p3      =       0.5
aenv    linseg  1,p3-0.05,0.1,0.05,0
ipw     =       0.25
a1      vco2    0.5,kFrq1,2,ipw
a2      vco2    0.5,kFrq2,2,ipw
a3      vco2    0.5,kFrq3,2,ipw
a4      vco2    0.5,kFrq4,2,ipw
a5      vco2    0.5,kFrq5,2,ipw
a6      vco2    0.5,kFrq6,2,ipw
amix    sum     a1,a2,a3,a4,a5,a6
amix    reson   amix,5000,5000,1
amix    buthp   amix,5000
amix    buthp   amix,5000
amix    =       amix*aenv*p4
anoise  noise   0.8,0
aenv    linseg  1,p3-0.05,0.1,0.05,0
kcf     expseg  20000,0.7,9000,p3-0.1,9000
anoise  butlp   anoise,kcf
anoise  buthp   anoise,8000
anoise  =       anoise*aenv
amix    =       (amix+anoise)*0.3*p4
outs    amix,amix
endin

; tambourine
instr   8
p3      =           0.5
asig    tambourine  0.3*p4, 0.01, 32, 0.47, 0, 2300, 5600, 8000
outs        asig, asig
endin

</CsInstruments>

<CsScore>
i 1 0 [3600*24*365]
</CsScore>

</CsoundSynthesizer>
```

Another approach, and one which provides better code economy, sends data retrieved from arrays using a pointer derived from the LFO/sample-and-hold to a single instrument. The following example sends values for stiffness and duration to an instrument generating sound using the `barmodel` opcode.

```; Example05.csd

<Cabbage>
form size(350,100), caption("Strobe Bar Model")
button    bounds( 10,10,60,30), text("Off","On"), channel("OnOff")
numberbox bounds( 80, 5,70,30), range(0,1000,6,1,0.0001), channel("SHrate"), text("S&H.Rate")
numberbox bounds(160, 5,70,30), range(0,1000,20.25,1,0.0001), channel("LFOrate"), text("LFO.Rate")
numberbox bounds(160,40,70,30), range(0,1000,55.0625,1,0.0001), channel("LFOrate2"), text("LFO.Rate 2")
</Cabbage>

<CsoundSynthesizer>

<CsOptions>
-n -dm0
</CsOptions>

<CsInstruments>

sr = 44100
ksmps = 16
nchnls = 2
0dbfs = 1

giTri       ftgen   0,0,4097,7,0,2048,1,2048,0
giSqu       ftgen   0,0,4097,7,1,2048,1,0,0,2048,0

instr   1
kOnOff     chnget   "OnOff"
gkSHrate   chnget   "SHrate"
gkLFOrate  chnget   "LFOrate"
gkLFOrate2 chnget   "LFOrate2"
if trigger(kOnOff,0.5,0)==1 then
event    "i",2,0,-1
elseif trigger(kOnOff,0.5,1)==1 then
turnoff2 2,0,0
endif
endin

instr   2
ktrig      metro      gkSHrate

aNdx       poscil3    5.999, gkLFOrate, giTri
aNdx       samphold   aNdx, ktrig
kNdx       downsamp   aNdx

aamp       poscil3    1, gkLFOrate2, giSqu
aamp       samphold   (aamp*0.7) + 0.3, ktrig
kamp       downsamp   aamp

schedkwhen ktrig,0,0,3,0,0.1,int(kNdx),kamp,kNdx
endin

giStiffs[]  fillarray  37,  50,  60,  70,  80, 90
giDurs[]    fillarray  2, 1.8, 1.6, 1.4, 1.2,  1

instr   3
p3         =          giDurs[p4]
iK         =          giStiffs[p4]
iK         =          37*(1+(2/(p6+1)))
asig       barmodel   2, 2, iK, 0.5, 0, p3, 0.4+rnd(0.2), 2000*p5, 0.07
outs       asig, asig
endin

</CsInstruments>

<CsScore>
i 1 0 [3600*24*365]
</CsScore>

</CsoundSynthesizer>
```

## II. Pendulum Waves

The pendulum waves experiment is a favourite of physics department open days. It takes an array of pendulums of decreasing lengths, all connected along a single axle, which are released into motion from the same initial angle at the same time. The magic of the effect is provided by the sequence of patterns that emerge and dissolve unexpectedly as various members of the set of pendulums move into and out of phase at various times during the process. The caveat is that the lengths of the pendulums need to be carefully chosen to ensure the most visible results, but these are relatively easy to calculate. Several demonstrations of the pendulum waves effect can be found on YouTube, here is one of them:

If we ignore the effects of air resistance and friction at the pivot, the physics of a pendulum are relatively simple. It is initially surprising to note that the mass of the bob is irelevant in defining the period of the swing. This irrelevance of mass, once air resistance is removed, was first proposed by the Italian Renaissance scientist Galileo Galilei and was finally confirmed experimentally on the Moon when astronaut David Scott demonstrated a feather and a hammer released at the same time, falling at the same rate.

The formula for calculating the period (time to complete an oscillation) of a pendulum of a known length is:

Where L is the length of the pendulum and g is acceleration due to gravity. If we have a desired period and want to know the length of pendulum that will create this, we can derive the following equation:

For the pendulum waves effect we are interested in creating a sequence of pendulums that are all initially all in sync and only again after 1 minute, therefore we want to replace period with frequency in swings per minute. Frequency is simply the reciprocal of period so we can derive the following equation:

Now we simply need to replace f with an integer number of swings per minute for each of our pendulums. g, or acceleration due to gravity, can be replaced with the approximation, 9.81. A typical set of values for twelve pendulums might be:

 Swings per minute 60 59 58 57 56 55 54 53 52 51 50 49 Length (m) 0.248 0.257 0.266 0.275 0.285 0.296 0.307 0.318 0.331 0.344 0.358 0.373

We can use this data to create animations of pendulums using Cabbage widgets. The phase of each pendulum is defined using a sine wave oscillator and trigonometry is used to calculate the positions of pendulums at any particular point in time. A simple sonification is applied whereby each pendulum triggers a sound when it passes through the lowermost point of its travel. In order to sonically identify each pendulum, the pitches produced are proportional to the length of the pendulums.

```; Example06.csd

<Cabbage>
form size(300,200), guirefresh(16), colour("white")
image bounds(150,0,1,165), colour(200,200,200), shape("sharp")
image bounds(0,0,0,0), widgetarray("bob",12), shape("ellipse"), colour("black")
numberbox bounds(5,170,60,30), text("Time"), channel("time"), range(-100000,10000,0,1,0.01), colour("white"), textcolour("black"), fontcolour("black")
</Cabbage>

<CsoundSynthesizer>

<CsOptions>
-n -dm0
</CsOptions>

<CsInstruments>

sr = 44100
ksmps = 16
nchnls = 2
0dbfs = 1

instr   1
gkRefresh  metro   32
endin

instr   2
iMToPix    init    400                             ; scaling sizes to pixels
ix_OS      =       150                             ; x position offset
iInitAngle =       75                              ; initial angle from which to swing
iInitAngle /=      90                              ; initial angle in radians
iSpeed     =       0.3                             ; speed modulation of the process: 1=normal <1=slowed down etc.
iDamp      =       0.001                           ; amount of damping experience by the pendulums
iL         =       ((60/(2*\$M_PI*p4))^2)*9.81      ; calculate length of pendulum
iT         =       2 * \$M_PI * sqrt(iL/9.81) * (1/iSpeed)  ; period of a pendulum swing - dependent upon pendulum length but not mass
kamp       init    iInitAngle                      ; set initial angle
kamp       =       kamp / (1+(iDamp/kr))           ; amplitude of swing, decreased by damping coefficient upon each k-cycle
kphs       poscil  kamp,1/iT,-1,0.75               ; pendulum phase
ktime      init    0                               ; initialise elapsed time counter
if metro:k(8)==1 then                              ; update elapsed time widget 8 times per second
chnset  ktime,"time"                    ; send new time value to numberbox widget
endif
ktime      +=      iSpeed/kr                       ; increment time (account for speed modulation)
Sident     sprintf "bob_ident%d",p5
if gkRefresh==1 then                               ; if a (graphic) refresh trigger is received...
kx_pix    =       sin(\$M_PI*kphs*0.5)*iL*iMToPix  ; use trig to calculate x position for bob
ky_pix    =       cos(\$M_PI*kphs*0.5)*iL*iMToPix  ; use trig to calculate y position for bob
chnset  Smsg, Sident                    ; send new position message to widget
endif
if trigger(kphs,0,2)==1 && timeinstk()!=1 then     ; if pendulum is crossing the sound trigger line
icps      =       cpsmidinn(20+(p5*6.717))        ; derive a sound frequency value from the length of the pendulum
event   "i",3,0,440/icps,icps           ; trigger a sound event
endif
SKIP:
endin

instr   3   ; a simple sound 'ping'
aenv       expon   1,p3,0.001
iamp       =       1/octcps(p4)
asig       poscil  iamp*aenv,p4
asig       *=      poscil:a(1,300)
outs    asig,asig
endin

</CsInstruments>

<CsScore>
i 1 0 3600

i 2 0 3600 49 1
i 2 0 3600 50 2
i 2 0 3600 51 3
i 2 0 3600 52 4
i 2 0 3600 53 5
i 2 0 3600 54 6
i 2 0 3600 55 7
i 2 0 3600 56 8
i 2 0 3600 57 9
i 2 0 3600 58 10
i 2 0 3600 59 11
i 2 0 3600 60 12
</CsScore>

</CsoundSynthesizer>
```

Again it can be observed how the pendulums move in and out of phase with one another and at different locations in the swings, sometimes in pairs, sometimes in threes, fours or sixes but we only get fleeting impressions of this in this demonstration. If we replace the oscillator that defines the swing phases with just a calculation of that pendulum’s phase according to a given time since the pendulums were released, we can examine with more precision the stages of coherence that the set of pendulums goes through before they all resynchronise after 1 minute. The following example allows the user to increment time manually, go backwards in time, or to jump to a specific time. Whenever this time value changes the pendulums are set to their appropriate locations.

```; Example07.csd

<Cabbage>
form size(300,200), guirefresh(32), text("Pendulum"), colour("white")
image bounds(0,0,0,0), widgetarray("weight",12), shape("ellipse"), colour("black")
button    bounds(  0,185,20,15), text("-","-"), channel("dec"), latched(0), colour:0("white"), colour:1("white"), fontcolour:0("black"), fontcolour:1("black")
numberbox bounds( 20,170,70,30), channel("time"), range(0,10000,0,1,0.0001), text("time"), colour("white"), textcolour("black"), fontcolour("black")
button    bounds( 90,185,20,15), text("+","+"), channel("inc"), latched(0), colour:0("white"), colour:1("white"), fontcolour:0("black"), fontcolour:1("black")
</Cabbage>

<CsoundSynthesizer>

<CsOptions>
-n -dm0
</CsOptions>

<CsInstruments>

sr     = 44100
ksmps  = 16
nchnls = 2
0dbfs  = 1

gisine  ftgen   0,0,4096,9,1,1,270

giMToPix    init  400     ; scaling sizes to pixels
gix_OS      =     150     ; x position offset
giDamp      =     0       ; damping (0=no_damping 1=maximum_damping)
giInitAngle =     75
giInitAngle /=    90

instr   1
gkRefresh  metro   16
gktime     chnget  "time"
kdec       chnget  "dec"
kinc       chnget  "inc"
if kdec==1 then
chnset    limit(gktime-0.0001,0,10000),"time"
endif
if kinc==1 then
chnset    limit(gktime+0.0001,0,10000),"time"
endif
endin

instr   2
ilen       =       (((60/p4)/(2*\$M_PI))^2)*9.81    ; calculate length of pendulum
kamp       init    giInitAngle
kamp       =       kamp / (1+(giDamp/kr))
kphs       =       gktime*(p4/60)
kphs       tablei  kphs,gisine,1,0,1
kphs       *=      kamp
Sident     sprintf "weight_ident%d",p5
kx_pix     =       sin(\$M_PI*kphs*0.5)*ilen*giMToPix
if gkRefresh==1 then
kx_pix    =       sin(\$M_PI*kphs*0.5)*ilen*giMToPix
ky_pix    =       cos(\$M_PI*kphs*0.5)*ilen*giMToPix
chnset  Smsg, Sident
endif
endin

</CsInstruments>

<CsScore>
i 1 0 3600
i 2 0 3600 49 1
i 2 0 3600 50 2
i 2 0 3600 51 3
i 2 0 3600 52 4
i 2 0 3600 53 5
i 2 0 3600 54 6
i 2 0 3600 55 7
i 2 0 3600 56 8
i 2 0 3600 57 9
i 2 0 3600 58 10
i 2 0 3600 59 11
i 2 0 3600 60 12
</CsScore>

</CsoundSynthesizer>
```

Figure 4 below illustrates the most obvious phases of coherence through which the pendulums pass in a minute. The timings, from when initially released, at which these occur are shown in the bottom left corner of each frame.

Figure 4. Phases of pendulum coherence.

During phase 1 at around 7.5 seconds, 4 pairs of pendulums emerge, at around 10 seconds, 6 pairs of pendulums emerge, at around 12 seconds 2 sets of 3 pendulums and 3 sets of 2 pendulums emerge and so on.

With a greater understanding of the influencing factors upon the formation of patterns within the set of pendulums, we can start to manipulate the various parameters of the original model to explore what further possiblilities there might be for pattern generation in Csound. The final example (Figure 5 and Example08.csd below) adds many more options for real-time user interaction. The speed factor can now be modulated from within the GUI. If "Hold Pattern" is activated, the current pattern is maintained, i.e. all oscillation periods will be the same regardless of pendulum length. We can set the initial phase of all pendulums using "Init.Angle" although employment of changed values will require hitting "Restart". "Trig.Angle" sets the phase location at which pendulums will trigger a sound—this is no longer fixed at the lowest point of their swings—and this setting is reflected in the position of a grey line, as well as sonically. "Damping" allows for setting the amount of damping the pendulums will experience as they swing. If this is zero, all pendulums will swing indefinitely. "Size Scale" is merely used to scale up or down the visual representation of the swinging pendulums—this is useful if they swing beyond the boundaries of the panel. The frequency of each pendulum can also be modified from the GUI. This will be reflected in a changing length of that pendulum and this will happen in real-time so no restart of the process is required.

With these added options the instrument becomes an effective machine for real-time pattern generation. Controlling the pattern with these abstracted input controls suggests a form of algorithmic composition. Compositional decisions can be effected by, for example, freezing an emerging pattern that is deemed desirable using "Hold Pattern". Patterns can be revisited by reversing time through giving "Speed" a negative value. The greatest scope is presented by the ability to set each individual pendulum to any frequency value. This opens up the possibility of setting up multiple sub-groupings of pendulums: a polyphony of pendulum-waves sets.

Figure 5. Pendulum waves Cabbage interface.

```; Example08.csd

<Cabbage>
form size(300,330), guirefresh(16), text("Pendulum"), colour("white")
image bounds(150,0,1,165), colour(200,200,200), shape("sharp"), rotate(0,1,1), identchannel("TrigAngleID")
image bounds(0,0,0,0), widgetarray("bob",12), shape("ellipse"), colour("black")
numberbox bounds(5,170,60,30), text("Time"), channel("time"), range(-100000,10000,0,1,0.01), colour("white"), textcolour("black"), fontcolour("black")
checkbox bounds(75,180,60,15), text("Pause"), channel("pause"), fontcolour("black")
numberbox bounds(135,170,60,30), text("Speed"), channel("speed"), range(-10,10,0.2,1,0.01), colour("white"), textcolour("black"), fontcolour("black")
checkbox bounds(200,180,90,15), text("Hold Pattern"), channel("sync"), fontcolour("black")

numberbox bounds(5,205,55,30), text("Init.Angle"), channel("InitAngle"), range(-90,90,-75,1,1), colour("white"), textcolour("black"), fontcolour("black")
numberbox bounds(65,205,55,30), text("Trig.Angle"), channel("TrigAngle"), range(-90,90,0,1,1), colour("white"), textcolour("black"), fontcolour("black")
numberbox bounds(125,205,55,30), text("Damping"), channel("Damping"), range(0,0.1,0,1,0.0001), colour("white"), textcolour("black"), fontcolour("black")
button    bounds(185,210,50,20), text("Restart","Restart"), channel("Restart")
numberbox bounds(240,205,55,30), text("Size Scale"), channel("MToPix"), range(10,5000,400,1,1), colour("white"), textcolour("black"), fontcolour("black")

image     bounds(  2,240,296,88), colour(200,200,200), outlinecolour("black"), outlinethickness(2), shape("rounded")
label     bounds(  2,242,296,12), text("Pendulum Frequencies [per minute]"), fontcolour("black")
numberbox bounds( 15,255,40,30), text("1"),  channel("F1"),  range(10,500,49,1,0.1), colour("white"), textcolour("black"), fontcolour("black")
numberbox bounds( 60,255,40,30), text("2"),  channel("F2"),  range(10,500,50,1,0.1), colour("white"), textcolour("black"), fontcolour("black")
numberbox bounds(105,255,40,30), text("3"),  channel("F3"),  range(10,500,51,1,0.1), colour("white"), textcolour("black"), fontcolour("black")
numberbox bounds(150,255,40,30), text("4"),  channel("F4"),  range(10,500,52,1,0.1), colour("white"), textcolour("black"), fontcolour("black")
numberbox bounds(195,255,40,30), text("5"),  channel("F5"),  range(10,500,53,1,0.1), colour("white"), textcolour("black"), fontcolour("black")
numberbox bounds(240,255,40,30), text("6"),  channel("F6"),  range(10,500,54,1,0.1), colour("white"), textcolour("black"), fontcolour("black")
numberbox bounds( 15,290,40,30), text("7"),  channel("F7"),  range(10,500,55,1,0.1), colour("white"), textcolour("black"), fontcolour("black")
numberbox bounds( 60,290,40,30), text("8"),  channel("F8"),  range(10,500,56,1,0.1), colour("white"), textcolour("black"), fontcolour("black")
numberbox bounds(105,290,40,30), text("9"),  channel("F9"),  range(10,500,57,1,0.1), colour("white"), textcolour("black"), fontcolour("black")
numberbox bounds(150,290,40,30), text("10"), channel("F10"), range(10,500,58,1,0.1), colour("white"), textcolour("black"), fontcolour("black")
numberbox bounds(195,290,40,30), text("11"), channel("F11"), range(10,500,59,1,0.1), colour("white"), textcolour("black"), fontcolour("black")
numberbox bounds(240,290,40,30), text("12"), channel("F12"), range(10,500,60,1,0.1), colour("white"), textcolour("black"), fontcolour("black")
</Cabbage>

<CsoundSynthesizer>

<CsOptions>
-n -dm0
</CsOptions>

<CsInstruments>

sr      =   44100
ksmps   =   16
nchnls  =   2
0dbfs   =   1

instr   1 ; start pendulums
gkRefresh    metro   32          ; refresh rate
gkFreqs[]    init    12          ; array of pendulum frequency values
gkRestart    chnget  "Restart"       ; manual restart trigger
gkMToPix     chnget  "MToPix"        ; scaling sizes to pixels
gkFreqs[0]   =       chnget:k("F1")
gkFreqs[1]   =       chnget:k("F2")
gkFreqs[2]   =       chnget:k("F3")
gkFreqs[3]   =       chnget:k("F4")
gkFreqs[4]   =       chnget:k("F5")
gkFreqs[5]   =       chnget:k("F6")
gkFreqs[6]   =       chnget:k("F7")
gkFreqs[7]   =       chnget:k("F8")
gkFreqs[8]   =       chnget:k("F9")
gkFreqs[9]   =       chnget:k("F10")
gkFreqs[10]  =       chnget:k("F11")
gkFreqs[11]  =       chnget:k("F12")
gkL12        =       ((60/(2*\$M_PI*gkFreqs[11]))^2)*9.81 ; length of the last pendulum
iCount       =       0
loop:
event_i "i",2+(iCount/100),0,3600,iCount
loop_lt iCount,1,12,loop
endin

instr   2   ; a pendulum
if changed(gkRestart)==1 then          ; if a restart trigger has been received
reinit RESTART                ; reinitialise from label
endif
RESTART:
ix_OS        =   150                 ; x position offset
iInitAngle   chnget    "InitAngle"   ; initial angle of from which swing begins
iInitAngle   /=        -90           ; in radians
kSpeed       chnget    "speed"       ; speed 1=normal, <1=slow_motion etc.
kpause       chnget    "pause"       ; pause motion if '1'
ksync        chnget    "sync"        ; if '1' all pendulums adopt the same frequency, i.e. pattern is held and repeated
kTrigAngle   chnget    "TrigAngle"   ; angle at which sounds are triggered
kDamping     chnget    "Damping"     ; amount of amplitude damping the pendulums experience
if changed(kTrigAngle)==1 then       ; if sound trigger angle is changed...
Smsg        sprintfk  "rotate(%f,1,1)",-kTrigAngle/90 *1.57   ; create a new string for the Cabbage line widget
chnset    Smsg, "TrigAngleID" ; send string to the widget to update its appearance
endif
kFreq        =         gkFreqs[p4]   ; read frequency value for this pendulum from array
kL           =         ((60/(2*\$M_PI*kFreq))^2)*9.81           ; calculate length of pendulum
if kpause==1 kgoto SKIP              ; if pause button is on skip all further processes in this instrument
if ksync==0 then               ; is sync button is off (follow normal behaviour)
kPeriod     =         2 * \$M_PI * sqrt(kL/9.81) * (1/kSpeed)  ; period of a pendulum swing - dependent upon pendulum length but not mass
else
kPeriod     =         2 * \$M_PI * sqrt(gkL12/9.81) * (1/kSpeed)   ; sync is on: all pendulums use the same length value
endif
kamp         init      iInitAngle    ; amplitude initialised according to initial angle of swing set by widget
kamp         =         kamp / (1+(kDamping/kr))    ; amplit; update elapsed time widget 8 times per secondude of swing, decreased by damping coefficient upon each k-cycle
kphs         poscil    kamp,1/kPeriod,-1,0.75      ; pendulum phase
ktime        init      0             ; initialise elapsed time counter
if metro:k(8)==1 then
chnset    ktime,"time"  ; send new time value to numberbox widget
endif
if ksync==0 then                     ; if sync is off, (i.e. in sync-on mode, freeze elapsed time counter)
ktime       +=        kSpeed/kr     ; increment time (account for speed modulation)
endif
Sident       sprintf   "bob_ident%d",p4+1    ; generate a string for the ident name for this particular pendulum bob
if gkRefresh==1 then                 ; if a (graphic) refresh trigger is received...
kx_pix    =           sin(\$M_PI*kphs*0.5)*kL*gkMToPix ; use trig to calculate x position for bob
ky_pix    =           cos(\$M_PI*kphs*0.5)*kL*gkMToPix ; use trig to calculate y position for bob
chnset      Smsg, Sident          ; send new position message to widget
endif
if trigger(kphs,kTrigAngle/90,2)==1 && timeinstk()!=1 then ; if pendulum is crossing the sound trigger line
icps      =           cpsmidinn(20+((p4+1)*6.717))            ; derive a sound frequency value from the length of the pendulum
event       "i",3,0,440/icps,icps                   ; trigger a sound event
endif
SKIP:
endin

instr   3 ; a sound ping
aenv       expon   1,p3,0.001
iamp       =       1/octcps(p4)
asig       poscil  iamp*aenv,p4
asig       *=      poscil:a(1,300)
outs    asig,asig
endin

</CsInstruments>

<CsScore>
i 1 0 3600
</CsScore>

</CsoundSynthesizer>
```

## Conclusion

The ability to generate complex musical patterns that exhibit clear coherence, but through an abstracted and simplified interaction with that process, is an appealing prospect. It can be a compositional inhibition to have to manually define the start time and all ancilliary parameters for each and every sound event desired, the use of an abstracted machine for this task can streamline note generation and can spawn pleasantly unexpected results. Ignoring more conventional approaches such as loop sequencers and piano roll sequencers leads to a new kind of music, and in this area Csound excels. The visual analogues that were used as starting points for these models, and that were deployed using Cabbage’s facilities for manipulating its widgets, are ultimately superfluous in the final music making, but their presence provides a confirmation of the mechansim and even anticipation of imminent sound patterns. These examples stop at the point at which the visual manifestation has been transmuted into a musical analogue, but the obvious direction in which to proceed would be to release the focus with respect to the visual source and pursue purely musical avenues of interest.

## References

[2] "The Wagon-wheel effect." [Online] Available: https://en.wikipedia.org/wiki/Wagon-wheel_effect [Accessed January 26th, 2016].