The purpose of this lab is to:
As requested in the prelab, please use headphones while working on this lab.
Download the file mozart.tar into your cs150 folder and unpack it. Do NOT save it in your lab09 folder!
# save the file mozart.tar into ~/cs150 % tar xvf mozart.tar % ls
You should now have the directories Mfiles and Tfiles, each containing a slew of .wav files.
Now create a folder called lab09 inside your cs150 folder. Switch into this directory. This is where you'll put all the files you create for this lab. You should also save the following files here:
Do NOT move the Tfiles or Mfiles folders, or their contents. We do not want you submitting a bajillion .wav files when you run handin. The lab machines will break. More than usual.
Now let's check that you have what it takes to play sound files. Plug in your headphones, and use the file browser to navigate your way to the lab09 directory. If you've untarred the files properly, you should have some music files (with the .wav extension) in the Mfiles and Tfiles directory within your cs150 directory. Right-click on one of these files and open it with Audacity.
If you don't hear anything, then your sound may be either muted or turned down. First try increasing the volume via the keyboard volume control. If this fails, you can adjust the sound by typing
This will start a program that adjusts volume. You can adjust the volume up or down with the up/down arrows; toggle mute with the M key, and go from slider to slider with the left/right arrows. Press the escape key to quit the program.
soundwave.py: 22 points, individual.
First, let's talk about music representation in .wav files. Each music note is just a sine wave (for us, anyway). This wave is defined by its frequency (pitch), its amplitude (volume), and its length (duration). Middle C (the centre key on a piano) has frequency around 523, and a safe volume is an amplitude under 1. We can define all notes relative to this one: to go up a semitone, you just multiply your frequency by the 12th root of two (1.05946).
For example, C# (C sharp) has the value 523*(2**(1/12))=554, and B has the value 523/(2**(1/12))=493. There are 12 semitones in an octave (count 'em up on a piano if you like), so to go up an octave, you double your frequency (e.g. high C has the value 1046).
You can refer back to the prelab for all the details. For now all you really need to know is that at second t, the y-value of the sound wave is amplitude*sin( 2*PI*frequency*t ).
Here is a table listing the frequencies of the middle octave from A to A.
Since we're going to be creating music using sound waves, it seems like a good idea to have a Soundwave class that will represent a sampled sound wave. You should create this class in the file soundwave.py using the following guidelines.
amp*math.sin(2*math.pi*freq*t/samplerate)where the value of freq is
Observe that the note A440 is 3 semitones below middle C, and indeed if halftones has a value of -3, freq evaluates to 440. Once we've computed the frequency of the note, we're just applying our earlier formula for the sound wave at time t, except that we divide t by samplerate because we're taking samplerate samples for each second.
samples = samples + [v]Indeed, this does what we want. Unfortunately, it's actually quite slow. Why? Let's think about what the plus operator is doing. samples + [v] is building a new list whose first part is samples and whose last part is the singleton list [v]. But this means the operation has to copy every element of samples into this new list. This is useful if we want to maintain a copy of the original list for future use. But we're immediately assigning that list to the variable samples again! If we're halfway through creating a 1-second soundwave, then samples contains 22050 values, so we're doing 22050 copies just to add one more value to the list. Our computers can do 22050 copies pretty fast, but that's just adding one value -- we still have another 22050 values to add! Doing 22050 x 22050 copies is just too slow.
samples.append(v)This function modifies samples by adding a new entry (v) to the end, and takes roughly a single operation to do so. This isn't what we'd want if we needed to keep the old version of samples around for some reason, but since we don't, we'll use the much-faster append function.
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.
Add a function called play to your soundwave class. This function should take no parameters (except for self). The function play should simply pass samples to the audio.play() function.
You should now be able to run the provided file middlec.py. When you run this program, you should hear a single note (middle C) for approximately 2 seconds. If it works, great, continue onward! Otherwise you'll need to track down some bugs.
snippet = soundwave.Soundwave("imonaboat.wav")and have this set the samples, maxvol, and length appropriately. To suppose this, we'll need to do some type checking on the parameters. In particular, if the type of the first parameter passed to the constructor is a string (str), we want to use audio.read_file function on that string to generate our samples list. From that, we should be able to determine the length and maxvol. Otherwise, we want to do what we've been doing.
scale.py: 8 points, individual.
A scale is a sequence of notes, defined by the intervals between them. For example, the major scale is defined by the 7 intervals (and hence 8 notes) (2,2,1,2,2,2,1), that is, there are 2 semitones between the first and second notes, between the second and third notes, but a single semitone between the third and fourth notes, and so on. The C major scale is the major scale starting at C and is thus the sequence of notes starting at around frequency 523 and ending around 1046, that is, the notes (C, D, E, F, G, A, B, C). The D major scale is the major scale starting at D: the sequence of notes (D, E, F#, G, A, B, C#, D). The A major scale is the sequence of notes of (A, B, C#, D, E, F#, G#, A).
There are many other interesting scales, such as the minor scale, defined by the intervals (2,1,2,2,1,2,2), and the blues scale, defined by the intervals (3,2,1,1,3,2) (the scale only contains 7 notes).
For this part of the lab, you should write a program scale.py that will play a scale specified by command-line arguments. In particular, we'd like to be able to type
python3 scale.py -3 Mto play an A major scale,
python3 scale.py 0 Nto play a C minor scale, or
python3 scale.py 4 Bto play a blues scale in E. In particular, you will pass as command-line arguments the tonic note (in its half-tone offset from middle C) and which scale to play (as a character: 'M' for major, 'N' for minor, 'B' for blues). How can your program make use of the arguments you add after the program name? Easy -- if you add import sys at the beginning of your program, you'll get access to the variable sys.argv, which is a list of the arguments passed to Python. The first of these is always the name of the program itself. But if you were to run
python3 scale.py 4 Band that program included the statement print(sys.args), we'd get as output
['scale.py', '4', 'B']Given this (and possibly judicious use of the eval function), you should be able to get all the input you need from command-line arguments.
Requirements for your program:
intervals = [[2,2,1,2,2,2,1], [2,1,2,2,1,2,2], [3,2,1,1,3,2]]These correspond to the number of halftones between successive notes in the Major, Minor, and Blues scales respecively. This should make building scales cleaner.
mozart.py: 8 points, individual.
Now let's talk about this Minuet and Trio business. What is a Minuet and Trio? It is musical piece that is often the third movement of the Classical sonata cycle. Both the Minuet and Trio follow a specific rhythm and form, and they are usually combined by first playing the Minuet, then playing the Trio, then the Minuet once more. You can listen to a very nice Minuet and Trio here.
You'll be generating a Minuet and Trio based on a random algorithm developed by Mr. Mozart himself. Your Minuet will contain 16 measures (musical snippets), as will your Trio. For each of the 16 measures in the Minuet, you will randomly generate a number between 0 and 10 (inclusive); use each such number to pick a specific music snippet from the following table (there are 176 total minuet snippets). For example, if I generate the 16 random numbers (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0, 1, 2, 3, 4, 5) for the Minuet, then I will select the snippets (32, 95, 113, 45, 154, 133, 169, 123, 102, 20, 26, 56, 73, 160, 1, 151). Here is such a randomly generated Minuet and Trio. For the Trio, you do the same thing except your random number is between 0 and 5, inclusive.
In this part of the lab, you will write a program mozart.py that generates such a random Minuet and Trio (that is, it generates a Minuet followed by a Trio followed by your Minuet a second time). There are three main steps to this program, which will be discussed in more detail below.
Requirements and suggestions for your program:
README: 2 points, individual.
As with every lab, your last job prior to submission is to complete a brief write-up in a README file. If you haven't already done so, please create a new README file in your lab07 folder.
In this file, write a sentence or two about what you learned in this lab. Also give an estimate of the amount of time you spent on the lab. If you have further thoughts about the lab (e.g. parts that were confusing, helpful, annoying, fun, or challenging), please let us know.
PLEASE READ: When you do your handin, double check that you haven't somehow moved the Mfiles or Tfiles into your lab09 directory.
Check through your files and make sure you have your name at the top in comments. Also, if you followed the Honor Code in this assignment, insert a paragraph attesting to this fact in the top comment block of this file as well.
I affirm that I have adhered to the Honor Code in this assignment.
You now just need to electronically handin all your files.
% cd # changes to your home directory % cd cs150 # goes to your cs150 folder % handin # starts the handin program # class is 150 # assignment is 9 # file/directory is lab09 % lshand # should show that you've handed in something
You can also specify the options to handin from the command line
% cd ~/cs150 # goes to your cs150 folder % handin -c 150 -a 9 lab09
soundwave.py scale.py mozart.py optional.py # optional (obviously) README