[113]:
import automuse as am
import automuse.chord as chord
import automuse.modes as modes
from automuse.scale import scale
from automuse import note_i2s
from automuse import note_s2i
from automuse import interval_s2i
from automuse import interval_i2s
from automuse import name_interval
from automuse import reach
from automuse import same_class

An Introduction to Music Theory

This notebook covers the basics of music theory, namely intervals, modes, scales, and chords.

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. These intervals are important – all music is the combination of melodies and harmonies.

The half-step, (also semitone) is the smallest apartness commonly used in Western music. A whole-step equals two half-steps.

In integer notation, an interval is denoted by a simple integer. In text however, intervals are often communicated by name. The following sections explain how these names are constructed.

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 / Unison

1

Second

2

Third

3

Fourth

4

Fifth

5

Sixth

6

Seven

7

Eight

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 in specific intervals. \(\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.

The terms “major” and “minor” refer to the interval’s quality. Only the 2nds, 3rds, 4ths, 6ths, and 7ths can be 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:

image

Some examples follow:

Apartness

Name

0

Prime

1

Minor Second

3

Minor Third

5

Perfect Fourth

7

Perfect Fifth

9

Major Sixth

11

Major Seventh

12

Perfect Eighth (Perfect octave)

To add, compound intervals are larger than an octave. These intervals are named by adding 7 to the generic interval.

Following these rules, there exists a injective mapping between names and integers (difference in semitones). A set of utilities in AutoMuse work with this fact:

  • INTERVALS maps every name to an integer.

  • name_interval takes two notes and returns a name for their interval.

[114]:
print(f"Augmented 2 is {am.INTERVALS["augmented 2"]} in semitones.")
_test_pairs: list[tuple[str, str]] = [
    ('C#', 'D'),
    ('C', 'D'),
    ('C', 'D#'),
    ('B', 'C'),
]

for __from, to in _test_pairs:
    print(f"The interval from {__from}"
          f" to {to} is"
          f" {name_interval(__from, to)}")
Augmented 2 is 3 in semitones.
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, choose one that corresponds to the general interval.

[115]:
persephone: list[tuple[int, str]]\
       = sorted([(b, a) for a, b
                 in am.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")
[115]:
Half-Step DifferenceName Half-Step DifferenceName
0diminished 2 7diminished 6
0prime 1 7perfect 5
1augmented 1 8augmented 5
1minor 2 8minor 6
2diminished 3 9diminished 7
2major 2 9major 6
3augmented 2 10augmented 6
3minor 3 10minor 7
4diminished 4 11diminished 1
4major 3 11diminished 8
5augmented 3 11major 7
5perfect 4 12augmented 7
6augmented 4 12perfect 8
6diminished 5 13augmented 8

The table shows an interesting fact: that the same semitone difference can have different names, depending on the generic interval. This is an instance of enharmonic equivalence in music where two things sound the same yet write differently.

Reaching by Intervals

The reach function “reaches up” from a given note by either an integer or an interval name.

To demonstrate the correctness of reach, assuming that name_interval is correct, let’s reach from every note to every other note by the name of the interval between them.

[116]:
for name_x in am.NOTE_NAMES:
    for name_y in am.NOTE_NAMES:
        same_class(reach(name_x, name_interval(name_x, name_y)), name_y)

Scales

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 mode (otherwise). These inconsistencies are due to historical reasons.

Mathematically, both keys and modes are patterns of intervals. These patterns can be found in automuse.modes.

Parts of this section are based on MusicTheory.net and Play Guitar in 14 Days.

Aside: Major and Minor

Scales, for example the major scales and minor scales, are constructed from a pattern of intervals. In this context, the word key can communicate two things: (a) if a scale is major or minor (e.g. “the scale is in major key”), or (b) the tonic (e.g. “the scale is in the key of A”). Better not think too hard about it.

Here, you see another use of the terms “major” and “minor”. These two terms can apply to a great many things, but one rule remains consistent: a sound, be it a chord, a scale, or a juxtaposition of two pitches, is major if it is stable or bright; on the other hand, sounds that are not are minor.

Aside: Scale Degrees

The scale degree of a note is its position on a scale, up from the tonic. The \(i^\mathrm{th}\) degree is denoted as \(\hat{i}\).

For a heptatonic scale, these degrees are named:

Position

Name

8 (out of scale)

Tonic (again)

7

Leading Tone / Subtonic

6

Submediant

5

Dominant

4

Subdominant

3

Mediant

2

Supertonic

1

Tonic

Note that the submediant (\(\hat{6}\)) does not lead into the mediant (\(\hat{3}\)); rather, it is the “mediant” of the dominant (\(\hat{5}\)) and the subtonic (\(\hat{7}\)).

Also, the \(\hat{7}\) note can have two names: 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. These names are often used interchangeably.

Constructing Scales

To construct a scale from a tonic and a mode, start counting semitones from the tonic according to the mode. The scale.scale function constructs scales in this manner.

At minimum, the function requires a tonic and a mode.

[117]:
scale("C", modes.MAJOR)
[117]:
['C0', 'D0', 'E0', 'F0', 'G0', 'A0', 'B0']

Alternatively, setting use_offsets=True lets scale use semitone offsets instead of intervals. This is also known as the integer notation.

[118]:
scale("C", [0, 2, 4, 5, 7, 9, 11], use_offsets=True)
[118]:
['C0', 'D0', 'E0', 'F0', 'G0', 'A0', 'B0']

A set of functions, modes.intervals_to_offsets and modes.offsets_to_intervals, translate between intervals and offset notations:

[119]:
modes.offsets_to_intervals(
    modes.intervals_to_offsets(modes.MAJOR)) ==\
    modes.MAJOR
[119]:
True

Relative Scales

Relative scales contain the same notes, though not arranged in the same order. To build evidence that scale is correct, see if it correctly constructs relatives.

Here’s every pair of relatives from the circle of fifthssss. Ssss. Hisssss. For more on the circle, see the Circle of Fifths tutorial.

[120]:
from automuse 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 {am.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.

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 \(\{1, 2, 3, 5, 6\}\) (assuming 1-based indexing). Constructing pentatonic minor scales is similar, but uses \(\{1, 3, 4, 5, 7\}\) instead.

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 two pre-made modes, MAJOR_PENTATONIC and MINOR_PENTATONIC.

[121]:
assert modes.MAJOR_PENTATONIC == modes.pentatonic_major(modes.MAJOR)
assert modes.MINOR_PENTATONIC == modes.pentatonic_minor(modes.MINOR)
[122]:
assert same_class(
    scale("C", modes.pentatonic_major(modes.MAJOR)),
    ['C', 'D', 'E', 'G', 'A']
)

assert same_class(
    scale("A", modes.pentatonic_minor(modes.MINOR)),
    ['A', 'C', 'D', 'E', 'G']
)

Blues Scales

A blues minor scale is the pentatonic scale with an extra flat \(\hat{5}\). For example, whereas the A pentatonic scale is \([\text{A}, \text{C}, \text{D}, \text{E}, \text{G}]\), the A blues scale is \([\text{A}, \text{C}, \text{D}, \text{D}\#, \text{E}, \text{G}]\). A blues major scale gets a flat third instead.

The blues_major and blues_minor functions construct blue modes. As usual, pre-made modes for major and minor are also available.

[123]:
assert modes.MAJOR_BLUES == modes.blues_major(modes.MAJOR)
assert modes.MINOR_BLUES == modes.blues_minor(modes.MINOR)

assert same_class(
    scale('C', modes.blues_major(modes.MAJOR)),
    ['C', 'D', 'D#', 'E', 'G', 'A']
)

assert same_class(
    scale('A', modes.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.

A harmonic minor has a raised \(\hat{7}\). A melodic minor has both its \(\hat{6}\) and \(\hat{7}\) raised.

The harmonic_minor and melodic_minor functions construct these modes.

[124]:
assert modes.MINOR_HARMONIC == modes.harmonic_minor(modes.MINOR)
assert modes.MINOR_MELODIC == modes.melodic_minor(modes.MINOR)

assert same_class(
    scale("A", modes.melodic_minor(modes.MINOR)),
    ['A', 'B', 'C', 'D', 'E', 'F#', 'G#'])

assert same_class(
    scale("A", modes.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, ...]\)).

[125]:
from automuse.modes import DIMINISHED_WHOLE_HALF
from automuse.modes import DIMINISHED_HALF_WHOLE
from automuse.modes import AUGMENTED
[126]:
assert same_class(
    scale("A", modes.DIMINISHED_HALF_WHOLE),
    ['A', 'A#', 'C', 'C#', 'D#', 'E', 'F#', 'G'])

Chords

Simply put, a chord is a collection of tones. There are several ways to construct harmonically interesting chords. Let’s start with the triad:

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)\).

To construct a triad, select a scale then pick its \(\hat{1}\), \(\hat{3}\), and \(\hat{5}\). The \(\hat{1}\) is the root of the chord and remains so even if it is, after the triad is transformed, no longer the lowest note.

Constructing a Triad

AutoMuse implements two ways to construct chords. Let us begin with the easiest: chord.chord. This function implements all you need to construct a chord from specifications.

To see how it works, let’s construct this abomination of a C chord:

[127]:
chord.chord(
    tonic="C4",
    mode=modes.IONIAN,
    order=0,
    add=[6, 8, 10],
    sus=[0],
    sharp=[2],
    flat=[4],
    raw_offsets=["augmented 1"]
)
[127]:
['E4', 'F4', 'G4', 'A4', 'A#4', 'B4', 'C5', 'E5']

Alternatively, you can construct chords with scale.scale. modes has several options that specify chords, instead of scales:

[128]:
scale("C4", modes.TRIAD_MAJOR)
[128]:
['C4', 'E4', 'G4']

Seventh Chords

A seventh chord combines a triad with an interval of a seventh. The chord.seventh function construct seventh chords.

You can simple pick \(\hat{1}\), \(\hat{3}\), \(\hat{5}\), and \(\hat{7}\) from a scale, or choose the quality for the \(\hat{7}\):

[129]:
print(chord.seventh("C", modes.LOCRIAN))
print(chord.seventh("C", modes.LYDIAN))
['C0', 'D#0', 'F#0', 'A#0']
['C0', 'E0', 'G0', 'B0']

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.

Both chord.chord and chord.seventh can construct chords of the given order. To make \(\text{ii}^7\), write:

[130]:
chord.seventh("C", modes.MAJOR, order=1)
[130]:
['D0', 'F0', 'A0', 'C1']

Neapolitan Chords

To construct a Neapolitan chord, construct a major triad (1, 3, 5) starting with a flat second of the major scale. We can see that it is equivalent to the second triad on a Phrygian scale:

[140]:
print(chord.chord("A", modes.PHRYGIAN, order=1))
print(chord.neapolitan_chord("A", modes.MAJOR))
['A#0', 'D1', 'F1']
['A#0', 'D1', 'F1']

Only because it is possible … you can construct a Neapolitan chord on the Phrygian scale.

[143]:
print(chord.neapolitan_chord("A", modes.PHRYGIAN))
['A0', 'C#1', 'E1']

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. After inversion, the chord’s base note remains the same, but its bass note becomes the new lowest note.

Alternatively, the degree of inversion can be denoted by its bass note. For example, the F major triad is \([\text{F}, \text{A}, \text{C}]\); its first inversion \([\text{A}, \text{C}, \text{F}]\) can be denoted by \(\text{F}/\text{A}\). This notation is called a slash chord.

The automuse.transform module contains several such transforms. It supports both notations by invert_chord_by and invert_triad_to.

[146]:
from automuse.transforms import invert_chord_to, invert_chord_by
_c_4_triad = chord.chord("C4", modes.MAJOR)
print(invert_chord_to(_c_4_triad, _c_4_triad[1]))
print(invert_chord_by(_c_4_triad, 1))
['E4', 'G4', 'C4']
['E4', 'G4', 'C4']