Gold Wk 3 Problem 3: Sounds good!
[50 points; filename hw3pr3.py]
Starting files to download
Starter files for this problem:
Download pythonSoundsCGU.zip from this link.
Be sure to unzip that folder somewhere -- there is a file named hw3pr3.py from which to start.
Background on representing audio information
What is inside an audio file?
Depending on the format, the actual audio data might be encoded in many different ways. One of the most basic is known as pulse
code modulation (PCM), in which the sound waves are sampled every so often and given values in the range -128 to 127 (if 1 byte per sound sample is used)
or -32768 to 32767 (if there are 2 bytes for each sample). Wikipedia explains it here. The .wav file format encodes audio in basically this way, and the cross-platform program Audacity is an excellent tool for visualizing the individual PCM samples of
an audio file. You don't need Audacity for this problem, but it runs on Windows and Macs and is fun to play around with if you'd like to. Audacity can also convert to .wav from .mp3 and many other formats. Last but not least, Audacity was created by an HMC alum, Dominic Mazzoni.
Starter files
Be sure to grab the zipped folder pythonSounds.zip (above) -- it has a starter hw3pr3.py file and you'll need its support file, csaudio.py, three sounds files spam.wav, swfaith.wav, and swnotry.wav, and a Mac sound-playing program named play, which is also available from the play website. You won't need to download it from there, however, since it's already in the pythonSounds.zip file.
Getting started with csaudio:
- Type F5 to run the provided hw3pr3.py file.
- At the shell prompt, type test(). That should play one of the sound files.
- Then, try the other two functions and try writing sound-manipulation functions of your own, as described below.
The Python functions changeSpeed and flipflop
In your hw3pr3.py file, you will find two more functions, named changeSpeed and flipflop.
Try those two out with the calls
>>> changeSpeed( 'swfaith.wav', 44100 ) # Alvin as Darth...
>>> flipflop( 'swfaith.wav' ) # split and switched...
Be sure to read over both of these functions and understand why they do what they do. Then you'll be ready to write your own sound-manipulating code!
Warning: you'll see commented-out lines at the bottom of each function that would otherwise return the full list of sound samples. These are very large lists -- if you return the list of raw sound data (these return statements are currently commented out...), then be sure to assign the result to a variable! By doing so, e.g., L = changeSpeed('swfaith.wav', 30000), IDLE will not try to print out all of the raw sound data. If you don't assign the call to a variable, IDLE will try to print out the raw sound data, it will be too big, and IDLE will hang or crash.
Thus, when using the return statements, the assignment to L is important, because these functions return a large list of sound data samples! You can always look at pieces of L with slicing, e.g., L[0:100].
Functions to write
Question 0: Slowing down...
- Write a function
adjustSpeed(filename, multiplier)
that plays a new sound (based on the sound in filename), whose speed is adjusted by the input multiplier
.
When multiplier
is 2.0, the resulting sound should play twice as fast. When it is 0.5, it should play half as fast (taking twice as much time), and so on... .
Question 1: Sound reversal
- Write a function
reverse(filename)
that takes in an input .wav file's name (as a string) into the input argument filename and, optionally, takes a second filename (as a string) for the output. Again, the default will be "out.wav". The reverse function should write out a new .wav file in which the order of the sound samples has been reversed. Like flipflop, your reverse function should play the sound after writing it to file and it should return the new sound's list of raw data samples. Use flipflop as a model. In particlar, don't implement this function (or any sound-processing function) using raw recursion—sounds are so large that Python will run out of memory even for very short sounds. However, Python optimizes its implementation of slicing, and map, and list comprehensions (perhaps the easiest to use), so that the memory problems will not occur - at least not for moderate-sized sounds.
Question 2: Whisper/shout mode
- Write a function
volume(filename, percent)
that takes in a .wav file's name (as a string) and a floating-point value named percent, which represents how much louder or softer the output sound should be. As with flipflop and reverse, your volume function should play the sound after writing it to file and it should return the new sound's list of raw data samples. The output sound's filename, newFile is handled in the usual way. For example,
volume('swfaith.wav', 0.5) should create a sound with half the amplitude of the original
volume('swfaith.wav', 1.5) should create a sound with one and a half the amplitude of the original.
You'll notice that your hearing adjusts remarkably well to this function's changes in absolute volume, making their perceived effect less than you might expect. You will also find that if you increase the volume too much, the sound becomes distorted, just as when you turn an amplifier up to 11.
Question 3: A pure-tone generator
- Write a function
oneFreq(freq)
that takes in a floating-point frequency (in hertz, or periods per second) and it writes out a .wav file (you choose the name) that is one second of cosine wave - a pure tone - at the input frequency. I used math.cos. As usual, the oneFreq function should play the sound generated and return the list of its raw sample data. The sample rate used to create the new sound's file should be 22050 samples per second. The amplitude of your cosine wave should be the maximum of 32767.0.
At the heart of this function will probably be a list comprehension that looks like this: samples = [ SOMETHING for x in range(NUM_SAMPLES) ]. Variables that I defined for use in this expression included
- A, the amplitude of the cosine wave
- NUM_SAMPLES, which should be 22050
- TP, the value of 2*pi
- SR, the sampling rate used
Hint: As a hint, a 440 hertz concert-tuning A with amplitude of 20,000 and running for 1 second (which is 22050 samples) would be formed by the cosine wave
[ 20000.0*math.cos( 2*math.pi*440.0*x/22050 ) for x in range(22050) ]
Question 4: Chords
- Certainly oneFreq is not enough! Write a function
multiFreq(freqList)
whose input named freqList is a three-element list of lists of numbers of the form
[ [freq1, power1], [freq2, power2], [freq3, power3] ]
The multiFreq function should write out a two-second chord with all of those frequencies, having the same relative strength as their accompanying power
values. Again, use 22050 hz as the sample rate. As a hint, you might write an alternative version of oneFreq that could do the "heavy lifting" for you here -- and then call it three times and average the values returned... . It is best to be sure that the values in the list you generate do not go outside the range of -32767.0 to +32767.0, so you should divvy up the available amplitude (power) proportionally.
For those who have not studied the mathematics of music (like me), it is useful to know that a "half step" on the piano is equal to a frequency ratio of the twelfth root of two, or (as Python happily informs us) 1.05946309436. A whole step's ratio is the product of two half steps, or 1.122462048309373, a minor third's ratio is a half step to the third power, or 1.1892071150027212, and a major third's ratio is 1.2599210498948734. If you don't have musical training, it's also worth knowing that concert "A" is 440 Hz, and a pleasing chord can be made from a root (1.0), a major third, and a perfect fifth (which ideally is a ratio of 1.5, although you can see by multiplying half steps that you can't quite hit that in our usual tuning system).
So multiFreq([[440,1],[440*1.26,1],[440*1.5,1]])
should sound fairly nice to your ears.
You may wish to experiment with "odd" frequencies to see what kind of sounds you get. Try multiFreq([[440,1],[442,1],[443,1]])
ofor example.
Unfortunately, it usually takes at least 6 to 8 frequencies in ratios of 1.5-to-1 or 2-to-1 to begin to simulate the sound of acoustic instruments, and we won't go quite that far with this function.
Question 5: Your own effect(s)...
- Write one or more functions that combine or extend these effects—or, simply invent your own audio-altering computation. Be sure to include a comment with your function and/or in its docstring describing what your effect is. We look forward to trying your function on the sounds we have!
Submission
Simply submit your altered hw3pr3.py file in the usual way through the submission site.
Copyright © by the contributing authors. All material on this collaboration platform is the property of the contributing authors.
Ideas, requests, problems regarding TWiki? Send feedback