A Brief tutorial on keyboard5.py   http://media.mit.edu/~nvawter/otherProduct/



Part III - Scrutinzing keyboard5.py


The following sounds from keyboard5.py are explained in this file:

Sound 1 = Harmonic interval #1

Sound 2 = Harmonic interval #2

Sound 3 = Tremelo

Sound 5 = Vibrato

Sound 6 = Noise 

Sound 7 = Filtered noise

Sound 9 = Consonant chord

Sound 10 = Dissonant chord

Sound 11 = Kick Drum

Bonus: Sampled waveforms example!



In Part II, we learned basic sinusoid usage.  Now, let's take a look at advanced examples from keyboard5.py:




Sound 1 Harmonic Interval 1


length = Fs * 1.0   # 1.0 seconds

freq = 440.0 * (5.0/4.0)

amp = 16.0

tmp = []

for t in range(int(length)):

        v= amp * Numeric.sin(t*freq/Fs*2*Numeric.pi) 

        tmp.append(v)



What is the biggest difference between Sound 0 and Sound 1?  It's the line:


freq = 440.0 * (5.0/4.0)


The frequency of Sound 1 is exactly 5/4 (1.25) higher than Sound 0.  If you refer back to the web page by Sethares, you'll notice that 5/4 is one of the consonant intervals.  If you look at the page of piano keys and frequencies, you'll notice that 440 is an A, and 5/4 * 440 = 550 = C#.  (Actually the value on the chart is not exactly 550, because they use an approximation, but that's a story for another day!)


Sethares link:

http://eceserv0.ece.wisc.edu/~sethares/consemi.html


Piano keys, frequencies:

http://www.phys.unsw.edu.au/jw/notes.html





Sound 2  Harmonic Interval 2


Sound 2 is exactly like sound 1, except that it has a different frequency.  It's 440*4/3 = 587Hz, because of the link "freq = 400.0 * (4.0/3.0)".  If you consult Sethares' page, you'll notice that this interval is also very consonant.  Try changing its value to something else and make something dissonant.  Your music should usually ;) have a mixture of dissonant and consonant sounds.


length = Fs * 1.001   # 1.5 seconds

freq = 440.0 * (4.0/3.0)

amp = 16.0

tmp = []

for t in range(int(length)):

        v= amp * Numeric.sin(t*freq/Fs*2*Numeric.pi)

        tmp.append(v)





Sound 3 Tremelo


Tremelo is a very pleasing musical effect.  Listen to it a few times as you read this.  Tremelo is a wavering type of sound.  It's the equivalent of twisting a volume knob back and forth very quickly!  This effect is often used with guitars, btw.


To create it, we actually use a second sinusoidal oscillator!  Notice the line about "freq2" below.  You'll see its frequency is very low - 2 Hz!  You know that a 2 Hz frequency is generally inaudible to your ear, so why generate it?  Because we combine the 2 Hz oscillator with an audible one in a clever way that makes its sound more apparent.  We use the 2 Hz oscillator to control the volume of the audible oscillator!  


This control takes place in the final line of code, where v*v2 is calculated.  We simply multiply two sinusoids together to get the tremelo effect.  In synthesizer jargon, this is called "modulation."  We say that one oscillator modulates the volume of the other oscillator. More synthesizer jargon: most people call an inaudible oscillator used to modulate another one a Low Frequency Oscillator, or LFO.  


Things to try:  try increasing the frequency of the LFO to make it audible.  You will a more complex sound, called ring modulation.  "Ringmod" usually makes things sound less harmonic, so use it carefully....  


length = Fs * 1.001   # 1.5 seconds

freq = 440.0 * (4.0/3.0)

freq2 = 2.0

amp = 16.0

tmp = []

for t in range(int(length)):

        v = amp * Numeric.sin(t*freq/Fs*2*Numeric.pi)

        v2 = Numeric.sin(t*freq2/Fs*2*Numeric.pi)

        tmp.append(v*v2)





Sound 5 Vibrato


Listen to this sound and contrast it with the tremelo one.  Like tremelo, vibrato helps reduce stress on our ears, but too much vibrato can obscure the pitch of the sound.  


This example is very similar to the tremelo example.  Can you spot the difference?  

You see it also uses an LFO and that the frequency is a little faster (17.5 Hz, as opposed to 2 Hz).  What else?  Notice how the two sinusoids are not multiplied together.  Instead, in these lines, the output of the LFO modulates the frequency of the other oscillator:


  v = 1 + 0.02 * Numeric.sin(t*freq2/Fs*2*Numeric.pi)

        v2 = amp * Numeric.sin(t*freq/Fs*2*Numeric.pi * v)


How, specifically, does it work? In the first line, notice we're not assigning the output of the LFO directly to v.  Instead, we're multiplying it by a small number and adding one.  This means, v will, instead of ranging from -1 to 1, range from 0.98 to 1.02.


Then, in the second line, notice how the argument to sin() includes "* v".  This means that the actual frequency computed by the audio oscillator will vary with v.  Instead of being a sounding like a steady 440*4/3=587 Hz tone, it will be multiplied by numbers from .98 to 1.02.  Therefore, the output frequency will waver between 575 and 599 Hz.  



Things to try:  try modifying freq2 to change the modulation speed.  You can also change the value of 0.02 to change the modulation depth. 


length = Fs * 1.001   # 1.5 seconds

freq = 440.0 * (4.0/3.0)

freq2 = 17.5

amp = 16.0

tmp = []

for t in range(int(length)):

        v = 1 + 0.02 * Numeric.sin(t*freq2/Fs*2*Numeric.pi)

        v2 = amp * Numeric.sin(t*freq/Fs*2*Numeric.pi * v)

        tmp.append(v2)






Sound 6     Noise


And now for someting totally different!  Let's make a noisy sound!  You'll notice that most of the code is exactly the same as the sinusoid generation code, except that in the guts, instead of computing a sin() at each sample, we compute a random number with the redundant-looking statement:

        v = amp * random.random()


Why noise?  Volumes of books have been written about the philosophy of noise, but for now let's just say that noise in various forms turns out to be an integral part of most music.  For example, the sound of an organ, if you listen carefully, has a bit of quiet noise called a "chiff" when the key is first tapped.  After the chiff passes, you hear the clear tones of the organ's pipes, but that chiff changes the way you hear the rest of the sound, in the same way staring at purple then looking away makes you see a bit of green.  Experiment with mixing different amounts of noise into your music and in different ways.  Can you imagine modulating the frequency of a sound with a noise waveform, for example?


length = Fs * 0.2   # 0.2 seconds

amp = 16.0

tmp = []

for t in range(int(length)):

        v = amp * random.random()

        tmp.append(v)






Sound 7 Low-Pass Filtered Noise


Listen to this sound carefully, comparing it to sound 6.  Notice how this one sounds a little less harsh?  a little more low-pitched?  That's because it has been filtered.  Much like a sand sifter removing the small grains and leaving large, smooth stones behind, the high frequencies of this noise get removed by using this simple filter.  

There are millions and millions of ways to make filters, but this is one of the simplest.  How does it work?  


There are two key parts.  First, notice the new variable "Fc."  Fc stands for "cutoff frequency."  It acts as a variable which controls where the sound gets cutoff.  Low Fcs, like 0.1, mean the cutoff is in the low frequency range.  High numbers like 1.0 mean no noise gets filtered out.  Numbers outside the range 0-1 aren't recommend, unless you're specifically trying to make a mess :)


The second thing to notice is this line:

          v2 = v*Fc + (1-Fc)*lastv



What is happening here:  instead of appending the random samples directly to our array, we are combining the noise samples with the last value that we output.  This means the output can't change as quickly as it would like to, resulting in a smoother-looking and sounding waveform.


length = Fs * 0.2   # 0.2 seconds

Fc = 0.5

amp = 16.0

tmp = []

lastv = 0

for t in range(int(length)):

        v = amp * random.random()

        v2 = v*Fc + (1-Fc)*lastv

        tmp.append(v2)

        lastv = v2



Sound 9    Consonant chord


Examples 9 and 10 go together.  Example 9 demonstrates how to add multiple sinusoids

together to create a timbre which is richer than the one you get from a single sinusoid.  Try changing

the different amplitudes  (amp2-4) to get different timbres.  You might also consider adding tremelo or vibrato on top as well!


length = Fs * 1.0   # 0.2 seconds

base = 400.0

freq  = base * 1.0

freq2 = base * 2.0

freq3 = base * 3.0

freq4 = base * 4.0

amp = 16.0

amp2 = 8.0

amp3 = 4.0

amp4 = 2.0

tmp = []

for t in range(int(length)):

        v  = amp * Numeric.sin(t*freq/Fs*2*Numeric.pi )

        v2 = amp2 * Numeric.sin(t*freq2/Fs*2*Numeric.pi)

        v3 = amp3 * Numeric.sin(t*freq3/Fs*2*Numeric.pi)

        v4 = amp4 * Numeric.sin(t*freq4/Fs*2*Numeric.pi)

        tmp.append(v + v2 + v3 +v4 )




Sound 10    Dissonant Chord 


Sound 10 is different from sound 9 because the frequencies are all slightly changed.  Notice how instead of base *2.0, this example has base*2.1, also 3.0 gets changed to 2.9, and 4.0 gets changed to 3.87.  Listen to the effect this has and how it differs from sound 9.  Notice how they're similar in range and timbre, but sound 10 sounds a little more restless or interesting?  This is because it's non-harmonic.  Non-harmonic sounds are very valuable to create because they flavor and color music.


length = Fs * 1.0   # 0.2 seconds

base = 400.0

freq  = base * 1.0

freq2 = base * 2.1

freq3 = base * 2.9

freq4 = base * 3.87

amp = 16.0

amp2 = 8.0

amp3 = 4.0

amp4 = 2.0

tmp = []

for t in range(int(length)):

        v  = amp * Numeric.sin(t*freq/Fs*2*Numeric.pi )

        v2 = amp2 * Numeric.sin(t*freq2/Fs*2*Numeric.pi)

        v3 = amp3 * Numeric.sin(t*freq3/Fs*2*Numeric.pi)

        v4 = amp4 * Numeric.sin(t*freq4/Fs*2*Numeric.pi)

        tmp.append(v+v2+v3+v4)





Sound 11   Kick drum


In this example, we do a different kind of frequency modulation.  Whereas in the past, we used oscillators to modify frequency, this time we use an envelope.  Envelopes come in all shapes and sizes.  This is the simplest kind of envelope.  look at this line:


        env = (length-t)/length    # from 1 to 0


What does the variable env look like as t varies from 0 to length?    Let's make another little table:


t env

--- ----

0 1.0

1 .99990929705215419501

2 .99981859410430839002

... ...

11025 0

Similar to the frequency modulation example, the value of env will be used to modulate the frequency of main sinusoid.  Therefore, the frequency will vary from 220 Hz a the beginning, down to 0 Hz at the end.  This results in a sound like a kick or bass drum.  This type of sound is used heavily in techno.  


Things to try:  Instead of a linear ramp, try an exponential one, like this:

           env = pow(2, -10*t/length)     # from 1 to 0, exponentially


Exponential ramps sound a little better than linear ramps.  They are use in the famous Roland

TR-808 and TR-909 drum machines....in other words 99% of techno!  :)


length = Fs * 0.20

amp = 16.0

tmp = []

phase = 0

for t in range(int(length)):

        env = (length-t)/length    # from 1 to 0

        freq = 220 * env

        phaseDelta = freq/Fs*2*Numeric.pi

        phase = phase + phaseDelta

        v2 = amp * Numeric.sin(phase)

        tmp.append(v2)



Bonus!  Sample playing!


Sometimes, synthesizing a sound is too difficult, time-consuming, or it just doesn't sound right.  In those circumstances, try using a sample!  here's how to play a sample in python!!  The good news is, it couldn't be simpler.  Just make sure you put a .wav file in the same directory as keyboard5.py and add this line to use it!!


sound.append(pygame.mixer.Sound("pattern01.wav"))