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.
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.
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.