Problem 1: Mixing Sounds
Write a function
mix_sounds(snd1, snd2) that takes in two sound objects, and “mixes” them into a new sound that is returned. (Note: Neither of the original sounds should be modified in any way.) By “mixing”, we mean that the original sounds are played at the same time, so that each sound that is mixed is heard at the same time with the other sounds. For example, if we mix the three-note sound (“cde.wav”) and the sound of water bubbling (“water.wav”), we get this combination of notes and water (“cde-water.wav”). And if we mix the door slamming sound (“door.wav”) and the welcome sound (“welcome.wav”), a get this ominous door and welcome sound (“door-welcome.wav”). The length of the resultant sound should be the length of the longer of the two input sounds, otherwise part of one or more sounds will be cut off! In our notes-and-water example, the three notes were longer than the bubbling water, so the length of the mixed sound was the same as the notes sound.
Mixing two (or more!) sounds involves adding corresponding samples together. For example, if one sound has three samples with values: 2, 4, and 6; and another sound has four samples: 10, 11, 12, and 13; mixing them yields a sound of four samples: 12, 15, 18, and 13.
Note that if we try to mix two loud sounds together the resultant, mixed sound will sound distorted. This phenomenon is called clipping.
The reason we get clipping is that we use only 16 bits of data for each sample: once we try to store samples that fall outside the range representable by 16 bits, we cannot properly store them and hence get distortion.
mix_sounds function is worth <#points 9 pts>, broken down as follows.
- Correct mixing effect. <#points 5 pts>
- Mixed sound is same length as the longest input sound object. <#points 2 pts>
- Original sound not modified. <#points 1 pt>
- Correct docstring comment at beginning of function and appropriate comments in the function body. <#points 1 pt>
Problem 2: Song Generator
For this problem you will write a function
song_generator(notestring) that takes a “notestring” (to be described) and returns its representative sound object. The sound returned by your function can then be played like any sound loaded from a wav file. However, you will directly generate the returned sound; you are not to load any wav files at all for this part.
Let’s begin with the simplest notestrings, and incrementally describe all of the features you must support.
Feature 1: Notes and Pauses
Consider the notestring “CDEFGAB”. Passing this string to your
song_generator function should result in the sound object, which when played results in the sound of seven notes (listen to the “cdefgab.wav” file to hear this song). The sound is composed of the note C, followed by the note D, followed by the note E, and so on, until the note B.
The simplest notestrings, are therefore composed of the letters A, B, C, D, E, F, and G, corresponding to the seven notes of a scale.
The Note Class
In Lab04 you were introduced to the
Note class, which is part of the
sound module. This Note class allows you to create a note based on it’s name, its length (number of samples), and (optionally) its octave (i.e. pitch). You can use the “+” operator to combine two note objects together into a single one. The code below demonstrates this.
c_note <span class="pl-k">=</span> sound.Note(<span class="pl-s"><span class="pl-pds">"</span>C<span class="pl-pds">"</span></span>, <span class="pl-c1">22050</span>) <span class="pl-c"><span class="pl-c">#</span> C note that lasts for 22050 samples</span>_x000D_ d_note <span class="pl-k">=</span> sound.Note(<span class="pl-s"><span class="pl-pds">"</span>D<span class="pl-pds">"</span></span>, <span class="pl-c1">10000</span>, <span class="pl-v">octave</span><span class="pl-k">=</span><span class="pl-c1">2</span>) <span class="pl-c"><span class="pl-c">#</span> D note in octave 2 (10000 samples)</span>_x000D_ _x000D_ <span class="pl-c"><span class="pl-c">#</span> this will make a single sound object with c_note followed by d_note</span>_x000D_ cd_notes <span class="pl-k">=</span> c_note <span class="pl-k">+</span> d_note
As you are processing the notestring, you can use the accumulator pattern to keep adding more notes/sounds to a sound object that represents the whole song.
Note objects are like the sound objects we have been using all along (i.e. they support the same methods such as
Default Values for Notes
By default, you should use 14700 samples for notes that you create. Also be default, notes should use the “0” octave (i.e. which we’ll refer to as the default octave). All notes will, be default, have the same volume. We’ll refer to this volume as the “default” volume and give it a value of 1.0.
The length of the note, its octave, and its volume may all be modified using additional features of notestrings that we will describe in the following sections.
Pauses (i.e. Rests)
In addition to the characters “A”, “B”, “C”, “D”, “E”, “F” and “G”, you must support the character “P”. A P means “pause”: it indicates that you should add 14700 samples of silence to the song you are generating (rather than 14700 samples of a particular note). As an example, the string “CPCPPCPPPPPC” sounds like C notes with pauses. Four C’s are played, each one waiting more time to play than the one before it.
Feature 2: Note Lengths
The second feature of notestrings is evident in a string such as “2CD2E”. If we have a positive integer number n directly preceding a note or pause, it means that the note or pause should last n times its normal length. You may assume that the integer n will only be a single digit long (i.e. 0 through 9).
Here’s a sanity check. The string “CCCC” should sound like four distinct notes (listen to “cccc.wav”). The string “4C” should sound like one longer note (listen to “4C.wav”). Listening carefully, the first of these sounds contains four notes (you can hear little pauses between the notes), whereas the second contains one really long note with no gaps.
Here is a string for you to parse once your function supports lengths. It is the first ten notes of Canada’s national anthem: “4E3GG4C2P2D2E2F2G2A6D”.
Feature 3: Octaves
The third feature of notestrings is the ability to change octaves. A “
>” character means “increase the octave by 1” and a “
<” symbol means “decrease the octave by 1”. The new octave is active until changed by another “
<” or “
>” character. That is, all of the notes following an octave-changing sign will be in that new octave until the octave is changed again.
When we increase the octave, all of the notes still “sound the same” except they have a higher pitch. Similarly, when we decrease the octave, notes have a lower pitch. (Interestingly, the sound frequency doubles each time we increase the octave by 1. That is, in terms of frequencies, corresponding notes in successive octaves become more and more distant as the octaves increase, even though it sounds like a linear increase in pitch to us!… But you don’t have to care about this for the assignment.)
As an example, the string “
4E>4E>4E>4E<<<<4E>4E” sounds like E’s in different octaves (have a listen to “octaves.wav”). The note E is played in four increasing octaves; then played one octave below the default octave; then played again at the default octave. Here’s another example: “
4C>4C<2BGA2B>2C“; do you know this famous song?
Feature 4: Changing Volume
The fourth feature of notestrings is the ability to change volume. A “+” character means “increase the volume by 1” while a “-“” symbol means “decrease the volume by 1”. Like octave changes, the new volume setting is active until changed by another
- or – character later in the notestring. As previously mentioned, the default volume (level 0) is 1.0. A volume of level 1 causes notes to play at 120% of the default volume (i.e. 1.2); a volume of level 2 plays notes at 140% of the default volume (i.e. 1.4); and so on. A volume of level -1 causes notes to play at 80% of the default volume (i.e. 0.8); a volume of level -2 plays notes at 60% of the default volume (i.e. 0.6); and so on. (Do not worry about the situation where the volume level hits -6, i.e. -0.2)
As an example, the string “2C+2C+2C+2C++++2C——-2C” sounds like C’s at various volumes (listen to “volume.wav”). The double-length C note is played at the default volume, then at each of three increasing volume levels, then at volume 7, before being played again at default volume.
Note that the starter code includes a
scale_volume function that you may use to change the volume of a sound. This function does not modify the original sound object: instead of returns a new one with the volume adjusted. The following code snippet demonstrates.
e_note <span class="pl-k">=</span> sound.Note(<span class="pl-s"><span class="pl-pds">"</span>E<span class="pl-pds">"</span></span>, <span class="pl-c1">14700</span>)_x000D_ louder_e <span class="pl-k">=</span> scale_volume(e_note, <span class="pl-c1">1.2</span>) <span class="pl-c"><span class="pl-c">#</span> make an E note at volume level 1</span>
Feature 5 (Optional): Channels
The fifth feature of notestrings allows us to include multiple “channels” that are mixed together in the final sound. Note that this feature is optional: completing it will count as extra credit.
The “|” character in a notestring indicates that the current channel has ended, and a new channel is beginning. (Note that “|” is located on the same keyboard key as “”). For example, the string “8A|>4C” sounds like a sound with two channels channels. It plays two channels at the same time: the first plays an A while the second plays a C in octave 1. This second channel is shorter than the first, so it ends first; the first channel keep playing the harmonious chord. Any octave or volume changes must be restricted to the channel in which they occur; in particular, octave and volume commands have no effect on any channel descriptions that follow it in the string.
Another way to think of what the | does is to think in terms of “hands” playing a piano. The stuff before the first | is what your left hand is playing, the stuff after the | is what your right hand is playing. You may assume that there will be at most one | character in the notestring, i.e. you will have at most two channels in your song.
The following string is a larger example that collects most of the functionality discussed so far. It contains notes, notes with numbers preceding them (for increasing their length), octave changes, volume changes, and two channels played simultaneously:
The result is this nice little Twinkle Twinkle tune (the result should sound like “twinkle.wav”). The first channel contains the melody of the song (listen to “twinkle-melody.wav” to hear this alone), and the second channel contains the accompanying harmony (listen to “twinkle-harmony.wav” to hear this one alone). The harmony plays along with the melody to give the song a fuller sound.
The following is another notestring with channels to test out on: “
3C3C2CD3E2ED2EF3G>CCC<GGGEEECCC2GF2ED3C|9P3P3C3C2CD3E2ED2EF3G>CCC<GGGEEECCC2GF2ED3C” Do you know this song?
Feature 6 (Optional): Beats per Minute (BPM)
The final feature of our notestrings is that they may begin with the substring “[x]” (including the square brackets), where x is an integer indicating that the song should play at x beats per minute (BPM). Again, this is an optional, extra credit feature.
The BPM tells us how long a note, such as “C”, lasts. It also indirectly tells us how long “2C” and “3C” last”; “2C” lasts twice as long as “C”, and “3C” lasts three times as long as “C”.
Given a BPM value
x, we can take
(x / 60) to calculate the beats per second, y. If we then take
(22050 / y), we arrive at the number of samples constituting one beat (i.e. one note like “C”).
If a BPM is not explicitly specified, you should default to 180 BPM. In fact, this is what we’ve implicitly done all along, because we’ve used notes and pauses of 14700 samples. (Be sure you understand how 14700 samples and 180 BPM correspond for the default sample rate of 44100.)
The only place the BPM specifier can appear is at the very beginning of the entire notestring. In particular, it is not permitted to occur at the beginning of any channel’s description besides the first. If the first character of a notestring is not the “[” character, the BPM remains at the default of 180 for the song.
Here’s an example. The notestrings “CEG” and “CEG” are exactly the same C major chord at 180 BPM (have a listen to “chord-180.wav” to hear this). The notestring “CEG” is the same C major chord, but this time at 60 BPM (listen to “chord-60.wav”). In other words, the second is three times slower than the first.
Additional Information and Tips
- Refer to Lab04 for more examples of code using Notes. You’ll also fine the template you used for the turtle movement function useful for the
song_generatorfunction should use the accumulator pattern to slowly build up the song as you are processing the notestring. I recommend you initializing the accumulator variable to a silent sound with only 1 sample.
song_acc <span class="pl-k">=</span> sound.create_silent_sound(<span class="pl-c1">1</span>)
From here, you can add on to your accumulator using the “+” operator, which combines two song objects into one.
- Your notestrings should support lowercase and uppercase characters for the note names. Thus, “cccggg” is the same as “CCCGGG” or “cCcGGg”.
- Do not worry about malformed notestrings. For example, the string “C#” has a character that has no meaning in notestrings; the string “2C2” is malformed because there is no note after the second “2”. You do not have to consider such cases; your program will be tested with only valid notestrings.
- If you find that you are repeating code in more than one place, you should make a function out of the code and reuse it. For example, you’ll be extracting numbers from notestrings in more than one place; you should definitely not repeat that code. You should also use functions to break up your code into logically distinct components.
- For the optional Beats Per Minute feature, use the
mix_soundsfunction you wrote for Problem 1.
Like in Lab 04 did for turtle direction strings, the
comp110_psa3.py module contains a main function that asks you to enter a notestring before calling the
song_generator function and playing the resulting song.
Therefore, to test the function, you can either use the REPL (as you did for the
mix_sounds function) or you can run the following command while in your psa3 directory. Note that you should replace “python3” with “python” if you are using Windows.
Make sure you don’t put in any quotation marks (or apostrophes) in the notestring you enter.
The grader will be using this method for testing your
song_generator function so make sure it works for you also.
song_generator function is worth <#points 14 pts>, broken down as follows.
- Correctly implement notes and pauses (without any other features). In other words, characters A, B, C, D, E, F, G, and P should all work when put in a notestring. <#points 5 pts>
- Correctly implement note lengths, e.g. “2C” should produce a C note that is twice as long as the default note length. <#points 3 pts>
- Correctly implement octaves, i.e. the “
<” and “
>” characters should change the octave of subsequent notes in the notestring. <#points 2 pts>
- Correctly implement volumes, i.e. the “
+” and “
-” characters should change the octave of subsequent notes in the notestring. <#points 2 pts>
- Correct docstring comment at beginning of function and appropriate comments in the function body. <#points 2 pts>
You may also receive up to <#points 4 pts> of extra credit for completing the following features.
- Correctly implement channels, i.e. the “|” character should create a second channel that plays at the same time as the first. <#points 2 pts>
- Correctly implement beats per minute (BPM), i.e. a “[x]” at the beginning of the notestring should set the number of beats per minute.< #points 2 pts>