As a straightforward example of self-similarity that may be used toward great musical effect, consider the Cantor Set shown below.
The steps required to create this self-similar design is as follows:
(1) start with a horizontal line segment.
(2) make a copy of this line immediately below it,
(3) divide the new line into three parts.
(4) remove the middle of the three parts - as shown in the first two steps of the diagram above, we change from a single solid line to two smaller lines with a space in the middle.
(5) for each of the two lines just made, repeat from step(2) above
That’s it! That is all one needs to know (either a human or a computer), to create the full design of the Cantor Set.
To apply this design toward an arrangement of sound clips in EarSketch, we can use the line-by-line pattern of a Cantor Set to specify where sound clips should be placed on consecutive Reaper tracks, yielding something like this:
As part of the code example for this section, we’ve created a function that places sound clips on consecutive tracks in Reaper, according to the pattern of a Cantor Set.
makeCantorSet(musicList, 1, 1, 4, 4)
When calling the above function, we supply it with these parameters:
|audioclips||a list of audio clips (one for each track)|
|tracknum||the track number we want it to start at|
|start||the measure number we want it to start at|
|length||the length of the full pattern in bars|
|depth||a depth amount|
The last parameter depth amount specifies the total number of tracks we want the function to create, which corresponds to the same number of lines in a Cantor set pattern (see the first diagram at the top).
from earsketch import *
def makeCantorSet(audioclips, tracknum, start, length, depth):
# parameters: list of audio clips, starting track number, starting measure, total length of section (in measures), depth of recursion (number of tracks to create)
if depth == 0: # when depth reaches zero, exit the function
fitMedia(audioclips, tracknum, start, start+length) # place the first audioclip of the list on the current track, starting at start and ending at start+length
smallerLength = length / 4.0 # calculate a new length value, for use for the two sound sections on the next track
secondSectionStart = start + (smallerLength * 3.0) # calculate the start of the second audio section on the next track
# to make each of the two smaller sections on the next track, recursively call the function with updated tracknum, length, and depth parameter values
makeCantorSet(audioclips[1:len(audioclips)], tracknum+1, start, smallerLength, depth-1) # create the first section on the next track
makeCantorSet(audioclips[1:len(audioclips)], tracknum+1, secondSectionStart, smallerLength, depth-1) # create the second section on the next track
soundList1 = [DUBSTEP_DRUMLOOP_MAIN_001, Y36_ELECTRO_1, DUBSTEP_BASS_WOBBLE_025, ELECTRO_ANALOGUE_LEAD_001, DUBSTEP_BASS_WOBBLE_025, ELECTRO_ANALOGUE_LEAD_001]
soundList2 = [DUBSTEP_DRUMLOOP_MAIN_007, Y43_SYNTH_HARP_1, Y36_ELECTRO_1, Y35_ELECTRO_2, Y36_ELECTRO_1, Y35_ELECTRO_2]
makeCantorSet(soundList1, 1, 1, 4, 4)
makeCantorSet(soundList1, 1, 5, 4, 4)
makeCantorSet(soundList2, 1, 9, 4, 4)
makeCantorSet(soundList2, 1, 13, 4, 4)
fitMedia(Y35_ELECTRO_2, 6, 1, 9)
fitMedia(Y35_ELECTRO_3, 6, 9, 17)
We see that on lines 11 and 12,
makeCantorSet() calls itself twice from within itself. Thus
makeCantorSet() is a recursive function, and its calls to itself on lines 11 and 12 are recursive calls.
Notice that in this example, there is more than one recursive call used within the definition of the function. These two recursive calls correspond to step(5) in the Cantor Set design instructions at the top of the page: one recursive call for each of the two new lines just created in step(4). And since there are two recursive calls here, each level of recursion has two times the number of recursive calls as the preceding level, shown by each level of the Cantor Set pattern – see both diagrams above !