SoundWave 22 points

Our main goal for this lab is to write a sound generator capable of playing any combination of notes, or tones, we desire. We can think of these tones as objects with their own attributes and methods. For example, a tone can have a duration attribute and a method for combining itself with other tones to create a song!

In this part of the lab, we’ll get started by creating a SoundWave class that will represent a sampled sound wave. Your SoundWave class should be written in a separate file, and it should follow the specifications below. Remember to refer back to the Warmup for details on how to generate and sample the sine waves needed to create your tones.

SoundWave Constructor

The constructor for your SoundWave class will begin with an __init__() function for instantiating new SoundWave objects. We’ll start by designing our constructor to accept four parameters specifying a single note:

  1. halftones - the number of halftones above or below middle C of the note
  2. duration - the length of the note in seconds
  3. amp - the amplitude of the note (i.e., it’s volume or loudness)
  4. sample_rate - the interval at which the sine wave is sampled

These four parameters define everything we need to generate the sine wave for a given half tone. Recall, however, that the sine wave equation requires a frequency. We can compute the frequency as outlined in the Warmup using:

freq = 220*(2**((halftones+3)/12))

Now that the sine wave parameters have been determined, we need to sample the waveform and store those sampled values. Your constructor should create an instance variable called self.samples, and define self.samples to be the list of sampled values. You should populate this list such that it has a length of duration * sample_rate (truncated to an integer), where entry i in this list is created as follows:

s = amp * math.sin(2 * math.pi * freq * i/sample_rate)

Say you’ve computed a new sample s that you plan on appending to your list samples. A natural way to do this is with the instruction:


Default Values

For the purposes of this lab, we’ll always be using a sample rate of 44100, or 44.1 kHz (this value is dictated by the WAV audio format we’ll be using to save our music). To make our programs cleaner, we’ll want our SoundWave constructor to assign default values when parameters are left unspecified. The default values for halftones, duration, amp, and sample_rate should be 0 (middle C), 0.0 (zero seconds), 1.0 (max volume), and 44100, respectively.

Setting Default Values

We can set the default value of a function parameter by doing an assignment within the function definition. For example,

def my_function(param1, param2=10):
    # do something

sets a default value of 10 to param2 and no default value for param1. By setting the default value of param2, we can call the function with


and Python will automatically set param2 to 10. Note that default values work the same for methods.

By having these default values (and the parameters in this order), we allow users of this object to invoke the constructor as:

note = soundwave.SoundWave(6,3,.5)

to get an F-sharp with length 3 seconds at volume 0.5,

note = soundwave.SoundWave(2,1)

to generate a D with length 1 second at volume 1.0, or

note = soundwave.SoundWave()

to generate an empty SoundWave object.

Checking Your Samples

Before moving on, you should test out your SoundWave class, even though you can’t play the full tone just yet!

Below your class definition, write a function called test_samples() which, when called, creates a SoundWave instance s and prints out the first 10 samples of that instance (e.g., s.samples[0:10]). You should look at the output and check that the values seem to be changing in a sensical way as you adjust the parameters passed to the constructor (i.e., getting bigger but less than amp). Your function should also print out the total number of samples in s to confirm you are generating what you intended (i.e., duration * sample_rate, truncated to an int).