COS 126

Plucking a Guitar String
Programming Assignment


Pair programming. On this assignment, you are encouraged (not required) to work with a partner provided you practice pair programming according to the guidelines described at the top of the checklist. Before pair programming, you must read the article All I really need to know about pair programming I learned in kindergarten. You should choose a partner of similar ability. You may choose a partner from any current COS126 precept. Partners are required to do all coding together, including at office hours.

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 next 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
-----------------------------------------------------------------------------------------
        RingBuffer(int capacity)  // create an empty ring buffer, with given max capacity
    int size()                    // return number of items currently in the buffer
boolean isEmpty()                 // is the buffer empty (size equals zero)?
boolean isFull()                  // is the buffer full  (size equals capacity)?
   void enqueue(double x)         // add item x to the end
 double dequeue()                 // delete and return item from the front
 double peek()                    // return (but do not delete) item from the front
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()/peek() on an empty buffer.

Ring buffer

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
------------------------------------------------------------------------------------------------------------------------
       GuitarString(double frequency)  // create a guitar string of the given frequency, using a sampling rate of 44,100
       GuitarString(double[] init)     // create a guitar string whose size and initial values are given by the array
  void pluck()                         // pluck the guitar string by replacing the buffer with white noise
  void tic()                           // advance the simulation one time step
double sample()                        // return the current sample
   int time()                          // return number of times tic was called so far

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 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 your 37 notes.

Provided files.   In the Possible Progress Steps on the checklist we provide optional templates for RingBuffer and GuitarString. Here is a copy of GuitarHeroLite.java which you will extend to create GuitarHero. Finally, we provide a readme.txt as well as an abbreviated partner readme.txt file — in each pair, one partner should submit the .java files and the long readme, and the other should submit only a short readme.

Submission.   Submit RingBuffer.java, GuitarString.java, GuitarHero.java, and a completed readme.txt. (Or, if your partner is submitting the java files, just submit the completed partner readme.txt.) The names, logins, and precept numbers of both partners MUST appear at the top of every submitted file.

Extra credit. There are two optional extra credit parts. You can do either one if you want or both. If working with a partner, you can do them together or alone — but decide before you start it.

Extra credit 1. Write a program GuitarHeroVisualizer.java (by modifying GuitarHero.java) that plots the sound wave in real-time, as the user is playing the keyboard guitar. The output should look something like this, but change over time.

  Sampling from Karplus-Strong

Extra credit 2. Write a program AutoGuitar.java that will automatically play a song with GuitarStrings. The song should be between 10 seconds and 2 minutes in length. It will be run with

% java AutoGuitar
It will make calls to StdAudio.play() but it must not make any other calls to the Std* libraries. In particular, make sure there are no calls to StdDraw, and that it works with no user input. Your main() must terminate; it cannot loop infinitely. To get the timing to work, either use the Stopwatch class, or use the fact that 1/44100 of a second elapses between calls to StdAudio.play(). This will make it have consistent timing on different machines. Use arrays, loops, conditionals, and randomization as you see fit. If you are interested, you can add chords, repetition, or different instruments (see below). You may submit additional classes, but don't modify your original classes (such as GuitarString).

Challenge for the bored. Here are suggestions on other effects and instruments to synthesize. Some come from the paper of Karplus and Strong.

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