I have a hard time printing musical scales efficiently in Python¶

JT¶

now playing: "Live your life" by TI, Rihanna¶

Consider the following musical defimitions, and accompanying python code. This is a fairly quick way to intuitively print the major scales.

For anyone who isn't intimately familiar with a piano/keyboard labeling, consoider the following free to use and modify image which I have used and modifed:

A scale has two components: A root key and a "tonality". We will explore all of the musical letters as root keys, but for now we will just explore major scale tonality. More on this letter.

Looking at the above piano,

In [1]:
# Defitions
# Intervals are the number of half steps between notes. I only define those necessary to write a major scale in any key
MAJOR_SECOND = 2
MAJOR_THIRD = 4
PERFECT_FOURTH = 5
PERFECT_FIFTH = 7
MAJOR_SIXTH = 9
MAJOR_SEVENTH = 11
OCTAVE = 12
In [2]:
# The following 7 keys are flat key signatures. We can generate them by taking our key with one flat (F major),
# and raising to fourths until reaching Cb major (B Major)
flat_run = ['F', 'Bb', 'Eb', 'Ab', 'Db', 'Gb', 'Cb']

# I put C in with the sharps, because when I practice my key signatures I start at the top and go right. It could just as easily be in flats.
# The following 7 keys are sharp key signatures. We can generate them by taking our key with one sharp (G major),
# and raising to fourths until reaching C# major (Db Major)
sharp_run = ['C', 'G', 'D', 'A', 'E', 'B', 'F#', 'C#']
In [3]:
#These are the notes in a flat key signature
flat_notes  = ['A', 'Bb', 'B', 'C', 'Db', 'D', 'Eb', 'E', 'F', 'Gb', 'G', 'Ab']

#These are the notes in a double flat key signature
double_flat_notes = ['A', 'Bb', 'Cb', 'C', 'Db', 'D', 'Eb', 'Fb', 'F', 'Gb', 'G', 'Ab']

sharp_notes = ['A', 'A#', 'B', 'C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#']
double_sharp_notes = ['A', 'A#', 'B', 'B#', 'C#', 'D', 'D#', 'E', 'E#', 'F#', 'G', 'G#']
In [4]:
# This is the function that generates the scale. Root is a note, is_flat is boolean.
def major_scale(root):
    
    # The notes in the flat_run list need to have different names than sharps.
    is_flat = True if root in flat_run else False
    
    # This selects the correct naming system based on is_flat, and stores it to notes
    notes = flat_notes if is_flat else sharp_notes
    
    # For Cb and C# we have different spellings. Special edge cases.
    if root == 'Cb':
        notes = double_flat_notes
    if root == 'C#':
        notes = double_sharp_notes

    
    #zero indexed note
    root_idx = notes.index(root)
    
    #major scale progression through the notes
    progression = [root_idx % OCTAVE,
                   (root_idx + MAJOR_SECOND) % OCTAVE,
                   (root_idx + MAJOR_THIRD) % OCTAVE,
                   (root_idx + PERFECT_FOURTH) % OCTAVE,
                   (root_idx + PERFECT_FIFTH) % OCTAVE,
                   (root_idx + MAJOR_SIXTH) % OCTAVE,
                   (root_idx + MAJOR_SEVENTH) % OCTAVE, 
                   (root_idx + OCTAVE) % OCTAVE]  #... + OCTAVE) % OCTAVE is uneccessary, but there for readability
    scale = list(map(lambda x: notes[x], progression))
    return scale
In [5]:
for sharp in sharp_run:
    ms = major_scale(sharp)
    print(sharp, ms)
for flat in flat_run:
    ms = major_scale(flat)
    print(flat, ms)
C ['C', 'D', 'E', 'F', 'G', 'A', 'B', 'C']
G ['G', 'A', 'B', 'C', 'D', 'E', 'F#', 'G']
D ['D', 'E', 'F#', 'G', 'A', 'B', 'C#', 'D']
A ['A', 'B', 'C#', 'D', 'E', 'F#', 'G#', 'A']
E ['E', 'F#', 'G#', 'A', 'B', 'C#', 'D#', 'E']
B ['B', 'C#', 'D#', 'E', 'F#', 'G#', 'A#', 'B']
F# ['F#', 'G#', 'A#', 'B', 'C#', 'D#', 'F', 'F#']
C# ['C#', 'D#', 'E#', 'F#', 'G#', 'A#', 'B#', 'C#']
F ['F', 'G', 'A', 'Bb', 'C', 'D', 'E', 'F']
Bb ['Bb', 'C', 'D', 'Eb', 'F', 'G', 'A', 'Bb']
Eb ['Eb', 'F', 'G', 'Ab', 'Bb', 'C', 'D', 'Eb']
Ab ['Ab', 'Bb', 'C', 'Db', 'Eb', 'F', 'G', 'Ab']
Db ['Db', 'Eb', 'F', 'Gb', 'Ab', 'Bb', 'C', 'Db']
Gb ['Gb', 'Ab', 'Bb', 'B', 'Db', 'Eb', 'F', 'Gb']
Cb ['Cb', 'Db', 'Eb', 'Fb', 'Gb', 'Ab', 'Bb', 'Cb']

The problem is that code is long. It's wasteful. There's only 12 notes that make music, but theres 4 enumerations of the 12 notes with different labelings, and two subset lists.

Let's look at a representation of the musical alphabet that might be more helpful for this called the circle of fifths. To create a circle of fifths, write a starting note at the top of the circle. The note to the left is 7 half steps LOWER on a piano, the note to the right is 7 half steps HIGHER on a piano.

Finally, lets zero index label our circle of fifths (hours 0-11 instead of 1-12, the modulo operator is easier to deal with)

The three easiest scales to remember for me are at positions 11, 0, and 1 on the circle since they have the smallest amount of accidentals (flats/sharps).

C ['C', 'D', 'E', 'F', 'G', 'A', 'B', 'C'] --> 0, 2, 4, 11, 1, 3, 5, 0

G ['G', 'A', 'B', 'C', 'D', 'E', 'F#', 'G'] --> 1, 3, 5, 0, 2, 4, 6, 1

F ['F', 'G', 'A', 'Bb', 'C', 'D', 'E', 'F'] ..> 11, 1, 3, 10,0, 2, 4, 11

Do you see the pattern? I'll do D major (index 2) as an example and show a problem that we'll run into:

First we need to select the notes that make up the D major scale. To do this looking at the circle of fifths, lets circle 1 to the left of the root note, the root note, and the five notes to the right. These notes are considered "in key" in D major, they may sound melodic if played in a D major song correctly. The other notes are considered "out of key"; except for special circumstances and style (american blues and jazz are great out of key examples) we wouldn't use these notes in a D major song; they would sound like they don't belong!

Follow the Rainbow

Next we need to define our walk (every other note):

D (2)

D, E(4)

D, E, F# (6)

Now we're in a tricky spot. The next index is 8, but Ab is not circled. It's out of key in D major. No worries, we'll just ignore all the not circles notes. In our D major circle, the mote after Db is G.

D, E, F#, G (1)

D, E, F#, G, A (3)

D, E, F#, G, A, B (5)

D, E, F#, G, A, B, Db (7) (Eww. We'll discuss this Db.)

The final note of the D major scale is D at index 2 not Eb at index 9. Remember, we skip the notes that are out of key.

Making our D major scale that we generate from the circle:

D ['D', 'E', 'F#', 'G', 'A', 'B', 'Db', 'D']

but that's different from the one that my python function had generated for us!

D ['D', 'E', 'F#', 'G', 'A', 'B', 'C#', 'D']

Instead of a C#, we've accidentally introduced a Db! Looking back at our piano diagram we're relieved to find out that it's actually the same note, but it's named wrong; the D major scale has two sharps not a sharp and a flat.

In my next post I plan to go through enharmonics (why we have different aliases for the same note), and writing code to effectively print a circle of fifths without all the unecessary re/defines and logics.