< Back

Music Synthesis

Sound is simply a vibration of the air molecules around you. Pleasing sounds have some nice structure to the vibrations. These are continuous structures, but our computers are inherently digitial, discrete beings. For a computer to play music, we must create a discrete representation of the continuous wave. We do this by generating a series of samples of the wave. For this assignment you will create a program that can read a textual music notation file and synthesize a digital representation of a simple stringed instrument playing the music. The program will use the Karplus-Strong string synthesis algorithm to generate the samples.


Setup

Create a directory assignment8 under assignments in your cs170 directory. All code for the assignment should be stored in this directory.

  cd ~/cs170/assignments
  mkdir assignment8 
  cd assignment8 

Details

Create a program that creates a wave file of the music in a text file specified through the command line. The program should use the Karplus-Strong algorithm (described below) to generate the sound samples. The following is an example of a music file that contains four ascending notes played one beat apart:

  120 4
  -1 1.0
  0 1.0
  1 1.0
  2 1.0

The first line of the music file is the header that specifies information about the rest of the file. The header line consists of two space separated integers. The first integer is the number of beats per minute to play the rest of the file and the second integer is the total number of beats of the music in the file. All subsequent lines of the music file specify notes to be played. A note line consists of an integer and a real number separated with a space. The integer is a note to be played. The real number specifies the number of beats to wait until playing the next note. The note 0 is concert A, or 440Hz. A positive note with value n is n steps higher in pitch than concert A. A negative note with value -n is n steps lower in pitch than concert A.

Using the above encoding technique, the frequency of any note can be computed from the number of steps it is from concert A using the following equation:

\[note\_frequency = 440 \times 2 ^ {(n / 12)}\]

Where 440 is the frequency, in Hertz, of concert A, n is the number of steps that a note is away from concert A, and 12 is the number of steps in an octave.

The Karplus-Strong algorithm heuristically generates samples that model a simple stringed instrument, such as a harp, piano, or guitar. The algorithm begins by creating a linked list of randomly generated floating point numbers between -1 and 1. The length of the linked list determines the pitch of the string, and you can compute this value by dividing the sample rate (44,100 samples per second) by the frequency of the note to be played.

\[list\_length = \frac{SAMPLE\_RATE}{note\_frequency}\]

Once you have created this linked list, you have essentially created a linked list containing samples of the note to be played. To generate all of the samples of a note, you need to repeatedly remove the value contained at the head of the linked list. This is a sample of this note. In order to maintain the pitch of the note, you must add a new value to the end of the linked list. According to the Karplus-Strong algorithm, The value you add to the end of the linked list should be the "decayed" average of the produced sample (the value you just removed from the linked list) and the value that is now at the front of the linked list. To do this, compute the normal arithmetic mean of the two values, then multiply it by a floating point value less than one. .996 is a good decay value to start with.

\[ new\_list\_value = \frac{produced\_sample + new\_front\_value}{2} \times DECAY\]

The above process will create one sample of the note to be played. Each note must have several samples generated for it, so that each note has a duration. Since we know that there are 44,100 samples per second, we need to figure out how many seconds each note lasts. This can be computed by converting beats per minute (BPM) into seconds per beat.

\[ number\_of\_samples = \frac{60}{BPM} \times beat\_duration \times SAMPLE\_RATE\]

Note that the BPM and beat_duration are values read from the text file.

The file sound.py contains a class called Sound that can be used to create audio files. Every sample that is generated for a given text file should be added to the same sound object, using the add_sample method. Once all of the samples have been added, the samples can be written to a wave file using the write method.

Once you've tested your program on simple examples like the one above, you can test your program on the file test_song_1.


Pseudocode

For Monday, March 21st, you are required to provide pseudocode for the ENTIRE assignment. There are no class requirements for this assignment. However, I would strongly encourage you to think about creating at least one class.

You should also pseudocode the "main" driver program, and create a list of functions you need for this driver program to run. For any function you need to define, make sure you list the Pre and Post conditions!

In addition to all of that, there are at least 3 equations you need to figure out from the above reading:

  1. Frequency of a note,
  2. Length of the Linked List, and
  3. Number of samples to generate from the Linked List

Make sure you demonstrate that you understand these equations by defining them in your pseudocode. These will be a major part of your grade!


Submission

You are required to submit a tar file to http://inquire.roanoke.edu/. On inquire, there is a link for Assignment 8. You can create a tar file by issuing the following commands:

cd ~/cs170/assignments
tar czvf assignment8.tgz assignment8/

"Hacker" Prompt

  1. Chords: Music files can specify that multiple notes be played at once by specifying that the wait until the next note is 0 beats. The following is an example of a music file that plays a C major chord for four beats:

    120 4
    -2 0.0
    -5 0.0
    -9 4.0

    In order to play multiple notes simultaneously, you will need a linked list for each of the notes. Each should be initialized to the length that corresponds to the note's frequency. Then, sum the result of the samples generated from all of the linked lists Note that the sum may be outside of the range of -1 to 1. If this occurs, just clamp the value into the appropriate range. That is, if the sum is greater than 1, set the sum to 1, or if it is less than -1, set the sum to -1.

    Once you've tested your program on simple examples like the one above, you can test your program on the file test_song_2.

  2. Compose or Transpose: Write your own song in or transpose an existing song to the text music file format. Be prepared to play your composition in class.

  3. Melodies: So far, your program has only been able to generate sounds for one part of a song. However, a real song usually has several different parts being played simultaneously. In sheet music, these different parts are specified on different staff lines. For your program, however, they will be specified in separate files.

    With chords, we could use separate linked lists for each component of the note. However, that does not work for different parts of the song. This is solely because the notes can have different durations between the two parts. As such you need to run the entire Karplus-Strong algorithm on each part separately!

    After you have gone through and generated the samples necessary for both parts of the song, you need to create a new sound which contains the pair-wise average of each sample generated for the overall song.