Project 2 Extra: Sound Synthesis

Pre-requisites: Completion of project 2.

This assignment is entirely optional. There is no autograder, no due date, and no points. In this project, you’ll:

  • Use your ArrayDeque61B as the engine of a physical simulation of a vibrating guitar string or other instruments.
  • Build an interactive musical instrument with your keyboard.
  • Build a step sequencer for creating music, also known as a “tone matrix”.

Credits: The GuitarString portion of this assignment is adapted from the Fall 2022 version of Project 1, which in turn descends from Kevin Wayne’s classic Guitar Hero assignment at Princeton. The Tone Matrix is inspired by Andre Michelle’s original ToneMatrix and by Keith Schwarz’s Nifty 2026 Tone Matrix assignment.

Setup

Follow the assignment workflow to get the proj2-extra skeleton and open it in IntelliJ.

Then, copy the ArrayDeque61B.java you wrote for Project 2 into this assignment’s src folder.

Part 1: GuitarString

In 1983, Kevin Karplus and Alex Strong published a simple algorithm for generating a realistic guitar sound. We can implement this algorithm in only a few lines of code by using an ArrayDeque.

The Karplus-Algorithm can be implemented with the following three steps:

  1. Initialize a Deque<Double> of length N with random values between -0.5 and 0.5.
  2. Take the front two doubles in the Deque and compute their average x. Remove the first double and place it at the back of the Deque.
  3. Play x. Go back to step 2 and repeated forever.

Or visually, if the Deque is as shown on the top, we’d remove the 0.2, combine it with the 0.4 to form 0.2988, add the 0.2988, and play the 0.2.

Implement the following from GuitarString.java: the constructor, pluck(), tic(), and sample(). When you’re done, the tests in GuitarStringTest.java should pass.

To hear the sound of your GuitarString.java you can run the main method in FirstNote.java which will create a single hard-coded GuitarString at 440 Hz and play 50,000 samples (at 44,100 samples per second).

Run FirstNote. If your GuitarString (and your ArrayDeque61B…) are correct, you’ll hear a bit more than one sound of a plucked guitar string.

Part 2: A Pentatonic Keyboard

In this part, you’ll build an interacctive keyboard that will allow the user to play notes from the pentatonic scale.

Implement PentatonicKeyboard.java. When you run this program, it should wait for keypresses. When you press a, s, d, f, or g, the effect should be as if you’d just plucked a guitar string that is a C4, D4, E4, G4, or A4 note, with frequencies given in the table below:

key note frequency (Hz)
a C4 261.63
s D4 293.66
d E4 329.63
f G4 392.00
g A4 440.00

The program should be able to play multiple notes at once. That is, if you press a, then s right afrer, you should be able to hear an overlapping C4 and D4 note.

The basic idea is that you’ll instantiate five GuitarString objects, one for each note. Then, inside the infinite loop you’ll:

  • Check StdDraw.hasNextKeyTyped().
  • If a key is typed, then you’ll use StdDraw.nextKeyTyped() to determine which string to pluck.
  • During each iteration of the simulation, you’ll play the sum of the five strong’s sample().

Don’t forget to tic each String.

Note: Feel free to add more keys! If you want a piano-like keyboard that uses the entire standard 12-TET scale, you can instead use something like these 35 characters q2we4r5ty7u8i9op-[=zxdcfvgbnjmk,.;/', where character $i$ gets frequency $440 \cdot 2^{(i - 24)/12}$ Hz. For example, q is key 0, 2 is key 1, etc.

Part 3: The Tone Matrix

Lastly, you might find it fun to build a grid sequencer. If you’d like to try out an existing grid sequencer, see https://tonematrix.audiotool.com/.

For this part, you’ll be building this GUI from scratch. That is, your program should be as follows:

  • A 16×16 grid of cells is shown, each on or off. Clicking a cell toggles it.
  • Each row is one note of the major pentatonic scale — one GuitarString per row, low notes at the bottom. Row $i$’s frequency is $130.81 \cdot 2^{s_i/12}$ Hz (starting from C3), where $s_i$ is the $i$th value of the sequence you get by taking the offsets ${0, 2, 4, 7, 9}$ and adding 12 for each octave: $0, 2, 4, 7, 9, 12, 14, 16, 19, 21, 24, \ldots$
  • A cursor sweeps across the columns from left to right forever, wrapping around. When it enters a column, every lit cell in that column plucks its row’s string — simultaneously.
  • Audio works exactly like Part 2, with 16 strings: every loop iteration plays the sum of all samples and tics all strings.

Because the cursor loops around, it should play the song forever. It should be possible to do the toggling as the music plays.

Your StdDraw primitives

Since this is an extra assignment, we’re not going to give much scaffolding on how the drawing and interaction should work. The design is part of the fun. While you COULD use an LLM to help you, we think the experience will be way more satisfying if you do everything yourself without letting an LLM do the work.

For this part, you get exactly two graphics tools (also in the skeleton’s comments):

Drawing a rectangle. After setting the coordinate system to match the grid (StdDraw.setXscale(0, 16), StdDraw.setYscale(0, 16)), the cell in column c, row r (row 0 at the bottom) is drawn with:

StdDraw.setPenColor(StdDraw.BOOK_LIGHT_BLUE); // or any color you like
StdDraw.filledRectangle(c + 0.5, r + 0.5, 0.45, 0.45);

Registering clicks. Inside your loop:

if (StdDraw.isMousePressed()) {
    int c = (int) StdDraw.mouseX();
    int r = (int) StdDraw.mouseY();
    // ...
}

Two important notes:

  • isMousePressed() returns true for the entire duration of a click, i.e. thousands of loop iterations. So you dont’ want to toggle every time isMousePressed() is true, otherwise you’ll get ultra rapid toggling. Instead, act only when the mouse becomes pressed. If you want the ability to “paint” the tone matrix by dragging your mouse across the window, you should also toggle “on” whenever the mouse is held down and you cross the edge of a rectangle.
  • Your loop is also your audio engine running at 44,100 samples per second. You should not redraw every iteration, otherwise the audio might crackle or not work at all (because your computer is spending too much time drawing and not enough time geneating sound samples). You can fix this by either only drawing every so many iterations, or alternately only when something on screen changes. Typical displays do not draw more than 120 times per second, so there’s definitely no need to draw more frequently than that.

Implement ToneMatrix.java. Everything beyond the two primitives above is your design. That includes how you store the grid, how your draw the grid, how structure the loop, pace the cursor, draw the cursor, etc. If you decide to do use a full 12 note scale instead of a pentatonic scale, that’s fine. Or if you build something microtonal, let me know, because microtonal music is great (I recommend 19-TET).

Even More: Other Matrices

Optionally, consider the ability to play multiple tone matrices at once, e.g. on mine you can press 1, 2, 3, 4, 5, 6, 7, 8, 9, or 0 to change between tone matrices. All matrices are playing but can be muted individually with the m key.

Even More: Other Instruments

Optionally try adding other instruments, either synthesized or stored as .wav files. Two neat synthesis tricks:

  • Harp: to get a harp sound, flip the sign of the new value before adding it to the buffer.
  • Drum: to get a drum sound, flip the sign with probability 0.5, and change the decay factor to 1.0 (no decay).

Show Off Your Creation

If you want to share, submit to the showcase form. We may feature favorites in lecture (the form asks whether that’s OK, and whether to credit you by name).

Submission

There is nothing to submit, but do commit so your work is saved.

LLM Disclosure: This spec was written with some help from Fable 5.1, but nearly all words are mine (Josh Hug). The starter code is primarily Fable 5.1 generated with direction from me.