[1]:
import omusic
import omusic.chord as chord
import omusic.modes as modes
from omusic.scale import scale
from omusic import note_i2s
from omusic import note_s2i
from omusic import interval_s2i
from omusic import interval_i2s
from omusic import name_interval
from omusic import invert
from omusic import reach
from omusic import same_class
Music Theory
Musical Notations
There are may systems to represent musical notes as letters. This project uses two: NOTES_MIDI and NOTES_CLASS:
omusic.NOTES_CLASSrepresents pitch classes, whereNOTES_CLASS[0]is \(\mathrm{C}\) andNOTES_CLASS[11]is \(\mathrm{B}\). Also,NOTES_MIDI[i]is the integer notation for that class.omusic.NOTES_MIDIrepresents scientific names whereNOTES_MIDI[i]is the \(i^\mathrm{th}\) MIDI sound. That is,NOTES_MIDI[0]is \(\mathrm{C}_{0}\) andNOTES_MIDI[127]is \(\mathrm{C}_{10}\). This is the default option.
To use a music space, assign the variable to omusic.NOTES.
Check that the default notation is used:
[2]:
assert omusic.NOTES == omusic.NOTES_MIDI
Representing Notations
Internal representations of notes can be printed. For example, the \(0^\mathrm{th}\) note in omusic.NOTES_CLASS is prints to C.
The string representation is standard. Two sets of functions convert from / to it:
note_s2candnote_c2sconverts between strings andomusic.NOTES_CLASS. The optionnote_s2candnote_c2sconverts between strings andomusic.NOTES_MIDI.
Let’s begin with note_s2i and note_i2s. Some of its outputs are below.
Now, it’s worth noting that two systems of string representations exist in parallel: class and octave (e.g. “C0”) or just the class (e.g. “C”).
note_s2iaccepts both. If no octave is given, the note is assumed to be on the \(0^\mathrm{th}\) octave.note_i2salways outputs both the class and the octave.
[3]:
from tabulate import tabulate
_headr: list[str] = ["Expression", "Result"]
_expressions_to_test: list[str] = [
"note_s2i(\"C\")",
"note_s2i(\"C0\")",
"note_i2s(0)",
]
print(tabulate([[x, eval(x)] for x in _expressions_to_test],
headers=_headr))
Expression Result
-------------- --------
note_s2i("C") 0
note_s2i("C0") 0
note_i2s(0) C0
To check that two string representations have the same class (if not the same octave), call same_class. With it, assert that note_i2s and note_s2i are each other’s inverse.
[4]:
for i in range(len(omusic.NOTES_CLASS)):
assert same_class(note_i2s(note_s2i(omusic.NOTES_CLASS[i])),
omusic.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 omusic.NOTES == omusic.NOTES_MIDI:
assert note_s2i("C") == note_s2i("C0")
[6]:
from omusic import Pitch, Interval
Intervals
An interval is the musical distance between two notes. An interval between two notes is harmonic if these notes are played at the same time; otherwise, the interval is melodic. The half-step is the smallest apartness commonly used in Western omusic. A whole-step equals two half-steps.
In integer notation, an interval be denoted by a simple integer. In text however, intervals are often communicated by name. The following sections explain how these names are formed.
Generic Intervals
Generic intervals measure the difference between the staff positions of two notes. In practice, this measure ignores accidentals: \(\mathrm{C}\text{--}\mathrm{D}\) are one genetic step apart, but so are \(\mathrm{C}\text{--}\mathrm{D}\#\). This is because \(\mathrm{D}\) and \(\mathrm{D}\#\) are on the same staff.
Generic intervals, like people, have names. For example, \(\mathrm{C}\text{--}\mathrm{D}\) are a second apart. The following table lists these names.
Step difference |
Name of Interval |
|---|---|
0 |
First / Prime |
1 |
Seconds |
2 |
Thrids |
3 |
Fourths |
4 |
Fifths |
5 |
Sixths |
6 |
Sevens |
7 |
Eights |
Specific Intervals
Specific intervals are measured on both staff and half steps. For example, recall that \(\mathrm{C}\text{--}\mathrm{D}\) are a generic second apart. Because they are also 2 half steps apart, they are a major second apart. \(\mathrm{B}\text{--}\mathrm{C}\) on the other hand are a minor second apart because they are a genetic second apart while being just one half step apart. Some examples follow:
Apartness |
Name |
|---|---|
2 |
Major Second |
4 |
Major Third |
5 |
Perfect Fourth |
7 |
Perfect Fifth |
9 |
Major Sixth |
11 |
Major Seventh |
12 |
Perfect Eighth (Perfect octave) |
The terms “major” and “perfect” refer to the interval’s quality. Only the 2nds, 3rds, 4ths, 6ths, and 7ths are major intervals. The rest (1sts, 4ths, 5ths, and 8ths) are perfect intervals instead.
A minor interval has 1 fewer half step than a major interval. An augmented interval has one more than a major interval. An augmented interval has one more half step than a perfect interval. A diminished interval has one less half step. Minor intervals can be diminished by subtracting yet another half-step. The following figure illustrates these relations:
The following cell seeks to capture this behaviour. In particular:
INTERVALSmaps every name of an interval to an apartness (by half steps).apartness_to_namemaps every apartness to a set of names.name_apartness, when given two notes, returns a probable name for hte interval between them.
The name_interval function names the interval between two notes.
[7]:
_pairs: list[tuple[str, str]] = [
('C#', 'D'),
('C', 'D'),
('C', 'D#'),
('B', 'C'),
]
for __from, to in _pairs:
print(f"The interval from {__from}"
f" to {to} is"
f" {name_interval(__from, to)}")
The interval from C# to D is minor 2
The interval from C to D is major 2
The interval from C to D# is augmented 2
The interval from B to C is minor 2
Interval Cheat Sheet
The following generated cheat sheet gives possible names for an interval in half steps. To find out the actual name, reference the general interval.
[8]:
persephone: list[tuple[int, str]]\
= sorted([(b, a) for a, b
in omusic.INTERVALS.items()])
import tabulate
tabulate.tabulate((p := persephone,
[(*x, *y) for (x, y)
in zip(p[:int(len(p)/2)],
p[int(len(p)/2):])])[1],
headers=["Half-Step Difference",
"Name"]*2,
tablefmt="html")
[8]:
| Half-Step Difference | Name | Half-Step Difference | Name |
|---|---|---|---|
| 0 | augmented 7 | 5 | perfect 4 |
| 0 | diminished 2 | 6 | augmented 4 |
| 0 | perfect 8 | 6 | diminished 5 |
| 0 | prime 1 | 7 | diminished 6 |
| 1 | augmented 1 | 7 | perfect 5 |
| 1 | augmented 8 | 8 | augmented 5 |
| 1 | minor 2 | 8 | minor 6 |
| 2 | diminished 3 | 9 | diminished 7 |
| 2 | major 2 | 9 | major 6 |
| 3 | augmented 2 | 10 | augmented 6 |
| 3 | minor 3 | 10 | minor 7 |
| 4 | diminished 4 | 11 | diminished 1 |
| 4 | major 3 | 11 | diminished 8 |
| 5 | augmented 3 | 11 | major 7 |
The reach function “reaches up” from a given note by either the given apartness (in half steps) of an interval specified by name.
To show the correctness of reach, assuming that name_interval is correct, try to “reach” from every note to every other note by the name of the interval between them.
[9]:
for name_x in omusic.NOTE_NAMES:
for name_y in omusic.NOTE_NAMES:
same_class(reach(name_x, name_interval(name_x, name_y)), name_y)
Inverting Intervals
To invert a group of notes is to move the lowest note an octave higher. This is easy to implement.
Inverting an interval carries a related meaning. Suppose that inverting C-G gives G-C: inverting the interval between C-G should yield the interval between G-C. The invert_interval function captures this behaviour.
Perfect intervals always invert to perfect intervals. A factoid: inverting the perfect 4th and the perfect 5th give each other.
[10]:
assert invert(omusic.INTERVALS["perfect 5"])\
== omusic.INTERVALS["perfect 4"]\
and invert(omusic.INTERVALS["perfect 4"])\
== omusic.INTERVALS["perfect 5"]
Reaching “up” from a note by a given interval, then again by the invert of that interval, should produce that note (albeit an octave higher). Together, reach and invert_interval allows us to test this:
[11]:
for note, interval in zip(omusic.NOTES,
omusic.INTERVALS.values()):
assert same_class(note,
reach(reach(note, interval),
invert(interval)))
Scales
The author’s knowledge on chords come from two sources: (a) www.musictheory.net and (b) Play Guitar in 14 Days by Troy Nelson.
A scale is an ordered sequence of notes. In western music, a scale (particularly a diatonic scale) is constructed by counting notes from a starting note. This starting note is its home note (or tonic); the pattern of counting is either its key (if the pattern is major or minor) or its mood (if the pattern is, for example, ionian). These inconsistencies are due to historical reasons.
Mathematically, both keys and modes are patterns of intervals. Both can be found in omusic.modes. Major and Minor Scales begins with a good example.
[12]:
from omusic.modes import AEOLIAN
print(AEOLIAN)
[2, 1, 2, 2, 1, 2, 2]
Major and Minor Scales
The major scale is a heptatonic scale constructed from a specific pattern of half steps and full steps. Here, such pattners are represented as a sequence of 2s and 1s. The minor scale uses a similar pattern.
For scales, the word key can communicate two things: (a) if a scale is major or minor (“the scale is in major key”), or (b) which note is the tonic of the scale (“the scale is in the key of A”). In the latter case, the scale itself is the key. Better not think too hard about it.
Modes
The adjectives “major” and “minor” can apply to many things, from intervals to keys (modes) to scales. For convenience, this library describes all sequences of intervals as modes. See omusic.modes for pre-defined modes.
Aside: Scale Degrees
The scale degree of a note is its position on a scale, up from the tonic. The \(i^\mathrm{th}\) degree can be denoted as \(\hat{i}\).
For a heptatonic scale, these degrees are named:
Position |
Name |
|---|---|
8 |
Tonic (again) |
7 |
Leading Tone / Subtonic |
6 |
Submediant |
5 |
Dominant |
4 |
Subdominant |
3 |
Mediant |
2 |
Supertonic |
1 |
Tonic |
Note a peculiarity: the submediant (6th) does not lead into the mediant (3rd); rather, it is the “mediant” of the dominant (5th) and the subtonic (7th).
Also, the 7th node can have two names (“may”, since some tutorials just call it the supertonic): If it is one half step below the tonic, then it is the leading tone; if it is one whole step below the tonic, then it is the subtonic.
[13]:
import omusic.modes as modes
Constructing Scales
To construct a scale from a tonic and a mode, start counting from the tonic according to the mode. The construct_scale function generates scales this way.
To see that it works, let’s construct the C major scale.
[14]:
scale("C", modes.MAJOR)
[14]:
['C0', 'D0', 'E0', 'F0', 'G0', 'A0', 'B0']
Relative Scales
Relative scales contain the same notes, though not arranged in the same order. To build evidence that construct_scale is correct, see if it correctly constructs relatives.
Here’s every pair of relatives from the circle of fifthssss. Ssss. Hisssss.
[15]:
from omusic import same_class, name_interval
CIRCLE_OF_LIFE: list[tuple[str, str]]\
= [("C", "A"),
("G", "E"),
("D", "B"),
("A", "F#"),
("E", "C#"),
("B", "G#"),
("F#", "D#"),
("C#", "A#"),
("G#", "F"),
("D#", "C"),
("A#", "G")]
for major_key, minor_key in CIRCLE_OF_LIFE:
assert same_class(
scale(major_key, modes.MAJOR),
scale(minor_key, modes.MINOR),)
interval_name: str = name_interval(major_key, minor_key)
print(f"Interval from {major_key} to {minor_key}"
f" is {name_interval(major_key, minor_key)},"
f" or {omusic.INTERVALS[interval_name]} half steps.")
Interval from C to A is major 6, or 9 half steps.
Interval from G to E is major 6, or 9 half steps.
Interval from D to B is major 6, or 9 half steps.
Interval from A to F# is major 6, or 9 half steps.
Interval from E to C# is major 6, or 9 half steps.
Interval from B to G# is major 6, or 9 half steps.
Interval from F# to D# is major 6, or 9 half steps.
Interval from C# to A# is major 6, or 9 half steps.
Interval from G# to F is diminished 7, or 9 half steps.
Interval from D# to C is diminished 7, or 9 half steps.
Interval from A# to G is diminished 7, or 9 half steps.
[16]:
name_interval("A", "G")
[16]:
'minor 7'
Pentatonic Scales
A pentatonic scale is a scale with five tones instead of seven. To construct a pentatonic scale from a major heptatonic scale, take items at indices \([1, 2, 3, 5, 6]\) (assuming 1-based indexing). Constructing pentatonic minor scales is similar, but uses indices \([1, 3, 4, 5, 7]\).
The pentatonic_major and pentatonic_minor functions construct pentatonic “modes”, which can then be used to construct pentatonic scales. These functions can also scales from other modes.
You can also construct scales from pre-made modes, MAJOR_PENTATONIC and MINOR_PENTATONIC.
[17]:
from omusic.modes import pentatonic_major
from omusic.modes import pentatonic_minor
assert modes.MAJOR_PENTATONIC == pentatonic_major(modes.MAJOR)
assert modes.MINOR_PENTATONIC == pentatonic_minor(modes.MINOR)
[18]:
assert same_class(
scale("C", pentatonic_major(modes.MAJOR)),
['C', 'D', 'E', 'G', 'A']
)
assert same_class(
scale("A", pentatonic_minor(modes.MINOR)),
['A', 'C', 'D', 'E', 'G']
)
Blues Scales
Blues scales are 6-notes long. A blues minor scale is the pentatonic scale with an extra flat 5th. For example, whereas the A pentatonic scale is [‘A’, ‘C’, ‘D’, ‘E’, ‘G’], the A blues scale is [‘A’, ‘C’, ‘D’, ‘D#’, ‘E’, ‘G’] A blues major scale gets a flat third instead.
The blues_major and blues_minor functions construct blue modes.
[19]:
from omusic.modes import blues_major
from omusic.modes import blues_minor
[20]:
assert same_class(
scale('C', blues_major(modes.MAJOR)),
['C', 'D', 'D#', 'E', 'G', 'A']
)
assert same_class(
scale('A', blues_minor(modes.MINOR)),
['A', 'C', 'D', 'D#', 'E', 'G']
)
Harmonic and Melodic Minors
I see your harmonic minor and raise you a melodic minor.
Recall that the A minor is [‘A’, ‘B’, ‘C’, ‘D’, ‘E’, ‘F’, ‘G’]. Its harmonic minor is […, ‘E’, ‘F’, ‘G#’]; its melodic minor is [… ‘E’, ‘F#’, ‘G#’]
The harmonic_minor and melodic_minor functions construct these modes.
[21]:
from omusic.modes import harmonic_minor
from omusic.modes import melodic_minor
[22]:
assert same_class(
scale("A", melodic_minor(modes.MINOR)),
['A', 'B', 'C', 'D', 'E', 'F#', 'G#'])
assert same_class(
scale("A", harmonic_minor(modes.MINOR)),
['A', 'B', 'C', 'D', 'E', 'F', 'G#'])
Augmented and Diminished Scales
There are two kinds of diminished scales: the typical whole-half diminished scale (of intervals [2, 1, 2, 1, …]) and the half-whole diminished scale (of intervals [1, 2, 1, 2,…]).
[23]:
from omusic.modes import DIMINISHED_WHOLE_HALF
from omusic.modes import DIMINISHED_HALF_WHOLE
from omusic.modes import AUGMENTED
[24]:
assert same_class(
scale("A", modes.DIMINISHED_HALF_WHOLE),
['A', 'A#', 'C', 'C#', 'D#', 'E', 'F#', 'G'])
Chords
A chord is a combination of three or more notes. There are many ways to construct chords, such as constructing triads on a scale.
As you know, a triad is a triple of topological spaces \(\{P, A, B\};~A,B\prec P\) where \(P=\mathrm{int}(A)\cup\mathrm{int}(B)\).
What you might not know is the fact that the triad of a scale is a subset of notes in that scale at the prime, a third, and a fifth. This “prime” is the root of the triad.
Construct from Intervals
Triads always include the prime, in addition to a \(3^\mathrm{rd}\) and a \(5^\mathrm{th}\). Some popular triads can be constructed as follows:
Chord name |
choice of \(3^\mathrm{rd}\) |
choice of \(5^\mathrm{th}\) |
|---|---|---|
Major triad |
major |
perfect |
Minor triad |
minor |
perfect |
Augmented triad |
major |
augmented |
Diminished triad |
minor |
diminished |
Following the table, the C triad includes notes C, E, and G.
[25]:
from omusic.chord import reach_many, count_triad_major
assert reach_many("C", ["prime 1", "major 3", "perfect 5"]) ==\
count_triad_major("C") ==\
["C0", "E0", "G0"]
A set of functions construct triads this way. These are: count_triad_major, count_triad_minor, count_triad_augmented, and count_triad_diminished.
Seventh Chords
A seventh chord combines a triad with an interval of a seventh. There are five types of common seventh chords:
The dominant seventh uses a major triad and a minor seventh …
… and so on.
A set of functions generate seventh chords. These are: count_seventh_major, count_seventh_minor, count_seventh_half_diminished, count_seventh_diminished, count_seventh_minor_major, count_seventh_augmented_major, and count_seventh_augmented_minor.
[26]:
assert omusic.same_class(
chord.count_seventh_dominant("C"),
['C', 'E', 'G', 'A#'])
Diatonic Triads; Better Constructors
Every major and minor scale have seven diatonic triads, which are formed from notes on that scale.
The first triad uses the 1st, 3rd, and 5th notes counting up from the root (the root itself being the 1st). The nth triad instead counts from the nth note instead.
Here is also a good place to introduce two general functions: triad and seventh. These functions construct triads directly from a prime and a mode.
[27]:
from omusic.chord import triad
from omusic.chord import seventh
[28]:
seventh("C", modes.MAJOR, order=8)
[28]:
['D1', 'F1', 'A1', 'C2']
Note that this method is identical to the aforementioned approach of counting intervals.
[29]:
assert chord.count_triad_major("C")\
== triad("C", modes.MAJOR)
assert chord.count_seventh_augmented_major("C")\
== seventh("C", modes.AUGMENTED, "major")
Neapolitan Chords
To construct a Neapolitan chord, construct a major triad (1, 3, 5) starting with the second scale degree of another scale.
[30]:
from omusic.chord import neapolitan_chord
[31]:
neapolitan_chord("A", modes.MAJOR)
[31]:
['A#0', 'D1', 'F1']
Inverting Triads
Like inverting intervals, inverting a triad moves the lowest note up an octave.
When the bass note, the lowest note in the triad, is its root, the triad is in root position. Inverting the triad once moves it into first inversion; inverting again moves it to the second inversion.
Alternatively, the degree of inversion can be denoted by its bass note. For example, the F major triad is ['F', 'A', 'C']; its first inversion ['A', 'C', 'F'] can be denoted by F/A. The invert_triad_to function uses this notation: for example, to obtain the second inversion of a triad, invert it to its second item.
[32]:
from omusic.chord import invert_triad_to
_c_4_triad = count_triad_major("C4")
invert_triad_to(_c_4_triad, _c_4_triad[1])
[32]:
['E4', 'G4', 'C5']