**Topics:** Connecting nature, music and number, Pythagorean theorem, music from math curves, sin() and cos() functions, the Python math library, visualizing oscillations, the harmonograph, sonifying oscillations, Kepler’s harmony of the world revisited.

In the previous chapters, we studied essential building blocks of music and computer science. We now know enough about music and programming to return to the themes introduced in chapter 1. In this chapter we will deepen our exploration into the connections between music, number, and nature, and introduce you to ideas that will hopefully inspire and guide you in your own personal journey into music and programming.

The Pythagoreans discovered that music harmony can be modeled by numbers. As Aristotle mentions, they thought that the principles of mathematics “were the principles of all things. Since, of these principles, numbers are by nature the first, and in numbers there seemed to see many resemblances to the things that exist and come into being” (Aristotle 1992, pp. 70-71).

## Making music from math curves

A mathematical function describes a line or curve. These curves could be thought of as describing a musical gesture, for example a melodic contour. A function can be mapped to any musical parameter, e.g., pitch in the case of melodic curve, or volume in the case of an amplitude envelope. The mapping between the function values and the musical parameters is entirely up to the composer; for instance, a function may influence more than one parameter at a time. The work of Iannis Xenakis was inspired by this possibility.

This code sample (Ch. 10, p. 321) demonstrates how to create a simple melodic contour using the sin() function. This program creates the pianoroll shown in figure below, which traces the sine wave oscillation.

Here is the program:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
# sineMelody.py # # This program demonstrates how to create a melody from a sine wave. # It maps the sine function to a melodic (i.e., pitch) contour. # from music import * from math import * phr = Phrase() density = 25.0 # higher for more notes in sine curve cycle = int(2 * pi * density) # steps to traverse a complete cycle # create one cycle of the sine curve at given density for i in range(cycle): value = sin(i / density) # calculate the next sine value pitch = mapValue(value, -1.0, 1.0, C2, C8) # map to range C2-C8 note = Note(pitch, TN) phr.addNote(note) # now, all the notes have been created View.pianoRoll(phr) # so view them Play.midi(phr) # and play them |

The constant pi is approximately 3.141592653589793. It corresponds to half a circle (or 180 degrees). Accordingly, 2*pi is a full circle (or 360 degrees).

Next, we connect additional musical parameters to the sine function, namely note duration, dynamic, and panning. The pianoroll generated by the updated program is shown below:

Notice the distortion in the sine wave graph. Why does that happen? The sine wave graph assumes steady movement on the x-axis (time), i.e., constant note durations. Since we connected the sine function to note duration, the resultant note durations fluctuate, hence the distortion in the graph.This sine wave inside a sine wave is the basis for FM synthesis (or frequency modulation) – a technique used for creating realistic, rich sounds for synthesizers.

Here is the program:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
# sineMelodyPlus.py # # This program demonstrates how to create a melody from a sine wave. # It maps the sine function to several musical parameters, i.e., # pitch contour, duration, dynamics (volume), and panning. # from music import * from math import * sineMelodyPhrase = Phrase() density = 25.0 # higher for more notes in sine curve cycle = int(2 * pi * density) # steps to traverse a complete cycle # create one cycle of the sine curve at given density for i in range(cycle): value = sin(i / density) # calculate the next sine value pitch = mapValue(value, -1.0, 1.0, C2, C8) # map to range C2-C8 #duration = TN duration = mapValue(value, -1.0, 1.0, TN, SN) # map to TN-SN dynamic = mapValue(value, -1.0, 1.0, PIANISSIMO, FORTISSIMO) panning = mapValue(value, -1.0, 1.0, PAN_LEFT, PAN_RIGHT) note = Note(pitch, duration, dynamic, panning) sineMelodyPhrase.addNote(note) View.pianoRoll(sineMelodyPhrase) Play.midi(sineMelodyPhrase) |

## Simulating a lateral harmonograph

The harmonograph is a device used to study what happens when you combine harmonic oscillations. It is closely related to the Pythagorean discoveries about musical intervals and their relationship to harmonic ratios, as well as to various other musical tunings and scales (Ashton, 2003).

Harmonographs are used to explore and easily visualize the relationships between different harmonic ratios.

A **lateral harmonograph** is a device with a pen attached to two pendula moving in orthogonal directions to each other. As the pendula move, the pen draws geometric shapes on a paper. Our controls include:

- The length of the pendula — this affects the frequency of their oscillation. By combining different frequency ratios (e.g., 2:3), we get different shapes (such as the one shown below).
- The phase of the pendula, relative to one another. In Python this is modeled by either using two sin() functions (same phase), or one sin() and one cos() function (opposite, or orthogonal) phase.

The code sample below (Ch. 10, p. 328) creates a display onto which it draws points that trace the movement of the virtual pen connected to the pendula. It also outputs the current ratio setting as shown here:

Here is the program:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
# harmonographLateral.py # # Demonstrates how to create a lateral (2-pendulum) harmonograph # in Python. # # See Ashton, A. (2003), Harmonograph: A Visual Guide to the # Mathematics of Music, Wooden Books, p.19. # from gui import * from math import * d = Display("Lateral Harmonograph", 250, 250) centerX = d.getWidth() / 2 # find center of display centerY = d.getHeight() / 2 # harmonograph parameters freq1 = 2 # holds frequency of first pendulum freq2 = 3 # holds frequency of second pendulum ampl = 50 # the distance each pendulum swings density = 250 # higher for more detail cycle = int(2 * pi * density) # steps to traverse a complete cycle times = 6 # how many cycles to run # display harmonograph ratio setting d.drawText("Ratio " + str(freq1) + ":" + str(freq2), 95, 20) # go around the unit circle as many times requested for i in range(cycle * times): # get angular position on unit circle (divide by a float # for more accuracy) rotation = i / float(density) # get x and y coordinates (run and rise) x = sin( rotation * freq1 ) * ampl # get run (same phase) #x = cos( rotation * freq1 ) * ampl # get run (opposite phase) y = sin( rotation * freq2 ) * ampl # get rise # convert to display coordinates (move display origin to center, # from top-left) x = x + centerX y = y + centerY # draw this point (pixel coordinates are int) d.drawPoint( int(x), int(y) ) |

Notice the various harmonograph parameters (defined as constants at the program’s beginning). Again this makes it easy to adjust the harmonograph to new settings. Also, notice the commented-out statement in the loop. It can be used to reverse the phase.

## Simulating a rotary harmonograph

A **rotary harmonograph** is a device with a pen attached to two pendulums moving in orthogonal directions to each other. As the pendulums move, the pen draws geometric shapes on a paper. Additionally, the paper is placed on another pendulum mounted on a rotary bearing (i.e., gimbals), which adds another oscillation to the drawing.

The code sample below (Ch. 10, p. 330) draws points that trace the movement of the virtual pen connected to the pendulums. It also outputs the current ratio setting, as seen below:

Here is the program:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
# harmonographRotary.py # # Demonstrates how to create a rotary (3-pendulum) harmonograph # in Python. # # Here, the position of the pen is determined by two pendula, # and is modeled by either (sin, sin) or (cos, sin). # The third pendulum has its own sin() and cos() to model the second # circle. # # See Ashton, A. (2003), Harmonograph: A Visual Guide to the # Mathematics of Music, Wooden Books, p.19. # from gui import * from math import * d = Display("Rotary Harmonograph", 250, 250) centerX = d.getWidth() / 2 # find center of display centerY = d.getHeight() / 2 # harmonograph parameters freq1 = 2 # holds frequency of first pendulum freq2 = 3 # holds frequency of second pendulum ampl1 = 40 # holds swing of movement for pair of pendulums # (radius of first circle) ampl2 = ampl1 # holds swing of movement for third pendulum # (radius of second circle) #friction = 0.0003 # how much energy is lost per iteration density = 250 # higher for more detail cycle = int(2 * pi * density) # steps to traverse a complete cycle times = 4 # how many cycles to run # display harmonograph ratio setting d.drawText("Freq Ratio " + str(freq1) + ":" + str(freq2), 80, 10) # go around the unit circle as many times requested for i in range(cycle * times): # get angular position on unit circle (divide by a float # for more accuracy) rotation = i / float(density) # get x and y coordinates (run and rise) x1 = sin( rotation * freq1 ) * ampl1 # get run (same phase) y1 = cos( rotation * freq1 ) * ampl1 # get rise #x1 = cos( rotation * freq1 ) * ampl1 # get run (opposite phase) #y1 = sin( rotation * freq1 ) * ampl1 # get rise x2 = sin( rotation * freq2) * ampl2 # get run (second pendulum) y2 = cos( rotation * freq2) * ampl2 # get rise # combine the two oscillations x = (x1 - x2) y = (y1 - y2) # convert to display coordinates (move display origin to center, # from top-left) x = x + centerX y = y + centerY # draw this point (pixel coordinates are int) d.drawPoint( int(x), int(y) ) # loss some energy due to friction # ampl1 = ampl1 * (1 - friction) # ampl2 = ampl2 * (1 - friction) |

Again, notice the various harmonograph parameters (defined as constants at the program’s beginning). This makes it easy to adjust the harmonograph to new settings. Also, notice the commented-out statements in the loop. They can be used to reverse the phase.

## Non-integer ratios

Non-integer ratios correspond to musical intervals that are not harmonious, i.e., not pleasing to the ear.

Interestingly, such ratios generate chaotic behavior in the path traced by the harmonograph pen. When exploring, increase the value of variable times to allow the pen to trace orbits over several cycles, so that you can begin to see the behavior that emerges. For example, the figure below shows the shapes generated by particular non-harmonic ratios (with times set to 6).

Certain ratios result in paths that will never converge, that is they will never re-trace the same path.

It has been shown that the speed of convergence of the ratios (that is, how quickly they begin to re-trace the same path) corresponds to our perception of density (consonance and dissonance) of musical intervals (Legname 1998). This is a very important connection music, number, and nature.

## Kepler’s Harmony of the World, No. 2

This code sample (Ch. 10, p. 334) explores a more advanced sonification of the planetary velocities we explored in chapter 7. Here we apply some of the same processes used in the lateral harmonograph to convert planetary velocities to music (or to sonify planetary velocities).

Here is the program:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
# harmonicesMundiRevisisted.py # # Sonify mean planetary velocities in the solar system. # from music import * from math import * from random import * # Create a list of planet mean orbital velocities # Mercury, Venus, Earth, Mars, Ceres, Jupiter, Saturn, Uranus, # Neptune. (Ceres is included in place of the 5th missing planet # as per Bode's law). planetVelocities = [47.89, 35.03, 29.79, 24.13, 17.882, 13.06, 9.64, 6.81, 5.43] numNotes = 100 # number of notes generated per planet durations = [SN, QN] # a choice of durations instrument = EPIANO # instruent to use speedFactor = 0.01 # decrease for slower sound oscillations score = Score(60.0) # holds planetary sonification # get minimum and maximum velocities: minVelocity = min(planetVelocities) maxVelocity = max(planetVelocities) # define a function to create one planet's notes - returns a Part def sonifyPlanet(numNotes, planetIndex, durations, planetVelocities): """Returns a part with a sonification of a planet's velocity.""" part = Part(EPIANO, planetIndex) # use planet index for channel phr = Phrase(0.0) planetVelocity = planetVelocities[planetIndex] # get velocity # create all the notes by tracing the oscillation generated using # the planetary velocities for i in range(numNotes): # pitch is constant pitch = mapScale(planetVelocity, minVelocity, maxVelocity, C3, C6, MIXOLYDIAN_SCALE, C4) # panning and dynamic oscillate based on planetary velocity pan = mapValue(sin(i * planetVelocity * speedFactor * 2), -1.0, 1.0, PAN_LEFT, PAN_RIGHT) dyn = mapValue(cos(i * planetVelocity * speedFactor * 3), -1.0, 1.0, 40, 127) # create the note and add it the the phrase n = Note(pitch, choice(durations), dyn, pan) phr.addNote(n) # now, all notes have been created part.addPhrase(phr) # add phrase to part return part # and return it # iterate over all plants for i in range( len(planetVelocities) ): part = sonifyPlanet(numNotes, i, durations, planetVelocities) score.addPart(part) View.sketch(score) Write.midi(score, "harmonicesMundiRevisisted.mid") Play.midi(score) |

Notice how the pitch of a planet remains the same for all generated notes – it depends on its planetary velocity.

The panning position and dynamic level of the notes associated with each planet are continually modulated using sin() and cos(). The rate of change is associated with the planetary velocity. This way, different planets will vary at different speeds.

Since the planetary velocities have harmonic relationships to each other, this musical mapping *sonifies* these harmonies using the panning and dynamic musical dimensions. The effect is breathtaking—dynamic variations give an impression of proximity (loud appears close to the listener, while soft appears far); and panning gives an impression of left to right movement. Two orthogonal (harmonic) oscillations perceivable in one ever-changing sound.

This sonification requires a nice pair of stereo headphones (or speakers).