Quick Reference Pages

Wavesynth Reference

For producing (cheesy) synthesized sounds, we've written a wavesynth module that can be used to build simple music out of notes and beats. The selection of instruments and their quality is extremely limited, but it allows us to make music as an alternative to drawing things with the turtle module. Using the wavesynth module is more like programming a robot that writes sheet music than programming a robot that plays an instrument.

You can download the latest version of wavesynth.py at this link:


Music Concepts

To use this module you will need a basic grasp of some musical concepts, although you will not need to really be familiar with music theory or know how to play an instrument. The basic concepts you'll need are:

  1. Tracks. A "track" is a series of notes that are all played together, but which can be separated from other tracks. Tracks can be saved to a '.wav' file, and if you install the extra simpleaudio module, they can be played on your speakers directly within Python. The wavesynth module has a way to change the "current track," and any notes that you create will always be added to whichever track is current. You can use multiple tracks and later mix them together into a single track, or you can simply add all of your notes to a single track. For the most part in this class, we'll use the latter strategy, as it's simpler, and you won't have to worry too much about tracks.

  2. Notes vs. beats vs. "rests". The wavesynth module has three functions for adding music to a track: addNote, addBeat, and addRest.

    • addRest is the simplest of these functions, because a "rest" in musical terminology is a period of silence, when the musician gets to take a rest. Adding rests doesn't actually change the sound of a track, but it may extend its duration, and it can be convenient to add a rest to create a gap between notes if you want one, although it's not strictly necessary. Note that adding a rest on top of an existing note won't erase that note (and there's currently no way to erase notes you've added to a track).
    • addBeat is still pretty simple, and is used for drums and other percussion instruments which don't have a specific pitch to them. You can specify when the beat should be added, what instrument to use, and how long the beat should last.
    • addNote is the most complicated function, and is used for any instrument that has a specific pitch. You'll need to specify when in the track to add the note, the instrument to use, the pitch of the note, and its duration.
  3. Pitches. A pitch (or tone, or frequency) in music refers to how high or low a sound is, and it's represented as a number. You won't have to do math with these numbers yourself (as that's quite complicated) but you will have to use the wavesynth library's pitch modification functions to compute higher or lower pitches when you need them, and there are a few concepts to be aware of:

    • A "half-step" is the smallest unit of pitch difference that the wavesynth module can easily create, and there are halfStepUp and halfStepDown functions for calculating higher and lower pitches. Half steps are used as part of the basis for classical music, and there are 12 half-steps in an "octave," which represents a doubling of pitch.
    • A "step" or "whole step" is a larger unit of pitch difference that is usually equivalent to two half-steps. You can use the stepUp and stepDown functions to produce pitches that are higher or lower by a step. However, steps are not as regular as half-steps, because there are 8 whole steps in an octave, not 6. Classical music is just complicated like that. You won't need to worry about this too much: just use the stepUp and/or stepDown functions and they'll take care of what the pitch should be.
    • A "leap" is not a real musical term, but wavesynth has leapUp and leapDown functions which move between notes on what is called a pentatonic scale that uses 5 notes per octave instead of 8. Pentatonic scales are used in several non-Western music systems, and they're beginner-friendly because no matter what notes you combine, the result will always sound nice (in contrast, combining notes that are the wrong number of steps or half-steps apart can sound pretty horrible!). You don't need to worry too much about the details here, just pay attention to when we ask you to use 'leaps' vs. 'steps'.

    In addition to moving pitches up and down, you'll need to be able to talk about specific pitches, and there are two systems that wavesynth provides. Scientific pitch notation is a classical pitch system which labels each of the eight steps in an octave using the letters "A" through "G", and then pairs those letters with a number indicating which octave the note is in. So for example, the note B3 is one octave below (i.e. 8 whole steps below, or 1/2 the frequency of) the note B4. Similarly, G4 is 3 steps above D4. The confusing thing about this system is that each octave starts on a C, not an A, so B3 is only one step below C4, which is the first note in octave 4. The wavesynth module makes global variables named for all of the pitches C0 through B9 available. In addition to these variables, there is a set of variables named P0 through P14 which refers to successively higher notes on a pentatonic scale. These have the same values as some of the scientific pitch notation notes, they're just alternate names for them, but they can be used instead for a simpler way of naming higher and lower notes, with larger pitch differences between those notes. For reference, P5 is equal to C4, and that tone is a reasonable "middle" tone that's neither very high nor very low. If you happen to be familiar with musical notation, or if you've seen sheet music and wondered about how it works, here is an image of how these different pitch values map to sheet music notation:

    An image showing the bass and treble clefs with one note on each line and in each space ascending in each clef from two steps below the bottom line to one step above the top line. Each note is labeled underneath with its corresponding letter and octave number, from E2 to B3 on the bass clef and from C4 to G5 on the treble clef.


What follows is a summary of each of the most important wavesynth functions that you'll need to use, including a brief description of what the function does and examples of how it could be used.

Note that the simplest way to get access to these functions is to use the following import statement (assuming that wavesynth.py is in the same directory as the file you're working on):

from wavesynth import *

Also note that in order for wavesynth.py to play sounds directly from within Python, you'll need to the "Manage Packages" feature in the "Tools" menu in Thonny to install the "simpleaudio" package, although you can use wavesynth.py without this feature to produce .wav files that you can use a separate media player program to play back.

Basic Sounds:
Function Effect Example(s)
addRest Adds a period of silence to the current track (see setActiveTrack). The argument specifies how long the rest will be. If the end of the rest is after the end of the current track, the track's duration will be increased. The rest starts at the current time (see setTime), and the current time will be updated to the end of the rest after the rest is added. addRest(0.4)
addBeat Adds a percussive sound (with no specific pitch) to the current track (see setActiveTrack). The argument specifies how long the beat will be. If the end of the beat is after the end of the current track, the track's duration will be increased. Use setTime, setVolume, and setDrum to control when the beat happens, how loud it is, and what kind of drum is used. The current time will be updated to the end of the beat after it is added. addBeat(0.2)
addNote Adds a note to the current track (see setActiveTrack). The argument how long the note will be. If the end of the note is after the end of the current track, the track's duration will be increased. Use setTime, setVolume, setInstrument, and setPitch plus related functions like fastforward or stepUp to control when the note occurs, how loud it is, what pitch it's at, and what instrument is used. The current time will be set to the end of the note after it is added. addNote(0.2)
Function Effect Example(s)
kick A percussion instrument (use it with setDrum) meant to sound like a kick drum: a deeper reverberating sound. Kick drums are non-trivial synthesize, so this instrument won't sound that great, especially if you're using laptop speakers. setDrum("kick") setDrum(kick)
snare Another percussion instrument (use it with setDrum) meant to sound like a snare drum: a sharp and loud sound. Snare drums are easier to synthesize, so this works better than kick. setDrum("snare")
beep A very basic pitched instrument (use it with setInstrument) that sounds like (and well, is) an electronic beep. setInstrument("beep")
keyboard A basic pitched instrument (use it with setInstrument) that's supposed to sound vaguely like a piano. setInstrument("keyboard")
harmonica A pitched instrument (use it with setInstrument) that's supposed to sound vaguely like a harmonica or other wind instrument. setInstrument("harmonica")
Instrument Controls:
Function Effect Example(s)
setDrum Sets the current drum function to be used when addBeat is called. The argument may be either a drum function (e.g. kick; see above) or the name of one of those functions as a string. The default drum is snare. setDrum(kick) setDrum("snare")
currentDrumName Returns the name (a string) of the current drum function. drum = currentDrumName()
setInstrument Sets the current instrument function to be used when addNote is called. The argument may be either an instrument function (e.g. harmonica; see above) or the name of one of those functions as a string. The default instrument is keyboard. setInstrument(beep) setInstrument("harmonica")
currentInstrumentName Returns the name of the current instrument as a string. name = currentInstrumentName()
Note: If you're using a custom drum or instrument function (not covered in this documentation) you will not be able to use the strings returned by currentDrumName or currentInstrumentName with setDrum or setInstrument later. There are currentDrum and currentInstrument functions that can return the actual drum/instrument function in use.
Pitch Functions:
Function Effect Example(s)
setPitch Sets the current pitch value, which will be used for subsequent calls to addNote. There are constants like A3, Bb4 and P10 defined for both scientific pitch notation and a couple of octaves of pentatonic scale, and you can also just provide a number in Hertz. Use currentPitch to access the current pitch value. setPitch(440) setPitch(A3) setPitch(P5)
currentPitch Returns the numeric value of the current pitch. Use currentPitchName if you want to know what the pitch is close to in terms of scientific pitch notation. The value returned here can be used later with setPitch to set the pitch back to its old value. pitch = currentPitch()
currentPitchName Returns a string describing the current pitch. Use currentPitch if you want to get the numeric value instead. The string will try to describe the pitch based on scientific pitch notation if it can, but will include a number in Hertz if necessary. name = currentPitchName()
pitchName Returns a string describing a given pitch (provided as a number). Used by currentPitchName, but you can also use it with other numbers yourself.
name = pitchName(440)
# will be 'A3'
halfStepUp Modifies the current pitch value (see setPitch) so that it is one half-step above the old value. An optional argument specifies a number of half-steps to take, which could be negative to take half-steps down (but there's also halfStepDown for that). One half step up is the same as multiplying by 2<sup>1/12</sup>, or about 1.05946, while one half-step down is the same as dividing by this value. halfStepUp() halfStepUp(3)
# pitch will now be Eb4
halfStepDown Works just like halfStepUp (including the optional argument) but takes half-steps down instead of up. halStepDown() halfStepDown(5)
stepUp Modifies the current pitch value (see setPitch) so that it's one whole step higher than the old value, but first rounds the old value to the nearest piano key pitch. So for example, an input of 440.5 (slightly above A4) would be rounded to 440.0108 (exactly A4) before being raised a whole step to give a result of 493.8954, which is the note B4, two half-steps above A4. This gets even more complicated because sometimes the distance between whole steps is only one half-step, in which case this function only actually changes the pitch by that half step, but it will always change an A into a B, a B into a C, a C into a D, and so forth. Like halfStepUp, an argument may be provided to step up multiple steps at once, and a negative argument will step down (but see also stepDown). stepUp() stepUp(2)
# pitch will now be E4
stepDown Works just like stepUp, except it lowers the current pitch instead of raising it. The same rounding rules apply. stepDown() stepDown(2)
leapUp / leapDown These two functions work like stepUp and stepDown, but they set the current pitch to a pitch that's one place up or down a pentatonic scale, after rounding to the nearest note on that scale. This usually translates to two whole steps of pitch difference, but it depends on where you are in the scale, since the five notes per octave are not evenly spaced. Because of rounding, two leapUp results from notes that are as much as a whole step apart may be the same (e.g., leapUp starting from either E4 or F4 will set the current pitch to G4). Like stepUp and stepDown an optional argument may be provided to leap multiple notes at once. leapUp() leapDown(2)
# pitch will now be G4
Volume Controls:
Function Effect Example(s)
setVolume Sets the current volume. The single argument must be a number between 0 and 1 (inclusive). Notes and beats added (see addNote and addBeat) will be louder or quieter as a result. Note that the starting volume is 0.6, and the volume cannot be set higher than 1.0. setVolume(0.8) setVolume(0)
currentVolume Returns the current volume level, as a number between 0 and 1 (inclusive). vol = currentVolume()
louder Increases the current volume a bit (multiplies it by 1.5). Optionally, a single argument may be provided to increase the volume by multiple steps at once (it will respect the max volume of 1.0). louder() louder(2.5)
quieter Decreases the current volume a bit (divides it by 1.5, which is the same as multiplying by 2/3). Multiple steps may be taken by providing an argument. One step quieter is exactly the opposite of one step louder. quieter() quieter(2)
Time Controls:
Function Effect Example(s)
setTime Sets the current position in time. The single argument must be a non-negative number measured in seconds. Note that the addRest, addBeat, and addNote functions all create sound (or silence) starting at the current time, and they also update the current time value to the end of the rest/beat/note that they created. Setting the time to a position after the end of the current track is fine (the track will automatically expand once something is added) but doing so will not actually cause the track to extend until something is added (use addRest to add silence at the end of a track). Usually, using rewind and/or fastforward is more convenient. setTime(0) setTime(2.5)
currentTime Returns the current time, which is a non-negative number measured in seconds. currentTime
rewind Subtracts from the current time, moving backwards by the given number of seconds. If this would result in a current time before 0, the time is just set to 0. This can be used to layer notes on top of each other to form chords. rewind(3.5)
fastforward Adds to the current time, moving forwards by the given number of seconds. This is the opposite of rewind. If a negative number is given, the current time will be moved backwards, but if this would result in a time before 0, the time will just be set to 0 instead. fastforward(1.1)
Track Controls:
Function Effect Example(s)
setActiveTrack setActiveTrack takes a track name string as its only parameter and either creates a new track with that name or switches to the existing track with that name. Any notes that are added are always added to the current track, and setActiveTrack changes which track is current. If you never call setActiveTrack, a track named "default" is used for any notes that are added. setActiveTrack("drums")
eraseTrack Completely erases the contents of the current track, and sets its duration back to zero. Use setActiveTrack to control which track is the current one. eraseTrack()
trackDuration Returns the duration of the current track, starting at t=0 and ending at the end of the last note, beat, or rest that has been added to the track. d = trackDuration()
mixTracks Mixes two tracks together to create a third, new track. The first two parameters name the tracks to be mixed, while the last parameter is the name of the new track that will be created (it must not already be used by an existing track). mixTracks('melody', 'harmony', 'song')
Generating Output:
Function Effect Example(s)
printTrack Prints out a summary of the notes, beats, and rests that have been added to the current track. Can be useful for verifying the exact pitches or durations of different notes. This should not take a long time to run. printTrack()
prepareTrack Prepares the current track for playback or saving to a file. You do not need to call this function, because it will be called automatically when saveTrack or playTrack is called for the first time. However, this function takes a while to run (usually a little longer than the duration of the track you're preparing), so be patient. Thankfully, once this function has been called, whether manually or automatically, the results are saved, and both saveTrack and playTrack should be quick afterwards. prepareTrack()
saveTrack Saves the current track in a file with the given file name. The file name argument should be a string that ends with ".wav", since the file is saved in WAV format. Note that for long tracks, this uncompressed format may result in pretty large files. This function also takes some time to process the track, often roughly about as long as the duration of the track, or even a bit longer (see prepareTrack). saveTrack("song.wav")
playTrack Plays the current track using your computer's sound system. For this to work, you must first install the simpleaudio module. Like saveTrack, this may take a while to prepare the track before playback starts, so be patient (see prepareTrack). The longer the track, the more time it will take to prepare. Also be warned: the default volume is pretty loud, so you may want to turn your computer's volume down first, especially if you're using headphones. Because playTrack isn't quick, if you want to listen to output several times, it's better to use saveTrack (or use both functions) and play back the saved file. playTrack()

Pitch Details

More details about pitches: in fact, the number that represents a pitch tells us how many times per second that sound vibrates the air: "higher" pitches are produced by faster vibrations (meaning more tiny pulses of high-pressure air per second) while "lower" pitches have slower vibrations. The range of frequencies that's comfortable goes from something like 20-30 pulses-per-second on the very low end, all the way up to something like 10,000-20,000 pulses-per-second on the high end, and the range is so large in part because pitch is multiplicative, not additive. In other words, when we go "up" a certain amount in terms of pitch, we're multiplying the frequency of the note, not adding to it. Going up by the same amount again and again represents successive multiplications, which is why there's such a large range of values. Pitches are stored using floating-point numbers (or rarely, integers), and you won't have to do any of the math yourself because of the functions like stepUp or leapDown that the wavesynth module provides.