[1]:
import automuse
Machinery
In its simplest form, music theory concerns notes and intervals between notes. Notes and intervals form scales, which in turn produce chords. This notebook explains how AutoMuse represents these constructs.
Notations
The Western system of notations denotes pitches with letters:
The pitch space divides evenly into octaves. The international pitch notation (IPN) starts the \(0\text{th}\) octave at around 16 Hz.
Each octave contains 12 pitch classes labelled \([\text{C}, \text{C}\#, ...,\text{B}]\).
AutoMuse has two schemes for naming pitches:
automuse.NOTES_MIDInames notes in IPN, whereNOTES_MIDI[0]is \(\mathrm{C}_{0}\) andNOTES_MIDI[-1]is is \(\mathrm{B}_{9}\). Also,NOTES_MIDI[i]gives the name of the \(\mathrm{i}^\text{th}\) MIDI pitch, up to 121 (\(\mathrm{B}_{9}\)).automuse.NOTES_CLASSonly uses note classes; for exampleNOTES_CLASS[0]is \(\text{C}\) andNOTES_CLASS[-1]is \(\text{B}\)
To use a naming scheme, assign it to automuse.NOTES. The default is automuse.NOTES_MIDI.
[2]:
print(str(automuse.NOTES_MIDI))
print(str(automuse.NOTES_CLASS))
assert automuse.NOTES == automuse.NOTES_MIDI
['C0', 'C#0', 'D0', 'D#0', 'E0', 'F0', 'F#0', 'G0', 'G#0', 'A0', 'A#0', 'B0', 'C1', 'C#1', 'D1', 'D#1', 'E1', 'F1', 'F#1', 'G1', 'G#1', 'A1', 'A#1', 'B1', 'C2', 'C#2', 'D2', 'D#2', 'E2', 'F2', 'F#2', 'G2', 'G#2', 'A2', 'A#2', 'B2', 'C3', 'C#3', 'D3', 'D#3', 'E3', 'F3', 'F#3', 'G3', 'G#3', 'A3', 'A#3', 'B3', 'C4', 'C#4', 'D4', 'D#4', 'E4', 'F4', 'F#4', 'G4', 'G#4', 'A4', 'A#4', 'B4', 'C5', 'C#5', 'D5', 'D#5', 'E5', 'F5', 'F#5', 'G5', 'G#5', 'A5', 'A#5', 'B5', 'C6', 'C#6', 'D6', 'D#6', 'E6', 'F6', 'F#6', 'G6', 'G#6', 'A6', 'A#6', 'B6', 'C7', 'C#7', 'D7', 'D#7', 'E7', 'F7', 'F#7', 'G7', 'G#7', 'A7', 'A#7', 'B7', 'C8', 'C#8', 'D8', 'D#8', 'E8', 'F8', 'F#8', 'G8', 'G#8', 'A8', 'A#8', 'B8', 'C9', 'C#9', 'D9', 'D#9', 'E9', 'F9', 'F#9', 'G9', 'G#9', 'A9', 'A#9', 'B9', 'C10', 'C#10', 'D10', 'D#10', 'E10', 'F10', 'F#10', 'G10']
['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B']
Integer Notation
Notes in string form can be represented as integers, often to facilitate computing. A set of functions note_s2i and note_i2s convert between these representations.
The integer representation of a note is its position in the naming scheme; as such it depends on the current automuse.NOTES setting. This means each notes can have several representations:
If
automuse.NOTESisautomuse.NOTES_MIDI:note_s2imaps names in[C0, C#0, ..., C7]to MIDI pitches in \({0,...,127}\). Names without an octabe (e.g.C) are assumed to be on octave 0.note_i2smaps MIDI pitches to names.
If
automuse.NOTESisautomuse.NOTES_CLASS:note_s2imaps any name to its index as a pitch class. For example,Cmaps to 1 andC#maps to 2. The octave, if given, is ignored.notes_i2smaps any integer \((\text{mod}\,12)\) to the pitch class at that index.
Furthermore, while note_s2i can accept notes with both sharps and flats, note_i2s only uses sharps (e.g. it always outputs "C#" and never Db).
Accounting for this ambiguity, most functions in AutoMuse accepts strings only.
[3]:
from tabulate import tabulate
_headr: list[str] = ["Expression", "Result"]
_expressions_to_test: list[str] = [
"automuse.note_s2i(\"C\")",
"automuse.note_s2i(\"C0\")",
"automuse.note_i2s(0)",
]
print(tabulate([[x, eval(x)] for x in _expressions_to_test],
headers=_headr))
Expression Result
--------------------- --------
automuse.note_s2i("C") 0
automuse.note_s2i("C0") 0
automuse.note_i2s(0) C0
To check that two hames have the same class (if not the same octave), call same_class. Let’s assert that note_i2s and note_s2i are each other’s inverse with respect to same_class.
[4]:
for i in range(len(automuse.NOTES_CLASS)):
assert automuse.same_class(
automuse.note_i2s(
automuse.note_s2i(automuse.NOTES_CLASS[i])),
automuse.NOTES_CLASS[i])
An additional set of functions, note_m2s and note_s2m, always use NOTES_MIDI. These functions are used by the MIDI player.
[5]:
if automuse.NOTES == automuse.NOTES_MIDI:
assert automuse.note_s2i("C") == automuse.note_s2i("C0")
Disambiguating Integers
Many things in music can be represented as numbers. In AutoMuse, a number can mean one of three things:
An interval in semitones.
An offset from the root to a note in a scale / chord.
A pitch, as discussed.
Enforcing distinction between these usages through the type checker would lead to undue verbosity. As such the distinction is only hinted at … with type hints.
[6]:
from automuse import Interval, Offset, Note