COS 126 Plucking a Guitar String Programming Assignment Checklist

This assignment allows partnering. If you choose to work with a partner, you must follow the pair programming guidelines. 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 (including 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. 155–159, 211–215).

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.

• Plucking the string. The excitation of the string can contain energy at any frequency. We simulate the excitation with white noise: set each of the n displacements to a random real number between –1/2 and +1/2.

• The resulting vibrations. After the string is plucked, the string vibrates. The pluck causes a displacement that spreads wave-like over time. The Karplus–Strong algorithm simulates this vibration by maintaining a ring buffer of the n samples: the algorithm repeatedly deletes the first sample from the ring buffer and adds to the end of the ring buffer the average of the deleted sample and the first sample, scaled by an energy decay factor of 0.996. For example:

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

• The ring buffer feedback mechanism. The ring buffer models the medium (a string tied down at both ends) in which the energy travels back and forth. The length of the ring buffer determines the fundamental frequency of the resulting sound. Sonically, the feedback mechanism reinforces only the fundamental frequency and its harmonics (frequencies at integer multiples of the fundamental). The energy decay factor (0.996 in this case) models the slight dissipation in energy as the wave makes a round trip through the string.

• The averaging operation. The averaging operation serves as a gentle low-pass filter (which removes higher frequencies while allowing lower frequencies to pass). Because it is in the path of the feedback, this has the effect of gradually attenuating the higher harmonics while keeping the lower ones, which corresponds closely to the sound that a guitar string makes when plucked.
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 capacity()                //  returns the capacity of this ring buffer
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)   //  tests this class by directly calling all instance method
}
```
• Corner cases. Throw a RuntimeException with a custom message if the client calls either dequeue() or peek() when the ring buffer is empty or enqueue() when the ring buffer is full.

• Performance requirements. The constructor must take time at most proportional to the capacity. Every other operation must take constant time.

• Possible implementation. Since the ring buffer has a known maximum capacity, you can implement it using a double array of that length. For efficiency, use cyclic wrap-around: maintain one integer instance variable first that stores the index of the least recently inserted item and 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. To determine the size of the ring buffer (and whether it is full or empty), you will also need a third integer instance variable size that stores the number of items currently in the 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 {
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 length and initial values are given by the specified array
public     int length()                        //  returns the number of samples in the ring buffer
public    void pluck()                         //  plucks this guitar string (by replacing the ring buffer with white noise)
public    void tic()                           //  advances the Karplus-Strong simulation one time step
public  double sample()                        //  returns the current sample

public static void main(String[] args)         //  tests this class by directly calling both constructors and all instance methods
}
```

• Constructors. There are two ways to create a GuitarString object.

• The first constructor creates a RingBuffer of the desired capacity n (the sampling rate 44,100 divided by the frequency, rounded up to the nearest integer), and initializes it to represent a guitar string at rest by enqueuing n zeros.

• The second constructor creates a RingBuffer of capacity equal to the length n of the array, and initializes the contents of the ring buffer to the corresponding values in the array. In this assignment, this constructor's main purpose is to facilitate testing and debugging.

• Length. Return the number of samples n in the RingBuffer.

• Pluck. Replace the n items in the ring buffer with n random values between −0.5 and +0.5.

• Tic. Apply the Karplus–Strong update: delete the sample at the front of the ring buffer and add to the end of the ring buffer the average of the first two samples, multiplied by the energy decay factor.

• Sample. Return the value of the item at the front of the ring buffer.

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, it plays the sum of the two string samples.

 ``` public class GuitarHeroLite { public static void main(String[] args) { // create two guitar strings, for concert A and concert 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 110 Hz to 880 Hz. 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.

The ith character of the string keyboard corresponds to a frequency of 440 × 2(i − 24) / 12, so that the character 'q' is 110 Hz, 'i' is 220 Hz, 'v' is 440 Hz, and ' ' is 880 Hz. 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. If a keystroke does not correspond to one of the 37 possible notes, ignore it.

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 only a completed partner readme.txt.

Extra credit. Write a program AutoGuitar.java that will automatically play music using GuitarString objects. A few ground rules:

• We will execute your program with the following command:
`% java-introcs AutoGuitar`
Your program may not accept command-line arguments. Your program may not use standard input, standard output, or standard drawing. You may, however, submit an accompanying .txt file and read it using the In data type.

• The duration of your composition must be between 10 and 120 seconds. Since the sampling rate is 44,100 Hz, this translates to between 441,000 and 5,292,000 calls to StdAudio.play(). No infinite loops.

• Your program must behave consistently on different machines.

You may create chords, repetition, and phrase structure using loops, conditionals, arrays, and functions. Also, feel free to incorporate randomness. You may also create a new music instrument by modifying the Karplus–Strong algorithm; 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.

You may submit additional .java or .txt files to support the extra credit, (but do not modify RingBuffer.java, GuitarString.java, or GuitarHero.java). If you are working with a partner, you can do this part together or solo, but you must decide before you begin.

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