Instructions for highNotes

(produced at 03:06 a.m. UTC on 2023-09-19)

This task is part of project03 which is due at 23:00 EDT on 2023-09-26.

You have the option to work with a partner on this task if you wish. Working with a partner requires more work to coordinate schedules, but if you work together and make sure that you are both understanding the code you write, you will make progress faster and learn more.

You can download the starter code for this task using this link.

You can submit this task using this link.

Put all of your work for this task into the file highNotes.py
(which is provided among the starter files)

For this problem set, you have a choice: you may complete this task OR the treeTops task, but you do not need to complete both. treeTops is graphics-oriented and works with turtle graphics, while highNotes (this task) is based on audio and music instead, using the wavesynth library.

This task will help you practice the material on custom functions and how to use several functions together to achieve a complicated result. It will also have you work with functions that have both side effects and return values. For this task, you will work with the wavesynth.py audio library.

Motifs, Phrases, and Songs

For this task, you will be writing code to create a customizable song that involves repetition with variation: three-note motifs are repeated three times to form a phrase, and the whole song consists of two such phrases. (Note: the audio can be quite loud, so you may want to turn down your sound first until you can find the right volume).


Click here to download test_song.wav

To make the task more interesting, the song function that you write must return the pitch value of the highest note in the song (pitch is a number indicating how high or low a sound is).

You will need to write three separate functions that work together to produce the song: motif which creates three-note patterns, phrase which creates phrases out of three motifs, and song, which combines two phrases into a song.

wavesynth.py Usage

For this task, you will only need to use the climbUp and climbDown functions from wavesynth; we have provided a function called doubleNote which you will use to create notes.

The provided testing code also uses playTrack, saveTrack, and printTrack to play and show the results of your code.

Subtask A: motif

The songs that we'll be creating have repeated three-note sequences in them, so we will begin with a function that creates these. Technically, each 'note' will actually be a chord: two notes playing at the same time, but you do not have to construct these chords: the provided doubleNote function will do that.

Here are examples of what two different motifs might sound like, produced using different arguments to the motif function. In the first motif, produced by calling `motif(0.3, 1, 1)`, the three notes are simply ascending by climbing up 1 rung of the scale each time, e.g., C-D-E. In the second motif, produced by calling `motif(0.2, -1, 2)`, the second note is one rung below the first, and the third note is two rungs above the second note (so 1 rung above the first note), e.g., C-B-D.


Click here to download test_motif_1.wav


Click here to download test_motif_2.wav

motif must accept three parameters (in this order):

  1. The duration to be used for each note.
  2. The interval (i.e., number to pass to climbUp) to climb up between the first and second notes (to climb down, use a negative number).
  3. The interval to climb up between the second and third notes.

motif must call doubleNote three times to add three note-pairs back-to-back, using the given pitch intervals between them. motif must also return the pitch of the highest note it creates. To do this, you can simply take the return values from each call to doubleNote and use max to find the highest value, because the doubleNote function itself returns the pitch of the highest note it adds.

Finally, motif must maintain an invariant: by the end of the function, the current pitch must be returned to the initial pitch, even though the last note will often not be at that pitch.

We've provided some testing code which uses optimism to test motif. Even if you have not defined the other functions yet, those tests will be skipped, so once you are done with motif, you can run the test_highNotes.py file to see if it is working properly.

These motif examples show what should be printed when it runs, indicate what the return value should be, and include an audio snippet you can play so you can hear what it should sound like.

Subtask B: Assembling Phrases

Once motif is working, we need to assemble three motifs together into a phrase, where again there is some pitch change between each motif. The phrase function will have four parameters which specify the note duration, the first gap interval for the repeating motifs, the second gap interval for the repeating motifs, and the overall pitch interval between motifs. The first three arguments must be passed directly to motif to create the three motifs, while the final argument specifies a pitch interval to climb up in between each pair of motifs. phrase will add nine specific notes to the track, for example if the gaps are both 1 and the interval is 3, as in phrase(0.2, 1, 1, 3), the nine notes will each be one rung above the last and they'll form an ascending scale.

Unlike motif, where the pitch must return to the starting value at the end, phrase must leave the pitch at the pitch used for the final motif it creates. Because motif does not change the current pitch, this final pitch will be the result of climbing up twice using the specified between-motif interval. By doing this, any additional phrases or motifs will start from the same base pitch as the last motif of the previous phrase.

Like motif, phrase must return the pitch of the highest note that it creates. This can be achieved by calling max with the result values from the three motifs it created.

These phrase examples demonstrate how it should work.

Subtask C: The Full Song

After defining your phrase function, you're ready to create the song function, which has five parameters and adds two phrases to the track. The parameters are:

  1. The duration for each note of the song.
  2. The gap interval for the first motif of the first phrase of the song.
  3. The gap interval for the second motif of the first phrase of the song.
  4. The gap interval for the first motif of the second phrase of the song.
  5. The gap interval for the second motif of the second phrase of the song.

Essentially, the 2nd through 5th parameters define the up or down pitch changes used in two different motifs, and then phrase is used twice to create one phrase based on the first motif and another based on the second. The inter-motif intervals used for the two phrases are not given as arguments and are instead always the same: 1 for the first phrase and -1 for the second phrase.

As with motif and phrase, song must return the pitch of the highest note in the whole song. Once again, you will use max on the results of the two phrase calls to compute this value. It should also leave the pitch back at the starting pitch when it is done.

This song example demonstrates how it should work.

Testing

We have provided a file named test_highNotes.py which can be used to test your work. It will skip tests for any functions that it can't find, so you can use it even just to test motif before you have written phrase and/or song. We recommend that you test each function as you build it, because the lessons you learn from debugging your earlier functions will help you improve the later functions.

Notes

  • As usual, you must include a non-empty docstring for each function you write.
  • Also as usual, you must not waste fruit or waste boxes, and you shouldn't define functions inside other functions.
  • Your motif function may not call addNote directly (which is unnecessary, since you will call doubleNote) Similarly, your phrase function may not call addNote or doubleNote, because it needs to rely on motif to produce notes, and your song function must also not call those functions (it should call phrase to create notes).
  • Your motif function may not call setPitch, because it needs to return to whatever the starting pitch was, and moving to a specific pre-defined pitch makes that impossible. This restriction also applies to phrase and to song.
  • You are allowed to define and call extra functions if you wish, as long as these don't invalidate the rubric rules for which functions must call which other functions (note that there are some restrictions about which functions can call addNote directly, for example).
  • The longer a song is, the longer it will take Python to process it when it's asked to play or save the song. Using shorter duration values during testing can help alleviate this problem. Especially when testing song, expect to wait a few seconds (maybe as many as 10-20) for the audio to play.
  • The durations displayed by wavesynth.py when it prints tracks are rounded off, but your code should not do any rounding.

Examples

motif examples

These examples show how motif should work, including what it should print, what it should return, and the audio it should produce.

In []:
setPitch(D4) result = motif(0.2, -1, -2) printTrack() print("Highest pitch was:", round(result, 3)) playTrack()
Prints
at 0s a 0.2s keyboard note at D4 (60% vol) at 0s a 0.2s keyboard note at F4 (18% vol) at 0.2s a 0.2s keyboard note at C4 (60% vol) at 0.2s a 0.2s keyboard note at E4 (18% vol) at 0.4s a 0.2s keyboard note at A3 (60% vol) at 0.4s a 0.2s keyboard note at C4 (18% vol) Highest pitch was: 349.237
Audio In []:
setPitch(G3) result = motif(0.35, 2, 3) printTrack() print("Highest pitch was:", round(result, 3)) playTrack()
Prints
at 0s a 0.35s keyboard note at G3 (60% vol) at 0s a 0.35s keyboard note at B3 (18% vol) at 0.35s a 0.35s keyboard note at B3 (60% vol) at 0.35s a 0.35s keyboard note at D4 (18% vol) at 0.7s a 0.35s keyboard note at E4 (60% vol) at 0.7s a 0.35s keyboard note at G4 (18% vol) Highest pitch was: 392.005
Audio

phrase examples

These examples show how phrase should work, including what it should print, what it should return, and the audio it should produce.

In []:
setPitch(B3) result = phrase(0.2, 1, 1, 3) printTrack() print("Highest pitch was:", round(result, 3)) playTrack()
Prints
at 0s a 0.2s keyboard note at B3 (60% vol) at 0s a 0.2s keyboard note at D4 (18% vol) at 0.2s a 0.2s keyboard note at C4 (60% vol) at 0.2s a 0.2s keyboard note at E4 (18% vol) at 0.4s a 0.2s keyboard note at D4 (60% vol) at 0.4s a 0.2s keyboard note at F4 (18% vol) at 0.6s a 0.2s keyboard note at E4 (60% vol) at 0.6s a 0.2s keyboard note at G4 (18% vol) at 0.8s a 0.2s keyboard note at F4 (60% vol) at 0.8s a 0.2s keyboard note at A4 (18% vol) at 1s a 0.2s keyboard note at G4 (60% vol) at 1s a 0.2s keyboard note at B4 (18% vol) at 1.2s a 0.2s keyboard note at A4 (60% vol) at 1.2s a 0.2s keyboard note at C5 (18% vol) at 1.4s a 0.2s keyboard note at B4 (60% vol) at 1.4s a 0.2s keyboard note at D5 (18% vol) at 1.6s a 0.2s keyboard note at C5 (60% vol) at 1.6s a 0.2s keyboard note at E5 (18% vol) Highest pitch was: 659.271
Audio In []:
setPitch(C4) result = phrase(0.15, 3, 2, -2) printTrack() print("Highest pitch was:", round(result, 3)) playTrack()
Prints
at 0s a 0.15s keyboard note at C4 (60% vol) at 0s a 0.15s keyboard note at E4 (18% vol) at 0.15s a 0.15s keyboard note at F4 (60% vol) at 0.15s a 0.15s keyboard note at A4 (18% vol) at 0.3s a 0.15s keyboard note at A4 (60% vol) at 0.3s a 0.15s keyboard note at C5 (18% vol) at 0.45s a 0.15s keyboard note at A3 (60% vol) at 0.45s a 0.15s keyboard note at C4 (18% vol) at 0.6s a 0.15s keyboard note at D4 (60% vol) at 0.6s a 0.15s keyboard note at F4 (18% vol) at 0.75s a 0.15s keyboard note at F4 (60% vol) at 0.75s a 0.15s keyboard note at A4 (18% vol) at 0.9s a 0.15s keyboard note at F3 (60% vol) at 0.9s a 0.15s keyboard note at A3 (18% vol) at 1.05s a 0.15s keyboard note at B3 (60% vol) at 1.05s a 0.15s keyboard note at D4 (18% vol) at 1.2s a 0.15s keyboard note at D4 (60% vol) at 1.2s a 0.15s keyboard note at F4 (18% vol) Highest pitch was: 523.264
Audio

song example

This example shows how the whole song function should work, including what it should print, what it should return, and the audio it should produce. Since we use gap parameters 1, 2, -1, and -2, we get motifs that move up and then down. The first motif with jumps of 1 and then 2 is B-C-E when written as letters. This repeats as C-D-F and then as D-E-G to complete the first phrase. The second phrase starts with the second motif with jumps of -1 and -2, written as D-C-A. This repeats as C-B-G and then B-A-F (note letters wrap around from G to A) to complete the second phrase. The output below shows more notes because doubleNote creates louder and quieter notes in pairs at different pitches.

In []:
setPitch(B3) result = song(0.3, 1, 2, -1, -2) printTrack() print("Highest pitch was:", round(result, 3)) playTrack()
Prints
at 0s a 0.3s keyboard note at B3 (60% vol) at 0s a 0.3s keyboard note at D4 (18% vol) at 0.3s a 0.3s keyboard note at C4 (60% vol) at 0.3s a 0.3s keyboard note at E4 (18% vol) at 0.6s a 0.3s keyboard note at E4 (60% vol) at 0.6s a 0.3s keyboard note at G4 (18% vol) at 0.9s a 0.3s keyboard note at C4 (60% vol) at 0.9s a 0.3s keyboard note at E4 (18% vol) at 1.2s a 0.3s keyboard note at D4 (60% vol) at 1.2s a 0.3s keyboard note at F4 (18% vol) at 1.5s a 0.3s keyboard note at F4 (60% vol) at 1.5s a 0.3s keyboard note at A4 (18% vol) at 1.8s a 0.3s keyboard note at D4 (60% vol) at 1.8s a 0.3s keyboard note at F4 (18% vol) at 2.1s a 0.3s keyboard note at E4 (60% vol) at 2.1s a 0.3s keyboard note at G4 (18% vol) at 2.4s a 0.3s keyboard note at G4 (60% vol) at 2.4s a 0.3s keyboard note at B4 (18% vol) at 2.7s a 0.3s keyboard note at D4 (60% vol) at 2.7s a 0.3s keyboard note at F4 (18% vol) at 3s a 0.3s keyboard note at C4 (60% vol) at 3s a 0.3s keyboard note at E4 (18% vol) at 3.3s a 0.3s keyboard note at A3 (60% vol) at 3.3s a 0.3s keyboard note at C4 (18% vol) at 3.6s a 0.3s keyboard note at C4 (60% vol) at 3.6s a 0.3s keyboard note at E4 (18% vol) at 3.9s a 0.3s keyboard note at B3 (60% vol) at 3.9s a 0.3s keyboard note at D4 (18% vol) at 4.2s a 0.3s keyboard note at G3 (60% vol) at 4.2s a 0.3s keyboard note at B3 (18% vol) at 4.5s a 0.3s keyboard note at B3 (60% vol) at 4.5s a 0.3s keyboard note at D4 (18% vol) at 4.8s a 0.3s keyboard note at A3 (60% vol) at 4.8s a 0.3s keyboard note at C4 (18% vol) at 5.1s a 0.3s keyboard note at F3 (60% vol) at 5.1s a 0.3s keyboard note at A3 (18% vol) Highest pitch was: 493.895
Audio

Rubric

Group goals:
 
unknown All functions are documented
Each function you define must include a non-empty documentation string as the very first thing in the function.
 
unknown Do not define functions inside of other functions
None of your function definitions may be placed inside of other function definitions.
 
unknown Do not ignore the results of any fruitful function calls
According to the "Don't waste fruit" principle, every place you call a fruitful function (built-in or custom) you must store the result in a variable, or that function call must be part of a larger expression that uses its return value.
 
unknown Do not create any variables that you never make use of
According to the "Don't waste boxes" principle, every time you create a variable (using = or by defining a parameter for a function) you must also later use that variable as part of another expression. If you need to create a variable that you won't use, it must have the name _, but you should only do this if absolutely necessary.
 
unknown motif must return the correct result
The result returned when your motif function is run must match the solution result.
 
unknown motif must produce the correct note sequence
The notes added to the current track when motif is called must match the solution notes in terms of timing, instruments, pitches, and volumes.
 
unknown motif does not change the pitch.
We will run motif, setting the pitch beforehand to different values and recording the pitch afterwards. The final pitch must match the original pitch we specified.
 
unknown phrase must return the correct result
The result returned when your phrase function is run must match the solution result.
 
unknown phrase must produce the correct note sequence
The notes added to the current track when phrase is called must match the solution notes in terms of timing, instruments, pitches, and volumes.
 
unknown phrase changes the pitch correctly.
We will run phrase, setting the pitch beforehand to different values and recording the pitch afterwards. The change in pitch caused by the function must match the change caused by our solution: it should increase the pitch by the specified interval twice using climbUp.
 
unknown song must return the correct result
The result returned when your song function is run must match the solution result.
 
unknown song must produce the correct note sequence
The notes added to the current track when song is called must match the solution notes in terms of timing, instruments, pitches, and volumes.
 
unknown song does not change the pitch.
We will run song, setting the pitch beforehand to different values and recording the pitch afterwards. The final pitch must match the original pitch we specified.
 
unknown Define motif with 3 parameters
Use def to define motif with 3 parameters
 
unknown Call climbUp
Within the definition of motif with 3 parameters, call climbUp or climbDown in exactly 3 places.
 
unknown Call max
Within the definition of motif with 3 parameters, call max in exactly one place.
 
unknown Define motif with 3 parameters
Use def to define motif with 3 parameters
 
unknown Call doubleNote
Within the definition of motif with 3 parameters, call doubleNote in exactly 3 places.
 
unknown Do not call addNote
Within the definition of motif with 3 parameters, do not call addNote.
 
unknown Do not call setPitch
Within the definition of motif with 3 parameters, do not call setPitch.
 
unknown Call climbUp
Within the definition of motif with 3 parameters, call climbUp or climbDown in at least one place.
 
unknown Call max
Within the definition of motif with 3 parameters, call max in at least one place.
 
unknown Use a return statement
Within the definition of motif with 3 parameters, use return _ in at least one place.
 
unknown Define phrase with 4 parameters
Use def to define phrase with 4 parameters
 
unknown Call climbUp
Within the definition of phrase with 4 parameters, call climbUp or climbDown in exactly 2 places.
 
unknown Call max
Within the definition of phrase with 4 parameters, call max in exactly one place.
 
unknown Define phrase with 4 parameters
Use def to define phrase with 4 parameters
 
unknown Call motif
Within the definition of phrase with 4 parameters, call motif in exactly 3 places.
 
unknown Do not call addNote
Within the definition of phrase with 4 parameters, do not call addNote or doubleNote.
 
unknown Do not call setPitch
Within the definition of phrase with 4 parameters, do not call setPitch.
 
unknown Call climbUp
Within the definition of phrase with 4 parameters, call climbUp or climbDown in at least one place.
 
unknown Call max
Within the definition of phrase with 4 parameters, call max in at least one place.
 
unknown Use a return statement
Within the definition of phrase with 4 parameters, use return _ in at least one place.
 
unknown Define song with 5 parameters
Use def to define song with 5 parameters
 
unknown Call max
Within the definition of song with 5 parameters, call max in exactly one place.
 
unknown Define song with 5 parameters
Use def to define song with 5 parameters
 
unknown Do not call addNote
Within the definition of song with 5 parameters, do not call addNote or doubleNote.
 
unknown Do not call setPitch
Within the definition of song with 5 parameters, do not call setPitch.
 
unknown Call phrase
Within the definition of song with 5 parameters, call phrase in exactly 2 places.
 
unknown Call max
Within the definition of song with 5 parameters, call max in at least one place.
 
unknown Use a return statement
Within the definition of song with 5 parameters, use return _ in at least one place.