The purpose of this assignment is to introduce you to programming with functions and to survey some of the key ingredients of sound synthesis. You will create a library of functions to synthesize sound for an electronic synthesizer.
Building blocks. Here are some of the sound waves most commonly used in electronic synthesizers:
Mathematically, the amplitude \(y(t)\) of a sine wave of frequency f at time t is given by:
\( y(t) \; = \; \sin ( 2 \pi \, f \, t)\)The sine wave produces a pure note, similar to a tuning fork or whistle. Here is how a 440 Hz sine wave (concert A) sounds:
Mathematically, the amplitude \(y(t)\) of a square wave of frequency f at time t
is given by
\( y(t) \; = \; 2 \, \left ( 2 \, \lfloor \,\, f \, t \, \rfloor \; - \; \lfloor \, 2 \, f \, t \rfloor \right ) \;+\; 1 \)Here \( \lfloor x \rfloor\) denotes the floor function of \(x\), which is the largest integer less than or equal to \(x\).
The square wave produces a hollow and woody sound. It can be used to model reed instruments and guitar distortions. Here is how a 440 Hz square wave sounds:
Mathematically, the amplitude \(y(t)\) of a sawtooth wave of frequency f at time t
is given by
\( y(t) \; = \; 2 \times (\,f \, t \,-\, \lfloor \, f \, t + \frac{1}{2} \, \rfloor) \)
The sawtooth wave produce a clear and bright sound. It can be used to model string and brass instruments. Here is how a 440 Hz sawtooth wave sounds:
\( y(t) \,\sim\, \textit{Uniform}(-1, +1)\)
It produces a “sound wave” with no discernible frequency.
White noise can be used to model inharmonic percussive instruments such as hi-hats, cymbals, and snare drums. Here is how white noise sounds:
Digital audio.
In digital audio, we represent a sound wave as an array of real numbers between –1 and +1,
with 44,100 samples per second.
That is, to represent each of the sound waves above,
sample the function 44,100 times per second at the values
\(t = \frac{0}{44100}, \frac{1}{44100}, \frac{2}{44100}, \ldots \).
If the audio is \(d\) seconds long, you will have a total of
\( n = \lceil 44100 \times d \rceil \) samples.
Here, \( \lceil x \rceil\) denotes the ceiling function of \(x\),
which is the smallest integer greater than or equal to \(x\).
For example, the following diagram illustrates a sine wave (whose frequency is 2048 Hz) for \(d = \frac{1}{512}\) seconds. We represent it digitally as an array of length \( \lceil 44100 \times \frac{1}{512} \rceil = 87\), with sample \(i\) taken at time \(t = \frac{i}{44100}\).
Manipulating sound waves.
Synthesizers produce richer sounds by combining sound waves in different ways.
Here are a few simple and important mechanisms.
This can be used to adjust the timbre (perceived sound quality of a musical note) by adding harmonics (sound wave whose frequency is an integer multiple of the original sound wave) or inharmonic partials (sound wave whose frequency is not an integer multiple of the original sound wave). It can also be used to create chords (multiple musical notes that are heard simultaneously).
This can produce brash metallic sounds when the two frequencies are in tune. It can also produce a tremolo effect (a rapid wavering between getting louder and softer) when one of the frequencies is in the subaudio range (below 20 Hz). Here is a tremolo effect that results from multiplying a sine wave of frequency 220 Hz with a sine wave of frequency 1 Hz:
An exponential fade is a sound envelope in which the volume decreases from 1 toward 0 according to an exponential function:
The decay parameter \(\lambda\) controls the rate at which it fades to zero: the volume decreases
by a factor of 2 every \( 1 \, / \, \lambda\) seconds.
Mathematically, the sound envelope \(A(t)\) of an exponential fade is given by:
\( A(t) \; = \; 2^{\, - \lambda \, t}\)For example, the following diagram illustrates applying an exponential fade to a sine wave of frequency 30 Hz with \(\lambda = 10\):
Here are the results from applying exponential fades to different sound waves. Each sound lasts 1 second and is repeated 10 times.
wave frequency \(\lambda\) sound sine 440 10 square 55 5 saw 220 10 white noise – 10 white noise – 20
Synth.java
that implements a library of synthesizer functions,
as described above.
To do so, organize your program according to the following public API:
Here is some more information about the required behavior:public class Synth { // Returns the number of samples for the given duration. public static int length(double duration) // Returns the sine function of given frequency at time t. public static double sine(double frequency, double t) // Returns the square function of given frequency at time t. public static double square(double frequency, double t) // Returns the sawtooth function of given frequency at time t. public static double saw(double frequency, double t) // Returns a sine wave of the specified frequency, amplitude, and duration. public static double[] sineWave(double frequency, double amplitude, double duration) // Returns the square wave of given frequency, amplitude, and duration. public static double[] squareWave(double frequency, double amplitude, double duration) // Returns a sawtooth wave of the specified frequency, amplitude, and duration. public static double[] sawWave(double frequency, double amplitude, double duration) // Returns white noise of the specified amplitude and duration. public static double[] whiteNoise(double amplitude, double duration) // Returns a new array that is the sum of a[] and b[]. public static double[] add(double[] a, double[] b) // Returns a new array that is the product of a[] and b[]. public static double[] multiply(double[] a, double[] b) // Returns the wave that results from applying an exponential fade // to a[] with decay rate lambda. public static double[] fade(double[] a, double lambda) // Tests each public method. public static void main(String[] args) }
Exercise modular design: for example, the sineWave()
function should
call the sine()
and length()
functions.
main()
method must call every public method, either directly
or indirectly to test that it works as expected. For example, you can call
squareWave(22050.0, 0.5, 0.00021)
and check that it
returns an array of length 10, alternating between 0.5
and
-0.5
.
frequency
, duration
, and t
arguments are always non-negative.
NaN
.
null
.
public
functions. You are free to add private
helper methods to make your code easier to read, maintain, and debug.
Here are some examples of the expected behavior:
~/Desktop/functions> javac-introcs Synth.java ~/Desktop/functions> jshell-introcs ~/Desktop/functions> /open Synth.java jshell> double[] y = Synth.sineWave(440, 0.25, 5.0) jshell> StdAudio.play(y) jshell> double[] y = Synth.squareWave(440, 0.25, 5.0) jshell> StdAudio.play(y) jshell> double[] y = Synth.sawWave(440, 0.25, 5.0) jshell> StdAudio.play(y) jshell> double[] y = Synth.whiteNoise(1.0, 5.0) jshell> StdAudio.play(y) jshell> StdAudio.play(Synth.fade(y, 10.0)) jshell> double[] wave1 = Synth.sineWave(220.0, 1.0, 10.0); jshell> double[] wave2 = Synth.sineWave( 1.0, 1.0, 10.0); jshell> double[] tremolo = Synth.multiply(wave1, wave2) jshell> StdAudio.play(tremolo)
Synth
and creating additional functions of your own.
Here are the requirements to earn full credit:
main()
method must create the sound and play it using StdAudio.play()
.
Synth
.
MySound.txt
. We will
test your program with the command:
~/Desktop/functions> javac-introcs MySound.java Synth.java ~/Desktop/functions> java-introcs MySound.java < MySound.txt
You are welcome to use one of the following concrete ideas or do something entirely on your own:
For example, to create a supersaw of frequency f, create 7 sawtooth waves using the following frequencies and amplitudes:
frequency amplitude f – 0.191 0.05 f – 0.109 0.05 f – 0.037 0.05 f 0.05 f + 0.031 0.05 f + 0.107 0.05 f + 0.181 0.05
Then, add them together. Using these parameters, a 150 Hz supersaw will produce the following sound:
There is nothing sacrosanct about these parameters, so feel free to experiment.
frequency amplitude f × 0.56 0.125 f × 0.92 0.125 f × 1.19 0.125 f × 1.71 0.125 f × 2 0.125 f × 2.74 0.125 f × 3 0.125 f × 3.76 0.125 f × 4.07 0.125
If you add them together and apply an exponential fade (with \(\lambda = 2\)), the result should sound similar to a bell. A 500 Hz bell should sound like the following:
The attack, decay, and release phases can use linear, exponential, or logarithmic functions.
Mathematically, the amplitude \(y(t)\) of an FM wave with carrier frequency \(f_c\), modulation frequency \(f_m\), and modulation depth \(D\) is given by:
\( y(t) \; = \; \sin \left ( 2 \pi \, f_c \, t \;+\; D \sin(2 \pi \, f_m \, t) \right) \)
The following diagram illustrates an FM wave with \(f_c = 17 \; \text{Hz}\), \(f_m = 4 \; \text{Hz}\), and \(D = 10\):
Here are the sounds that result from playing various FM waves (10 times each for 1 second), applying an exponential fade to each FM wave:
\(f_c\) \(f_m\) \(D\) \(\lambda\) sound 210 35 10 2 220 110 8 1 220 10 110 2 2.5 175 150 4
Submission.
Submit the Java files Synth.java
and MySound.java
.
Also submit a MySound.txt
file if your MySound.java
program reads from standard input.
Finally, submit a readme.txt
file and answer the questions.
Grading.
File | Points |
---|---|
Synth.java | 28 |
MySound.java | 7 |
readme.txt | 5 |
Total | 40 |