Parse a description of musical notes into frequencies and durations.

Parse a description of musical notes into frequencies and durations.

As discussed in class, this assignment is optional. If you would like to complete it, I can use it to replace your lowest assignment grade from earlier in the term. However, if you’d rather not complete it, you don’t have to.

Background: eine kleine Musiktheorie

This assignment will ask you to calculate frequencies and durations for musical notes. In order to do these things, you need to understand just a very little bit of the physics behind musical notes. This builds on the physics of waves which you learn about in Physics 1051, but you don’t need to have completed Physics 1051 to use the information given below.

Frequencies and semitones

Calculating the frequency of a specific note (as you will do in the note_frequency function) depends on two things:

  1. the name of the note (A through G) and

  2. the baseline frequency of A within the given octave.

The name of the note is used to calculate a conversion factor that can be multiplied by the baseline frequency to get the final note frequency. This conversion factor will be semitone ratio $f_S = \sqrt[12]{2}$ raised to the power of the number of semitones between the note and A, given here:

Note Semitones from A

A

0

B

2

C

3

D

5

E

7

F

8

G

10

For example, in the octave for which A is 440 Hz, the frequency of the note C is given by:

\[ f_C = f_A \times \left( \sqrt[12]{2} \right)^3 = 440 \times 1.0594630943593^3 = 523.25 \textrm{ Hz} \]

We can check our calculations against a list of frequencies given on Wikipedia.

Octaves

Musical notes have the interesting property that every octave has same notes with frequencies exactly double those in the octave below. For example, our "standard" (or "tenor") A has a frequency of 440 Hz, but the A below it has a frequency of 220 Hz, then 110 Hz below that, then 55 Hz, 27.5 Hz, etc. When we change octaves, therefore, we only need to double or halve the baseline frequency that we are using to calculate note frequencies.

Note durations

The duration of a note depends on the number of beats in that note and the tempo of the overall tune. A beat is actually a period in the wave sense (i.e., $T = \frac{1}{f}$), so it’s a measure of duration. A quarter note has one beat, a half note has two beats and a whole note has four beats (at least in the kinds of music we’ll be playing!). The tempo of a tune is a frequency expressed in beats per minute, so to calculate the duration of, e.g., a whole note at 120 bpm, we simply need to apply a couple of conversion factors:

\[ 4 \times \frac{1}{120 / \textrm{min}} \times \frac{60 \textrm{ s}}{1 \textrm{ min}} = 2 \textrm{ s} \]

Objective

In this assignment, you need to write Python functions for converting descriptions of a musical tunes like "Ab4 Bb8 C8 D4 C Eb G#- Ab+" into lists or arrays of note frequencies and durations. My test code will provide you with strings that contain space-separated note descriptions, where each note is described with:

Note name (required)

A musical note name from A through G. Unless otherwise specified, our tunes will start in the octave (range) where A is 440 Hz; the remaining note frequencies in an octave can be calculated from the frequency of A using a multiplicative conversion factor. This conversion factor will depend on the number of semitones away from A that the note can be found (see details below).

Sharp or flat (optional)

A b means that the note is flat (lowered by one semitone from its usual frequency). A # means that the note is sharp (raised by one semitone). Again, see the description of semitones below. If neither sharp nor flat is specified, the frequency is whatever you get from calling note_frequency().

Octave change (optional)

If the + modifier is present, it means that this note is one octave up from the previous one. If - is present , the note is one octave down from the previous one. Changing octave changes the frequency of all following notes according to the simple mathematical relationship described below.

Note length (optional)

A measure of note length — actually the period of a note relative to a whole note. A half note (denoted by the number 2) is one-half the length of a whole note, a quarter note (denoted by the number 4) is one-quarter the length of a whole note, etc. A description of this is used to calculate note length is given below. If no length is specified, the note should have the same length as the previous note. In my tests, the first note will always have a length. I will only use the following note lengths:

Length Note Beats

1

Whole note

4

2

Half note

2

4

Quarter note

1

8

Eighth note

$\frac{1}{2}$

Assignment details

For this assignment, you need to write a Python module in a file music.py that contains four functions:

note_frequency

This function should calculate the frequency of a single note. It should have two parameters:

name (str)

The name of the note to calculate the frequency of ('A' through 'G')

baseline (float)

The frequency of 'A'

It should return the note’s frequency in Hertz.

note_duration

This function should calculate the duration of a single note. It should have two parameters:

length (int)

This will be a 1 for a whole note, 2 for a half note, 4 for a quarter note, etc.

bpm (int)

How many musical beats (also known as metronome markings, m.m.) occur in each minute.

This function should return the note’s duration in seconds.

count_notes

This function should count the number of notes that are contained in a tune. It should have one parameter:

tune (string)

A string that contains space-separated notes (e.g., "A2 G- F G A+1").

The function should return the number of notes contained in the given tune. For example, if passed the tune "A2 G- F G A+1", the function should return 5.

parse_tune

This function should parse a tune, converting it to note frequencies and durations. It should have two parameters:

tune (str)

A string containing space-separated descriptions of musical notes as described above.

bpm (int)

The number of musical beats / metronome markings / quarter notes in a minute.

The function should calculate the correct frequency (in Hz) and duration (in s) of each note, returning an iterable collection (list, tuple, etc.) of (frequency, duration) tuples.

Procedure

Please read the above description of theory carefully, then complete and submit your work to Gradescope as music.py. This file should import no external modules.

I would strongly encourage you to test your code using function calls such as the following:

# The following should evaluate to 261.62 Hz:
middle_c = note_frequency('C', 220)

# The following should evaluate to 2 s:
whole_note = note_duration(1, 120)

# A little ditty I just made up:
tune = "Ab4 Bb8 C8 D4 C Eb G#- Ab+"

# How many notes are in this tune?
notes = count_notes(tune)

# Figure out what notes would play this tune at 120 beats per minute:
results = parse_tune(tune, notes, 120)

Here are some examples of expected parse_tune() results:

Whole tune Note Frequency Duration

A2 G- F G A+1 @ tempo 120

A2

440 Hz (always start in 440 Hz octave)

1s (half note = 2 beats)

G-2

391.9954 Hz (G in the 220 Hz octave)

1s (stay with last duration)

F2

349.2282 Hz (still in the 220 Hz octave)

1s (stay with last duration)

G2

391.9954 Hz (G in the 220 Hz octave)

1s (stay with last duration)

A+1

440 Hz (back up to the 440 Hz octave)

2s (whole note = 4 beats)

Ab4 Bb8 C8 D4 C Eb G#- Ab+ @ tempo 96

Ab4

415.3047 Hz (A♭: semitone below A440)

0.625 s (quarter note = 1 beat)

Bb8

466.1638 Hz (B♭: semitone below B)

0.3125 s (eighth note = 1/2 beat)

C8

523.2511 Hz (C above 440)

0.3125 s (eighth note = 1/2 beat)

D4

587.3295 Hz (D above 440)

0.625 s (quarter note = 1 beat)

C

523.2511 Hz (C above 440)

0.625 s (stick with previous duration)

Eb

622.2540 Hz (E♭: semitone below E)

0.625 s (stick with previous duration)

G#-

415.3047 Hz (G♯ in the 220 Hz octave)

0.625 s (stick with previous duration)

Ab+

415.3047 Hz (A♭ in the 440 Hz octave)

0.625 s (stick with previous duration)

As always, assignments in this course must be done individually. You can (and are encouraged to!) work together on exercises, but you must do the assignments yourself. If in doubt, come and talk with me.

Submission

Submit your Python script (which must be named music.py) to Gradescope. As always, assignments in this course must be done individually. You can (and are encouraged to!) work together on exercises, but you must do the assignments yourself. If in doubt, come and talk with me.