Audio Synth

In this homework, you will build an audio synthesizer that plays and visualizes music.
This assignment is a demonstration of a well-decomposed object model: it has many small classes, each of which has clear, specific, and limited role to play in the overall program. Seeing a structure like this will help you think about your course project.
This homework serves several learning goals:
-
First and most importantly, the results of this assignment are fun (especially if you do the bonus part about note highlighting)!
-
This assignment gives you a little more practice with arrays and interfaces.
-
You’ll practice working with many small, focused classes in a heavily decomposed architecture. This whole assignment could be one unwieldy giant method, but instead you will implement it using many small pieces. (There are 14 classes and interfaces in the full solution, not counting tests.)
Sometimes the “one giant method” approach is in fact the best one: for getting a quick answer, perhaps, or prototyping. However, that kind of code hinders readability, maintainability, and future evolution. At the other end of the spectrum is the kind of careful decomposition you’ll see this assignment. This approach is typical of software projects that (1) have multiple collaborators and/or (2) are planning for change and growth. It is the approach you should bring to your course project.
How does this assignment fit into the big picture of COMP 127?
- In earlier assignments, we gave you small object models.
- In Breakout, you asked you to figure out a small object model on your own.
- In this assignment, we give you a somewhat larger object model.
- For your final project, you will have to figure out a larger object model on your own.
Terminology and starting point
We have given you some starter code that provides the following building blocks for your music synthesizer.
The audio package
In the audiosynth.audio package, you will find these three types, plus some supporting code:
-
Signal: a sound wave, represented as a function that maps time to wave amplitude. -
Waveform: the shape of a sound wave, independent of amplitude and frequency. AWaveformis like a musical instrument: it can produce many different notes, but they all share a particular sound. You can ask aWaveformto generate aSignalat a specific frequency. -
AudioBuffer: an arbitrary sound that is made of many individual amplitude measurements, all evenly spaced in time. WhileSignalandWaveformare both continuous, anAudioBufferis discrete. You can play anAudioBufferto your computer’s speakers.
Here is a more detailed explanation of each of these concepts:
Signal
A “signal” is any quantity that varies over time. In the case of this program, the quantity that varies is the amplitude of a wave, and the signal represents sound.
A signal can be any specific sound. If the sound is a wave, then it would be a wave with a specific amplitude and frequency. Each of these images depicts a different signal:
See the Signal interface for more information.
This simple interface is already complete; you won’t need to change it.
Waveform
A “waveform” is the general shape of a wave, independent of frequency, amplitude, or phase. For example:
- “sine wave” is a waveform, while
- “sine wave with amplitude 2 and wavelength 3” is a signal.
In this project, we give you a Waveform interface with several different implementations. Each class that implements Waveform can generate signals for different wavelengths (and thus for different musical pitches):
These different Waveform implementations all compute the signal using a formula in a lambda expression, much like the graphing calculator activity.
These implementations are all already complete and correct. Note how small a class can be and still be useful!
AudioBuffer
In computer science, the word “buffer” means “place where we can temporarily store a bunch of data while we are working with it.” In this case, the buffer stores sound data.
Unlike the waveforms, which the code computes using mathematical formulas, an AudioBuffer contains sampled audio: arbitrary sound data expressed as a sequence of specific numbers, in this case an array of floats. Each number in the array represents the amplitude at one moment in time, and the different array indices represent time steps of 1/48000th of a second. This is called discrete sampling.
This kind of sampled audio is how modern computers’ sound chips represent sound data. To play a signal as sound through your computer’s speakers, you must convert it to sample data:
The code in AudioBuffer is all correct, and the class is almost complete; you will add just one method to it.
The model package
In the audiosynth.audio package, you will find these three classes:
-
Note: a single note, starting at a specific time and lasting for a specific duration. -
Song: many notes gathered together to create a piece of music. -
SongReader: a utility to read a song from a data file.
Note that a Song object is like sheet music: it describes the notes, but it is not actually sound that the computer can play…at least not yet! Part of your job will be to render a Song to an AudioBuffer.
However, the point of a presentation-agnostic data model like Song like this is that you can use the same data in many ways! In addition to using this model to create audio, you will also use it to create visuals.
Your tasks
For this assignment, please work through the following list of guided tasks:
- Diagram: Create a map for yourself to navigate the existing classes.
- Mixing: Add the ability to combine many distinct signals into one piece of audio.
-
Song audio rendering: Create code to render your song model to an
AudioBufferso you can hear it. - Visualization: Create a class to visualize songs on the screen.
- Animation: Make the music scroll as it plays.
- Note highlighting : Highlight the currently playing notes. A little tricky, but it looks awesome!
Each task gives you the outline of a class structure, some hints, and some tests. Do not neglect the tests! Be sure to run the tests each step gives you and make sure they pass before moving on to the next step.
Note that the main point of this assignment is to give you the feeling of working with and building out an object model with many moving parts. The point is not to confuse you with tricky directions! If you look at any of the of the classes and methods described in the steps above and think to yourself, “What does that mean?!,” please ask for clarification right away.
Enjoy! 🎵