CPSC 170 Post Lab 8
Synthesizer
Due Before Class on Tuesday, March 20th

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. Computers represent audio as a series of discrete samples. The program will use the Karplus-Strong string synthesis algorithm to generate the samples.

Details

Create a program that plays the music in the text file specified as a command line argument. 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 double separated with a space. The integer is first a is a note to be played. The double is 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. Note that the relationship between the difference in pitch of two notes and frequency is not linear. The frequency of any note can be computed from the number of steps it is from concert A using the following equation:

440 * 2n / 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 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 file Synthesizer.java contains a method, play, that plays a specified array of samples, in bytes, at the specified number of samples per second. In order to play a single note you will need to synthesize an array of bytes using the KarplusStrongString class. Add a constructor to the KarplusStrongString class that initializes the buffer linked list to contain the specified number of random doubles between -0.5 and 0.5. To generate a byte array of samples for a single note, first compute the number of samples in the corresponding KarplusStrongString by dividing the number of samples per second by the frequency of the note to play. A good sample frequency to use 44,100 Hertz, or 44,100 samples per second. Then generate samples by repeatedly calling the sample method of the KarplusStringString class. A good decay for the sample method is 0.996. The number of times that the sample method is called is determined by the duration of the note as specified in the music file. Note that you will have to convert the number of beats to a number of samples by using the sample rate and the number of beats per second of the music file. Also note that the sample method will return a double in the range of -0.5 to 0.5, but the sample array is of bytes. You will need to scale and cast the sample before adding it to the sample array to play.

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 KarplusStrongString 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 sample method for all of the strings and convert the sum to a byte. Note that the sum may be outside of the range of -0.5 to 0.5. If this occurs, just clamp the value into the appropriate range. That is, if the sum is greater than 0.5, set the sum to 0.5, or if it is less than -0.5, set the sum to -0.5. If you create KarplusStrongString objects every time a note is encountered in the music file the program could have thousands of objects. Instead create one KarplusStrongString for every possible note between the maximum and minimum notes needed to play a music file. The maximum and minimum notes can be stored as constant instance variables. Each KarplusStrongString should be initialized to a buffer of length 0 and all strings should be summed to generate a sample. Note that KarplusStrongStrings with a buffer length of 0 will always return 0 when sampled, so they won't contribute when they are summed. Then, whenever a note is read from the file re-initialize the corresponding KarplusStrongString by calling the constructor with the appropriate buffer length.

Once you've tested your program on simple files, like the ones above, you can test your program on the smans file that Dumar created.


Submission: Submit your code as a zip file with your name as the zip file name on the course Inquire site.

Extra

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.

Visualize

If you are terribly disappointed that this assignment does not have any graphics, you can add some. Create a program that displays the waveform of the sample array that corresponds to a music file that is played. Each sample can be represented as a point in two dimensions where the sample value is the y value and the index in the array is the x value. Note both the x and y values will have to be scaled so that they fit the window dimensions. Create a window that is wide but not tall. The sample points can then be drawn to the window by either drawing a small oval for each, or drawing lines connecting neighboring samples.