[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_MIDI names notes in IPN, where NOTES_MIDI[0] is \(\mathrm{C}_{0}\) and NOTES_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_CLASS only uses note classes; for example NOTES_CLASS[0] is \(\text{C}\) and NOTES_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.NOTES is automuse.NOTES_MIDI:

    • note_s2i maps 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_i2s maps MIDI pitches to names.

  • If automuse.NOTES is automuse.NOTES_CLASS:

    • note_s2i maps any name to its index as a pitch class. For example, C maps to 1 and C# maps to 2. The octave, if given, is ignored.

    • notes_i2s maps 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