COS 126

Plucking a Guitar String
Programming Assignment


This assignment allows optional partnering. If you choose to do this, you must follow the pair programming guidelines. Anybody who gets ≤ 14 on the LFSR assignment needs to get permission from a lead preceptor for their partnership. Please note that writing code with a partner without following the pair programming instructions is a violation of the course collaboration policy. All writing of code, comments, the readme, and uploading to dropbox.cs must be done together from start to finish. If you come to office hours alone, you can get advice, but you may not change any code until both partners are together.

Write a program to simulate plucking a guitar string using the Karplus–Strong algorithm. This algorithm played a seminal role in the emergence of physically modeled sound synthesis (where a physical description of a musical instrument is used to synthesize sound electronically).

Digital audio. Before reading this assignment, review the material in the textbook on digital audio (pp. 147–151, 202–206).

Simulate the plucking of a guitar string. When a guitar string is plucked, the string vibrates and creates sound. The length of the string determines its fundamental frequency of vibration. We model a guitar string by sampling its displacement (a real number between –1/2 and +1/2) at N equally spaced points in time. The integer N equals the sampling rate (44,100 Hz) divided by the desired fundamental frequency, rounded up to the nearest integer.

Why it works? The two primary components that make the Karplus–Strong algorithm work are the ring buffer feedback mechanism and the averaging operation.

From a mathematical physics viewpoint, the Karplus–Strong algorithm approximately solves the 1D wave equation, which describes the transverse motion of the string as a function of time.

Ring buffer. Your first task is to create a data type to model the ring buffer. Write a class named RingBuffer that implements the following API:

public class RingBuffer {
    public         RingBuffer(int capacity)  //  creates an empty ring buffer with the specified capacity
    public     int size()                    //  returns the number of items currently in this ring buffer
    public boolean isEmpty()                 //  is this ring buffer empty (size equals zero)?
    public boolean isFull()                  //  is this ring buffer full (size equals capacity)?
    public    void enqueue(double x)         //  adds item x to the end of this ring buffer
    public  double dequeue()                 //  deletes and returns the item at the front of this ring buffer
    public  double peek()                    //  returns the item at the front of this ring buffer

    public static void main(String[] args)   //  unit tests this class
}
Since the ring buffer has a known maximum capacity, implement it using a double array of that length. For efficiency, use cyclic wrap-around: this ensures that each operation can be done in a constant amount of time. We recommend you maintain one integer instance variable first that stores the index of the least recently inserted item; maintain a second integer instance variable last that stores the index one beyond the most recently inserted item. To insert an item, put it at index last and increment last. To remove an item, take it from index first and increment first. When either index equals capacity, make it wrap-around by changing the index to 0.

Implement RingBuffer to throw a run-time exception if the client attempts to enqueue() into a full buffer or call dequeue() or peek() on an empty buffer.

Ring buffer

Your test client may contain the tests we give you, ones you write yourself, or a combination thereof.

Guitar string. Next, create a data type to model a vibrating guitar string. Write a class named GuitarString that implements the following API:

public class GuitarString {
    public         GuitarString(double frequency)  //  creates a guitar string of the specified frequency, using a sampling rate of 44,100
    public         GuitarString(double[] init)     //  creates a guitar string whose size and initial values are given by the specified array
    public    void pluck()                         //  plucks this guitar string (by replacing the buffer with white noise)
    public    void tic()                           //  advances the simulation one time step
    public  double sample()                        //  returns the current sample
    
    public static void main()                      //  unit tests this class
}

Again, your test client may contain the tests we give you, ones you write yourself, or a combination thereof.

Interactive guitar player. GuitarHeroLite.java is a sample GuitarString client that plays the guitar in real time, using the keyboard to input notes. When the user types the lowercase letter 'a' or 'c', the program plucks the corresponding string. Since the combined result of several sound waves is the superposition of the individual sound waves, we play the sum of all string samples.


  public class GuitarHeroLite {
      public static void main(String[] args) {

          // create two guitar strings, for concert A and C
          double CONCERT_A = 440.0;
          double CONCERT_C = CONCERT_A * Math.pow(2, 3.0/12.0); 
          GuitarString stringA = new GuitarString(CONCERT_A);
          GuitarString stringC = new GuitarString(CONCERT_C);

          while (true) {

              // check if the user has typed a key; if so, process it
              if (StdDraw.hasNextKeyTyped()) {
                  char key = StdDraw.nextKeyTyped();
                  if      (key == 'a') { stringA.pluck(); }
                  else if (key == 'c') { stringC.pluck(); }
              }

              // compute the superposition of samples
              double sample = stringA.sample() + stringC.sample();
  
              // play the sample on standard audio
              StdAudio.play(sample);
  
              // advance the simulation of each guitar string by one step
              stringA.tic();
              stringC.tic();
          }
       }
  }

Write a program GuitarHero that is similar to GuitarHeroLite, but supports a total of 37 notes on the chromatic scale from 110Hz to 880Hz. Use the following 37 keys to represent the keyboard, from lowest note to highest note:
String keyboard = "q2we4r5ty7u8i9op-[=zxdcfvgbnjmk,.;/' ";
This keyboard arrangement imitates a piano keyboard: The "white keys" are on the qwerty and zxcv rows and the "black keys" on the 12345 and asdf rows of the keyboard.

Piano keyboard


The ith character of the string keyboard corresponds to a frequency of 440 × 2(i − 24) / 12, so that the character 'q' is 110Hz, 'i' is 220Hz, 'v' is 440Hz, and ' ' is 880Hz. Don't even think of including 37 individual GuitarString variables or a 37-way if statement! Instead, create and initialize an array of 37 GuitarString objects and use keyboard.indexOf(key) to figure out which key was typed. Make sure your program does not crash if a key is pressed that does not correspond to one of the 37 possible notes.

Files for this assignment. The file guitar.zip contains GuitarHeroLite.java; optional API templates for RingBuffer.java and GuitarString; and this week's readme.txt template.

Submission.   Submit RingBuffer.java, GuitarString.java, GuitarHero.java, and a completed readme.txt. If your partner is submitting, you should submit a completed partner readme.txt.

In-class live concert (optional). Perform a musical piece on your synthetic guitar (or other instrument of your design), either as solo artist or in an ensemble. The concert will take place in the class meeting on Thursday, April 7. To request a peformance slot, please use this form.

Challenge for the bored. Modify the Karplus–Strong algorithm to synthesize a different instrument. Consider changing the excitation of the string (from white-noise to something more structured) or changing the averaging formula (from the average of the first two samples to a more complicated rule) or anything else you might imagine. See the checklist for some concrete ideas.

This assignment was developed by Andrew Appel, Jeff Bernstein, Maia Ginsburg, Ken Steiglitz, Ge Wang, and Kevin Wayne.
Copyright © 2005