Lab 7: Music Composition

 


Introduction

The objective of this lab is to explore JES's musical capabilities.



Working with playNote and playNotes

  1. The JES function playNote takes three arguments: the note that should be played, the duration of the note in milliseconds, and the volume of the note specified as a number from 0-127. Individual notes are expressed as integer values from the MIDI music encoding. You can find out which number corresponds to which musical note be referring to the table on the following website: Note names, MIDI numbers and frequencies . The note numbers are in the column on the left hand side of the keyboard graphic. The following command would play a C note lasting one second at full volume:
    >>> playNote(60, 1000, 127)
    
    Experiment with passing different values to the playNote function until you have a feel for how it works.

  2. In principle, we can play any melody we want by making a sequence of calls to playNote. In practice, it is cumbersome to make so many function calls. It would be more convenient to be able to make a single function call that would play a series of notes. The following function makes this possible:
    #playNotes - Play a series of musical notes at a given volume. 
    #notes - an array of integers: [note#1, dur#1, note#2, dur#2...] 
    #volume - a volume from 1-127
    def playNotes(notes, volume):
      for noteNum in range(0, len(notes), 2):
        playNote(notes[noteNum],notes[noteNum+1], volume)
    
    The first argument to this function, notes, is a list of numbers that represents subsequent notes that should be played, along with their durations in milliseconds. For example, the following function call will play three C notes in a row, the first for .5 seconds, the second for one second, and the third for 1.5 seconds:
    >>> playNotes([60, 500, 60, 1000, 60, 1500], 127)
    
    Notice the brackets that are used to denote the beginning and end of the list.

    Copy and paste the playNotes function into JES. Experiment with this function.

  3. We can now put together an entire song by making several calls to the playNotes function. The following function plays Frere Jacques:
    def brotherJohn(dur, vol):
      bar1 = [55, dur, 57, dur,  59, dur, 55, dur]
      bar2 = [59, dur, 60, dur,  62, dur * 2]
      bar3 = [62, dur/2, 64, dur/2, 62, dur/2, 60, dur/2, 59, dur, 55, dur]
      bar4 = [55, dur, 50,dur,  55, dur * 2]
      playNotes(bar1, vol)
      playNotes(bar1, vol)
      playNotes(bar2, vol)
      playNotes(bar2, vol)
      playNotes(bar3, vol)
      playNotes(bar3, vol)
      playNotes(bar4, vol)
      playNotes(bar4, vol)
    
    In this function dur is the duration of a quarter note in milliseconds, so dur * 2 represents the length of a half note, and dur / 2 represents an eighth note. Changing the value of dur changes the tempo of the song.

  4. After you have experimented with brotherJohn write your own song function. You can try to recreate a tune you know, or you can just use your imagination. It doesn't have to sound beautiful. Don't compose your song by stringing together one long series of notes. Most songs have repeated structure that can be handled as they are in brotherJohn or by using loops.

    Your song function needs to have a tempo parameter similar to dur in brotherJohn. You will make use of that parameter in the final exercise of the lab.


Songs You Can Save

One drawback of the playNote function is that it doesn't actually return a sound, it just plays the note. That means that it can't be used to make a sound that can be saved to a file. We've provided a set of functions that solve that problem. Copy the functions from this page into your .py file. There is a lot of code there, but you shouldn't panic. Many of these are functions we've already written in previous labs. Some of these are helper functions that we won't be calling directly.

  1. The main functions we are interested in are returnNote and returnNotes. These work just like the playNote and playNotes functions except instead of playing the notes they return them. The following function call will return the first bar of Frere Jacques:
    >>>sound = returnNotes([55, 200, 57, 200,  59, 200, 55, 200], 127)
    
    Try it out.

  2. When working with playNote, we could string one note after another just by calling playNote multiple times. When working with returnNote, we need a different way to string notes together. This could be done by making multiple calls to the appendSound function. An easier way is to use the appendSounds function. This function takes a list of sounds and sticks them together end to end to make one longer sound. The following function shows how appendSounds can be used:
    def returnBrotherJohn(dur, vol):
      bar1 = [55, dur, 57, dur,  59, dur, 55, dur]
      bar2 = [59, dur, 60, dur,  62, dur * 2]
      bar3 = [62, dur/2, 64, dur/2, 62, dur/2, 60, dur/2, 59, dur, 55, dur]
      bar4 = [55, dur, 50,dur,  55, dur * 2]
      n1 = returnNotes(bar1, vol)
      n2 = returnNotes(bar2, vol)
      n3 = returnNotes(bar3, vol)
      n4 = returnNotes(bar4, vol)
      s = appendSounds([n1, n1, n2, n2, n3, n3, n4, n4])
      return s
    
    Try out returnBrotherJohn and then make a version of your own song function that returns the song instead of playing it. You might also want to try out the returnSquareNotes function. This returns notes made with square waves instead of sine waves. They have more of a sci-fi sound.

There Was an Old Man Named Michael Finnigan

Some childrens' songs are designed to be sung repeatedly with a faster tempo each time through. Your goal for this exercise is to replicate that effect by generating multiple versions of your song inside a for loop. Each time through the loop you will increase the tempo, and append the resulting sound to the end of the previous verse. You may also wish to increase the volume each time through the loop.

  1. Write a function that takes two parameters: an initial tempo, and a number of repetitions. It should return the resulting song.
    Hint: It may be helpful to generate an initial sound object with a single sample, so that you have something to append your first verse to. For example, before your for loop you might have a line like the following:
    song = makeEmptySound(1)
    

  2. Test your function, and save a copy of the resulting song.

Submit your results

  1. Submit the file you created in this lab via .

Post your results

  1. Create a new Lab 7 web page to include links for your new sound files created by the shift function, and the echoes function from the previous mini-lab, as well as the song you composed in this lab. You should also include a link to the increasing tempo version of your song. For shift and echo, you should include the original sound and the modified sound. Since we do not have pictures to look at, it is even more important now that you add some text to describe what the viewer will be listening to. You might consider using a table to organize your sounds. Remember that to add sounds to your web page, you can just add them as you do links.

  2. Copy your web page and all the sound (.wav) files that should be on it to your personal web space on kzoo.edu. (You may want to review the instructions in Lab 1 if you've forgotten how to do this.)
  3. Include a link to your new Lab 7 web page from your course page. Bring up your course page in a web browser and test your link.

You may start on Programming Project #3: Audio Collage anytime after finishing this lab.