Doodles: Tuning Systems
Some prelimiaries…
%pip install tabulate
import math
import itertools
from IPython.display import display, HTML, display_html, display_markdown,
Markdown
from tabulate import tabulate
type Hz = float
type Note = int
A_440_Note = 69
A_440_Hz = 440.0
def note_class(n: Note) -> tuple[int, int]:
octaves, steps = divmod(n, 12)
return octaves - 1, steps
def note_name(n: Note) -> str:
# MIDI note 24 is C1
note_names = ['C', 'C♯/D♭', 'D', 'D♯/E♭', 'E', 'F', 'F♯/G♭', 'G', 'G♯/A♭',
'A', 'A♯/B♭', 'B'] octaves, steps = note_class(n)
return f"{note_names[steps]}{octaves}"
assert note_name(24) == 'C1'
12-TET Tempering
12-tone Equal Temperament divides an octave into a twelve logarithmically-equal parts. This is convenient because it has a very compact closed-form expression.
where is the reference pitch, the pitch in hertz at which . In fact this works for any number of steps per octave:
We can implement it in python with a higher-order function like this:
from typing import Callable
def equal_tempered(n: int) -> Callable[[Note], Hz]:
return lambda x: A_440_Hz * pow(2.0, (float(x - A_440_Note) / float(n)))
def tempered12tet(n: Note) -> Hz:
return equal_tempered(12)(n)
table = tabulate(
[(str(n), note_name(n), f"{tempered12tet(n): 0.04f}") for n in range(60, 82)],
tablefmt='html',
headers=('MIDI Note', 'Note Name', 'Hz'))
display(HTML(table))
| MIDI Note | Note Name | Hz |
|---|---|---|
| 60 | C4 | 261.626 |
| 61 | C♯/D♭4 | 277.183 |
| 62 | D4 | 293.665 |
| 63 | D♯/E♭4 | 311.127 |
| 64 | E4 | 329.628 |
| 65 | F4 | 349.228 |
| 66 | F♯/G♭4 | 369.994 |
| 67 | G4 | 391.995 |
| 68 | G♯/A♭4 | 415.305 |
| 69 | A4 | 440 |
| 70 | A♯/B♭4 | 466.164 |
| 71 | B4 | 493.883 |
| 72 | C5 | 523.251 |
| 73 | C♯/D♭5 | 554.365 |
| 74 | D5 | 587.33 |
| 75 | D♯/E♭5 | 622.254 |
| 76 | E5 | 659.255 |
| 77 | F5 | 698.457 |
| 78 | F♯/G♭5 | 739.989 |
| 79 | G5 | 783.991 |
| 80 | G♯/A♭5 | 830.609 |
| 81 | A5 | 880 |
Pythagorean Tuning
Pythagorean tuning constructs intervals between notes purely out of combinations of ratios of and .
Chromatic Scale with diatonic intervals:
| U | m2 | M2 | m3 | M3 | P4 | a4 | P5 | m6 | M6 | m7 | M7 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| C | C# | D | D# | E | F | F# | G | G# | A | A# | B |
Ratios to unison for intervals:
| Interval | Ratio | Hz (over 440) | 12-TET Hz |
|---|---|---|---|
| P4 | 586.666 | = 587.329 | |
| P5 | 660.666 | = 622.253 |
Notes that do not exist in C:
E♯/F♭ B♯/C♭
Circle of Fifths
Semitones_In_P5 = 7 # There are seven semitones in a perfect fifth
Center_Note = 60 # C4
Fifths_Window = 6 # We want the table to show 6 fifths above and below Center_Note
# implementation
Start_Note = Center_Note - Fifths_Window * Semitones_In_P5
End_Note = Center_Note + (Fifths_Window + 1) * Semitones_In_P5
note_range = range(Start_Note, End_Note)
fifths_batches = itertools.batched(note_range, Semitones_In_P5)
Headings = ('Note Class', 'Fifth', 'Octave', 'Unison', 'm2', 'M2', 'm3', 'M3', 'P4', 'a4')
rows = []
for i, fifth_batch in enumerate(fifths_batches):
row = []
unison = fifth_batch[0]
octave, step = note_class(unison)
row.append(str(step))
row.append(str(i-Fifths_Window))
row.append(str(octave))
row.extend([note_name(n) for n in fifth_batch])
rows.append(row)
table = tabulate(rows,
tablefmt='html',
headers=Headings)
display(HTML(table))
| Note Class | Fifth | Octave | Unison | m2 | M2 | m3 | M3 | P4 | a4 |
|---|---|---|---|---|---|---|---|---|---|
| 6 | -6 | 0 | F♯/G♭0 | G0 | G♯/A♭0 | A0 | A♯/B♭0 | B0 | C1 |
| 1 | -5 | 1 | C♯/D♭1 | D1 | D♯/E♭1 | E1 | F1 | F♯/G♭1 | G1 |
| 8 | -4 | 1 | G♯/A♭1 | A1 | A♯/B♭1 | B1 | C2 | C♯/D♭2 | D2 |
| 3 | -3 | 2 | D♯/E♭2 | E2 | F2 | F♯/G♭2 | G2 | G♯/A♭2 | A2 |
| 10 | -2 | 2 | A♯/B♭2 | B2 | C3 | C♯/D♭3 | D3 | D♯/E♭3 | E3 |
| 5 | -1 | 3 | F3 | F♯/G♭3 | G3 | G♯/A♭3 | A3 | A♯/B♭3 | B3 |
| 0 | 0 | 4 | C4 | C♯/D♭4 | D4 | D♯/E♭4 | E4 | F4 | F♯/G♭4 |
| 7 | 1 | 4 | G4 | G♯/A♭4 | A4 | A♯/B♭4 | B4 | C5 | C♯/D♭5 |
| 2 | 2 | 5 | D5 | D♯/E♭5 | E5 | F5 | F♯/G♭5 | G5 | G♯/A♭5 |
| 9 | 3 | 5 | A5 | A♯/B♭5 | B5 | C6 | C♯/D♭6 | D6 | D♯/E♭6 |
| 4 | 4 | 6 | E6 | F6 | F♯/G♭6 | G6 | G♯/A♭6 | A6 | A♯/B♭6 |
| 11 | 5 | 6 | B6 | C7 | C♯/D♭7 | D7 | D♯/E♭7 | E7 | F7 |
| 6 | 6 | 7 | F♯/G♭7 | G7 | G♯/A♭7 | A7 | A♯/B♭7 | B7 | C8 |
We obtain the tuning ratio for each tone by either going up or down the circle of fifths to the tone we wish to use, and for each step around the circle we either multiply by (going up) or (going down). We then multiply by an integral power of 2 in order to bring the interval back into the root octave. I’m going to work out some of these and compare them with Wikipedia.
| Tone | Fifth | Octave Shift | Ratio | Reduced | Wikipedia |
|---|---|---|---|---|---|
| C | 0 | 0 | |||
| C♯/D♭ | 7 | -4 | ‼️ | ||
| D | 2 | -1 | |||
| D♯/E♭ | -3 | 1 | |||
| E | 4 | -2 | |||
| F | -1 | 1 | |||
| … |
These figures for C♯ differ from Wikipedia, this is because I marched seven fifths around the circle in the positive direction to get to C♯, instead of five fifths back, and this results in the interval being different than the closer one by exactly or about 1.4%. is a little larger than the semitone and is called the augmented unison.
If I start over…
| Tone | Interval | Fifth | Octave Shift | Ratio | Reduced | Wikipedia |
|---|---|---|---|---|---|---|
| C | U | 0 | 0 | |||
| C♯/D♭ | m2 | -5 | 3 | ✅ | ||
| D | M2 | 2 | -1 | |||
| D♯/E♭ | m3 | -3 | 1 | |||
| E | M3 | 4 | -2 | |||
| F | P4 | -1 | 1 | |||
| F♯/G♭ | a4 | 6 | -3 | |||
| G | P5 | 1 | 0 | |||
| G♯/A♭ | m6 | -4 | 3 | |||
| A | M6 | 3 | -1 | |||
| A♯/B♭ | m7 | -2 | 2 | |||
| B | M7 | 5 | -2 |
And now everything is looking better.
Automating the process
I did these charts by hand referring to the Circle of Fifths chart, but is it possible to do this programmatically?
I had the thought that as intervals get wider, the Pythagorean tones might drift further away from the 12-TET ones.
12-TET…
| Interval | 12-TET Hz | Diff |
|---|---|---|
| C4–D4 | 32.039745 | |
| C4–D5 | 325.709491 | |
| C4–D6 | 913.048982 |
Pythagorean…
| Interval | Pythagorean Hz | Diff | ∂ 12-TET |
|---|---|---|---|
| C4–D4 | 32.70375 | -3.9 cents | |
| C4–D5 | 317.0375 | -3.9 cents | |
| C4–D6 | 905.705 | -3.9 cents |