{ "cells": [ { "cell_type": "code", "execution_count": 1, "id": "1e6b94a5", "metadata": {}, "outputs": [], "source": [ "import automuse" ] }, { "cell_type": "markdown", "id": "ce1cb978", "metadata": {}, "source": [ "# Machinery\n", "\n", "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.\n", "\n", "## Notations\n", "\n", "The Western system of notations denotes pitches with letters:\n", "\n", "* The **pitch space** divides evenly into octaves. The **international pitch notation** (**IPN**) starts the $0\\text{th}$ octave at around 16 Hz.\n", "\n", "* Each **octave** contains 12 pitch classes labelled $[\\text{C}, \\text{C}\\#, ...,\\text{B}]$.\n", "\n", "AutoMuse has two schemes for naming pitches:\n", "\n", "* `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}$).\n", "\n", "* `automuse.NOTES_CLASS` only uses note classes; for example `NOTES_CLASS[0]` is $\\text{C}$ and `NOTES_CLASS[-1]` is $\\text{B}$\n", "\n", "To use a naming scheme, assign it to `automuse.NOTES`. The default is `automuse.NOTES_MIDI`." ] }, { "cell_type": "code", "execution_count": 2, "id": "2824f682", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "['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']\n", "['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B']\n" ] } ], "source": [ "print(str(automuse.NOTES_MIDI))\n", "print(str(automuse.NOTES_CLASS))\n", "assert automuse.NOTES == automuse.NOTES_MIDI" ] }, { "cell_type": "markdown", "id": "3e1b898e", "metadata": {}, "source": [ "### Integer Notation\n", "\n", "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.\n", "\n", "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:\n", "\n", "* If `automuse.NOTES` is `automuse.NOTES_MIDI`:\n", " * `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.\n", " * `note_i2s` maps MIDI pitches to names.\n", "\n", "* If `automuse.NOTES` is `automuse.NOTES_CLASS`:\n", " * `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.\n", " * `notes_i2s` maps any integer $(\\text{mod}\\,12)$ to the pitch class at that index.\n", "\n", "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`).\n", "\n", "Accounting for this ambiguity, most functions in AutoMuse accepts strings only." ] }, { "cell_type": "code", "execution_count": 3, "id": "0c0b329d", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Expression Result\n", "--------------------- --------\n", "automuse.note_s2i(\"C\") 0\n", "automuse.note_s2i(\"C0\") 0\n", "automuse.note_i2s(0) C0\n" ] } ], "source": [ "from tabulate import tabulate\n", "_headr: list[str] = [\"Expression\", \"Result\"]\n", "_expressions_to_test: list[str] = [\n", " \"automuse.note_s2i(\\\"C\\\")\",\n", " \"automuse.note_s2i(\\\"C0\\\")\",\n", " \"automuse.note_i2s(0)\",\n", "]\n", "\n", "print(tabulate([[x, eval(x)] for x in _expressions_to_test],\n", " headers=_headr))" ] }, { "cell_type": "markdown", "id": "c6ab7e35", "metadata": {}, "source": [ "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`." ] }, { "cell_type": "code", "execution_count": 4, "id": "d98dce34", "metadata": {}, "outputs": [], "source": [ "for i in range(len(automuse.NOTES_CLASS)):\n", " assert automuse.same_class(\n", " automuse.note_i2s(\n", " automuse.note_s2i(automuse.NOTES_CLASS[i])),\n", " automuse.NOTES_CLASS[i])" ] }, { "cell_type": "markdown", "id": "ee0df5a7", "metadata": {}, "source": [ "An additional set of functions, `note_m2s` and `note_s2m`, always use `NOTES_MIDI`. These functions are used by the MIDI player." ] }, { "cell_type": "code", "execution_count": 5, "id": "731a31a5", "metadata": {}, "outputs": [], "source": [ "if automuse.NOTES == automuse.NOTES_MIDI:\n", " assert automuse.note_s2i(\"C\") == automuse.note_s2i(\"C0\")" ] }, { "cell_type": "markdown", "id": "39b7ccec", "metadata": {}, "source": [ "## Disambiguating Integers\n", "\n", "Many things in music can be represented as numbers. In AutoMuse, a number can mean one of three things:\n", "\n", "* An interval in semitones.\n", "\n", "* An offset from the root to a note in a scale / chord.\n", "\n", "* A pitch, as discussed.\n", "\n", "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." ] }, { "cell_type": "code", "execution_count": 6, "id": "15947c17", "metadata": {}, "outputs": [], "source": [ "from automuse import Interval, Offset, Note" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.10" } }, "nbformat": 4, "nbformat_minor": 5 }