Starter code: infiniharp-student.zip
Collaboration: not permitted
Overview
In this assignment, you will write a program to simulate plucking a harp string entirely algorithmically, using the Karplus-Strong algorithm. This approach is a form of physically modeled sound synthesis, where a physical description of a musical instrument is used to synthesize sound electronically.
Implementing this algorithm efficiently requires the use of an array-based circular queue, so you will build one of those for this assignment, too.
Like other recent assignments, we’ve not provided a large set of unit tests to help with automated testing or your design process.
The Gradescope autograder includes some tests of your program’s overall correctness. We have left the names of these tests visible as a hint to what they’re testing, but you may need to write some tests of your own to complete this assignment.
Goals
- Implement an array-based circular queue.
- Implement a Karplus-Strong-based simulation of a plucked string instrument.
- Practice writing unit tests.
- Test code using unit tests.
Downloading and importing the starter code
As in previous assignments, download and save (but do not decompress) the provided archive file containing the starter code. Then import it into Eclipse in the same way; you should end up with a infiniharp-student
project in the “Project Explorer”.
Digital Audio
According to Wikipedia, “Digital audio is sound that has been recorded in, or converted into, digital form. In digital audio, the sound wave of the audio signal is encoded as numerical samples in continuous sequence.” You may want to glance at that Wikipedia page for some background.
In short, though, we can represent a sound as a sequence of values. CDs, for example, use 16-bit values that are sampled 44,100 times per second. The sound card in your computer, if provided with a sequence of values in the correct range, will play sounds corresponding the sound wave those values describe. The 44,100 rate is pretty widely supported, and it’s what the provided code will use.
If you want to read about this stuff in more detail, including some neat interactive Javascript demos, read “Karplus-Strong String Synthesis”.
Modeling a harp string
Suppose you want to simulate the plucking of a stringed instrument, such as a harp. When it’s plucked, it vibrates and creates a sound. The length and tension of the string determines its fundamental frequency; typically musicians adjust the tension of strings to tune them. A well-known musical frequency is the “concert A” at 440 Hz, that is, a vibration of 440 times per second.
We will model a vibrating string by sampling its displacement (a number between -1⁄2 and +1⁄2) at N equally spaced points in time. How will we compute N? It is the desired sampling rate (in our case, 44,100) divided by the desired fundamental frequency of the string, rounded up to the nearest integer.
Plucking the string
When you pluck a string, you impart energy at a mixture of different frequencies. In physics, we call the imparting of this energy the excitation the string. To simulate this excitation, we’ll set each of the N displacements to a random number between -1⁄2 and +1⁄2.
You’ll need a random number generator to do this. In Java, you can create a new generator like so:
Random r = new Random(0);
Then, you can ask it for a random number using r.nextDouble()
, which gives you a number between 0.0 and 1.0 – you’ll have to adjust it, of course.
Modeling the vibrations
After the string is plucked, the string vibrates. The pluck causes a displacement which spreads in a wave-like way over time. The Karplus-Strong algorithm simulates this vibration by maintaining a queue of the N samples: the algorithm repeatedly:
- stores the first sample
- removes it from the queue
- adds to the queue the average of the stored first sample and the new first sample, scaled by a decay factor
- returns the stored first sample
In this assignment, we’ll use a decay factor of -0.994, which gives a nice harp-like sound.
Why and how
The two primary components that make the Karplus-Strong algorithm work efficiently are an array-based circular queue, and the averaging operation. (Efficiency was very important when this algorithm was developed in the early 1980s – home PCs at the time ran at about 5 MHz, had about 128 KB of RAM, and cost about $4,000 in 2019-inflation-adjusted dollars!)
The circular queue feedback mechanism. The queue models the medium (a string tied down at both ends) in which the energy travels back and forth. The length of the queue determines the fundamental frequency of the resulting sound as described above. Sonically, the feedback mechanism reinforces only the fundamental frequency and its harmonics (frequencies at integer multiples of the fundamental). The energy decay factor (0.994 in this case) models the slight dissipation in energy as the wave makes a roundtrip through the string.
The averaging operation. The averaging operation serves as a gentle low-pass filter. It removes higher frequencies while allowing lower frequencies to pass, hence the name. 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 with how a plucked string sounds.
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.
CircularDoublesQueue
Your first task is to implement the queue. You will implement an array-based circular queue. For efficiency, the array should be an array of primitive double
s, and not a generic array.
Your implementation of CircularDoublesQueue
should more-or-less mirror what we did in class.
HarpString
Next, implement the HarpString
to model a vibrating string. Here are some implementation notes on some of the methods:
- constructors: There are two ways to create a HarpString object.
The first constructor should create aCircularDoublesQueue
of the desired capacity N (sampling rate 44,100 divided by frequency, rounded up to the nearest integer), and initialize it to represent a string at rest by enqueuing N zeros.
The second constructor should create aCircularDoublesQueue
of capacity equal to the size of the array, and initialize the contents of the queue to the values in the array. The purpose of this constructor is to aid debugging and grading. pluck()
: Replace the N items in the queue with N random values between -0.5 and +0.5.tic()
: As described above, apply the Karplus-Strong update.sample()
: Return the value of the item at the front of the queue.time()
: Return the total number of timestic()
was called.
HarpPlayer
If you want to listen to your results, there’s an interactive player already written for you in HarpPlayer
. WARNING: Start with the volume low! If there’s a mistake in your Karplus-Strong update code, you might get loud static instead of the sound of a string being plucked.
If you open it up, you’ll see it uses the following definition:
String keyboard = "q2we4r5ty7u8i9op-[=zxdcfvgbnjmk,.;/' ";
This hardcodes the musical steps on a piano keyboard to your computer’s keyboard.
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 i-th character of the string corresponds to a frequency of 440 * 1.05956^(i - 24), so that 'q'
is approximately 110Hz, 'i'
is close to 220Hz, 'v'
is close to 440Hz, and ' '
is close to 880Hz.
Once you have HarpString
working, you can use HarpPlayer
to create some sweet sweet early-80s-style synth music!
Submitting the assignment
When you have completed the changes to your code, you should export an archive file containing the src/
directory from your Java project. To do this, follow the same steps as from Assignment 01 to produce a .zip
file, and upload it to Gradescope.
Remember, you can resubmit the assignment as many times as you want, until the deadline. If it turns out you missed something and your code doesn’t pass 100% of the tests, you can keep working until it does.