When a harp string is plucked, the string vibrates and creates sound. The length of the string determines its fundamental frequency of vibration. We model a harp string by sampling its displacement (a real number between -1/2 and +1/2) at N equally spaced points (in time), where N equals the sampling rate (44,100) divided by the fundamental frequency (rounding the quotient up to the nearest integer).
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
public class RingBuffer { private double[] rb; // items in the bufer private int first; // rb[first] = first item in the buffer private int last; // rb[last-1] = last item in the buffer private int size; // current number of items in the buffer }
See Vector.java for some other examples and p. 446 in the book for a slighty expanded explanation of exceptions..if (isEmpty()) throw new RuntimeException("The ring buffer is empty.");
% java RingBuffer 10 Size after wrap-around is 10 55.0 % java RingBuffer 100 Size after wrap-around is 100 5050.0
public class HarpString ------------------------------------------------------------------------------------------------------------------------ HarpString(double frequency) // create a harp string of the given frequency, using a sampling rate of 44,100 HarpString(double[] init) // create a harp string whose size and initial values are given by the array void pluck() // set the buffer to white noise void tic() // advance the simulation one time step double sample() // return the current sample int time() // return number of tics
% java HarpString 25 0 0.2000 1 0.4000 2 0.5000 3 0.3000 4 -0.2000 5 0.4000 6 0.3000 7 0.0000 8 -0.1000 9 -0.3000 10 -0.2991 11 -0.4487 12 -0.3988 13 -0.0498 14 -0.0997 15 -0.3490 16 -0.1496 17 0.0499 18 0.1994 19 0.2987 20 0.3728 21 0.4225 22 0.2237 23 0.0746 24 0.2237
String keyboard = "q2we4r5ty7u8i9op-[=zxdcfvgbnjmk,.;/' ";
The MiniHarp.java Example Program MiniHarp.java is a two-string version of Harp.java that you can use to test your RingBuffer.java, HarpString.java, and as a starting point for Harp.java. Type the lowercase letters 'a' or 'c' to pluck its two strings. Since the combined result of several sound waves is the superposition of the individual sound waves, we play the sum of all string samples. After you've completed RingBuffer.java and HarpString.java, run MiniHarp in order to check to see if everything works properly. You should hear two different pitches corresponding to A and C everytime you press the key.public class MiniHarp { public static void main(String[] args) { // create two harp strings, for concert A and C double A = 440.0; double C = A * Math.pow(2, 3.0/12.0); HarpString stringA = new HarpString(A); HarpString stringC = new HarpString(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 combined sound of all notes double sample = stringA.sample() + stringC.sample(); // play the sample on standard audio StdAudio.play(sample); // advance the simulation of each harp string by one step stringA.tic(); stringC.tic(); } } }
- Note: In order to enter keystrokes in MiniHarp, make sure to first click on the standard draw window before typing the keystrokes. If you are having trouble running MiniHarp, refer to the FAQ tab.
- Also, note how this code above uses an infinite loop to continually receive keystrokes from the user and generate new music samples. This infinite loop ends when the program terminates.
Debugging Harp.java can be a little tricky. The probably is that you can only hear sound if you are sending new samples to your speaker fast enough. "Fast enough" means 44,100 times per second. System.out.println() is surprisingly slow—it almost always takes longer that 1/44,100th of a second to print something. The result is that you will hear only clicks or silence if you have any print statements to help you debug. Your best bet is to thoroughly test and debug RingBuffer.java, then thoroughly test and debug HarpString.java. Next, comment out, but do not delete any print statements you add for debugging and test MiniHarp. You may need to uncomment these statements and do further testing later (by adding new test cases to RingBuffer's and/or HarpString's main()). Do not start on Harp.java until you are certain everything else, including MiniHarp, works.
nn//SS/ ..,,mmn //..,,m //..,,m nn//SS/ ..,,mmn (S = space)
w q q 8 u 7 y o p p i p z v b z p b n z p n d [ i d z p i p z p i u i i
Do not redraw the wave on every sample because StdDraw will not be able to keep up. Instead, draw the wave of the last n samples every n timesteps for an appropriate value of n. Experiment with different values of n to find one that you think looks good and draws smoothly. There is more than one way to handle the drawing — there is not a "right" way to do this. You may also do a different visualization, as long as it is tied to the audio samples.
Alexander Strong suggests a few simple variants you can try: