Musical Turtles

It takes years to learn to play a musical instrument. Plus, being able to play Stairway to Heaven on a guitar doesn't mean you will be able to play it on a piano, even though the actual score is the same for both instruments, and even though the difference between a middle C played on a guitar and a middle C played on a piano is in the quality of the sound rather than its frequency or duration. What to do? Learn to play the synthesizer.

A synthesizer looks like a piano keyboard but with the turn of a knob it can sound like any instrument: guitar, piano, flute, ocean waves, etc.

A drum machine consists of four or five rubber pads. With the turn of a knob any pad can be made to sound like any percussion instrument: snare, tom-tom, kettle drum, cymbal, etc.

A source (such as a keyboard or drum pad) sends messages to a digital audio player. Along the way these messages may be filtered or have effects applied to them. The format of these messages is specified by MIDI (Musical Instrument Digital Interface.) There are three basic types of MIDI messages:

Note-On (key down)

Aftertouch (pressure changes while holding the key down)

Note-off (key up)

The Note-On message consists of several parts:

Note-on(instrument, key, velocity, duration)

NetLogo's Sounds Extension simulates a 128 key synthesizer that can sound like 128 different instruments. It also simulates a drum machine that can sound like 47 different drums.

In this lab we will create the following interface to the NetLogo synthesizer:

Notice that this interface only has buttons and other controls. Where is the turtle world? Answer, I shrunk it down as much as possible and dragged it below the bottom edge of the window.

We can select an instrument adjust the time (duration) and loudness (velocity) of each note. We can also record our performances. The interface also provides 13 buttons, one for each key in the middle octave of a piano.

(Here's an Applet version that might work: Synthesizer.)

Here's how your program should begin:

extensions [sound] ; needed to use any extension

globals [score]    ; score = list of recorded notes

to init-score
  set score []     ; initially, score is the empty list
end

; to play one note
to playNote [note]
  sound:play-note instrument note loudness time
  if recording?
  [
    set score lput note score  
  ]
end

The sound:play-note procedure comes from the Sounds extension (and hence must be qualified by the word "sound"). The inputs instrument, loudness, and time come from the controls on the user interface. The note input is an integer between 0 and 127 and specifies some key on the synthesizer's keyboard. 60 is middle C.

List Processing in NetLogo

Programmers often need to remember several things at once. For example, we need to remember all of the notes played during some synthesizer performance. We can store multiple items in a list.

We begin by creating an empty list:

set score []

We can use the lput procedure (last-put) to add a new item to the end of a list. This creates a new list exactly the same as the original list, but with the new item added to the end:

set score lput note score

There are two ways to process each item in a list: recursion or iteration.

Iteration using the foreach command is easiest:

foreach score
[
   playNote ?
   wait 0.2 ; pause .2 secs so notes don't run together
]

Notice that each note in score will take turns being substituted for the "?" in the foreach block.

To use recursion, we must define a procedure that expects a score as input:

to playAscore [aScore]
  if aScore != []
  [
    playNote first aScore
    wait 0.2
    playAscore but-first aScore
  ]
end

If aScore is the empty list, nothing happens. Otherwise, the first note is played, we pause for .2 seconds, then playAscore calls on itself, but this time the input is aScore with the first note removed.

first someList = first elment in someList
but-first someList = newList like someList but with first element removed

Suppose the input is the beginning of Beethoven's Fifth. Here's a trace of the call to playAscore:

playAscore [64 64 64 60]
playNote 64
wait 0.2
playAscore [64 64 60]
playNote 64
wait 0.2
playAscore [64 60]
playNote 64
wait 0.2
playAscore [60]
playNote 60
wait 0.2
stop!