Basic Music Theory in ~200 Lines of Python

Last modified on April 20, 2021

Suppose: the final code for this textual content is most seemingly discovered right here as a Github gist. There’s additionally a fabulous dialogue on Hacker Records with lots of feedback that's most seemingly of passion.


I’m a self-taught guitarist of a protracted time, and love hundreds of self-taught musicians,
am woefully inept at (Western) music perception.

So naturally, I decided to write down some code.

This textual content explains the very fundamentals of Western music perception in round 200 strains
of Python.

We are in a place to first see on the notes in Western music perception, spend them to win the
chromatic scale in a given key, after which to mix it with interval system to
win frequent scales and chords.

In the damage, we're in a place to see at modes, which can be complete collections of scales derived
from frequent scales, that's most seemingly outdated to evoke further refined moods and atmospheres
than the chuffed-unhappy dichotomy that basic and minor scales present.

The Twelve Notes

The musical alphabet of Western music consists of the letters A by G, and so they
describe utterly completely different pitches of notes.

We are in a place to bid the musical alphabet with the next guidelines in Python:

alphabet=['A', 'B', 'C', 'D', 'E', 'F', 'G']

On the alternative hand, these notes won't be evenly spaced in their frequencies. To win a further
even spacing between pitches, we now devour the next twelve notes:

notes_basic=[
    ['A'],
    ['A#', 'Bb'],
    ['B'],
    ['C'],
    ['C#', 'Db'],
    ['D'],
    ['D#', 'Eb'],
    ['E'],
    ['F'],
    ['F#', 'Gb'],
    ['G'],
    ['G#', 'Ab'],
]

There are 4 issues to show masks right here: first, every and every of the notes are a half of step
or semitone aside, 2nd, the contrivance this is represented is by an elective
trailing picture (often called an unintentional) to show masks a half of-step elevate (sharp, ♯) or a half of-step
reducing (flat, ♭) of the disagreeable show masks, and third, the above notes merely loop
assist and supply over, nonetheless on the following octave.

In the damage, you’ll look that almost all of these notes
are represented by lists containing higher than one title: these are enharmonic equivalents,
a love contrivance of asserting that the an identical show masks can devour utterly completely different “spellings”.
So as an illustration the show masks half of a step above A is A♯, alternatively it's going to possibly perchance even be conception
of as half of a step beneath B, and may thus be often called B♭. For historic
causes, there are not any sharps or residences between the notes B/C, and E/F.

The basic factor to be acutely aware on why we would like these equivalents is that, after we supply as much as win frequent scales
(basic, minor and the modes), consecutive notes must supply up with consecutive letters.
Having enharmonic equivalents with utterly completely different alphabets permits us to win
these scales correctly.

If reality be instructed, for explicit keys, the above enharmonic notes won't be ample.
In expose to satisfy the “utterly different-alphabets-for-consecutive-notes rule”, we discontinue up having to spend
double sharps and double residences that elevate or decrease a show masks by a stout step.
These scales in general devour equivalents that produce not require these double
accidentals
, nonetheless for completeness, we're in a place to embrace all doable enharmonic
equivalents by rewriting our notes as follows:

notes=[
    ['B#',  'C',  'Dbb'],
    ['B##', 'C#', 'Db'],
    ['C##', 'D',  'Ebb'],
    ['D#',  'Eb', 'Fbb'],
    ['D##', 'E',  'Fb'],
    ['E#',  'F',  'Gbb'],
    ['E##', 'F#', 'Gb'],
    ['F##', 'G',  'Abb'],
    ['G#',  'Ab'],
    ['G##', 'A',  'Bbb'],
    ['A#',  'Bb', 'Cbb'],
    ['A##', 'B',  'Cb'],
]

Chromatic Scales

The chromatic scale is the simplest scale doable, and simply consists of the final
(twelve) semitones between an octave of a given key (the basic show masks in a scale, additionally
often called the tonic).

We are in a place to generate a chromatic scale for any given key very with out issues: (i) get dangle of the index of
the show masks in our notes guidelines, after which (ii) left-rotate the notes guidelines that again and again.

Finding the Index of a Given Suppose

Let’s write a straightforward attribute to look out a selected show masks in this guidelines:

def find_note_index(scale, search_note): 
    ''' Given a scale, get dangle of the index of a selected show masks '''
    for index, show masks in enumerate(scale):
        
        
        if type(show masks)==guidelines:
            if search_note in show masks:
                return index
        elif type(show masks)==str:
            if search_note==show masks:
                return index

The find_note_index() attribute takes as parameters a sequence of notes (scale),
and a show masks to look out (search_note), and returns the index by a straightforward linear
search. We tackle two circumstances throughout the loop: (i) the put the geared up scale consists
of explicit individual notes (love our alphabet guidelines above), or (ii) the put it consists of
a guidelines of enharmonic equivalents (love our notes or notes_basic lists above).
Here is an occasion of how the attribute works for each:

>>> find_note_index(notes, 'A')    # notes is a guidelines of lists
9
>>> find_note_index(alphabet, 'A') # alphabet is a guidelines of notes
0

Left-Rotating a Scale

We are in a place to now write a attribute to rotate a given scale by n steps:

def rotate(scale, n): 
    ''' Left-rotate a scale by n positions. '''
    return scale[n:] + scale[:n]

We slice the scale guidelines at educate n and alternate the two halves. Here is
an occasion of rotating our alphabet guidelines three locations (which brings the show masks D
to the entrance):

>>> alphabet
['A', 'B', 'C', 'D', 'E', 'F', 'G']
>>> rotate(alphabet, 3)
['D', 'E', 'F', 'G', 'A', 'B', 'C']

Producing a Chromatic Scale in a Given Key

We are in a place to now finally write our chromatic() attribute that generates a chromatic
scale for a given key by rotating the notes array:

def chromatic(key): 
    ''' Generate a chromatic scale in a given key. '''
    
    
    num_rotations=find_note_index(notes, key)
    return rotate(notes, num_rotations)

The chromatic() attribute above finds the index of the geared up key inside the notes
guidelines (the utilization of our find_note_index() attribute), after which rotates it by that amount
to lift it to the entrance (the utilization of our rotate() attribute). Here is an occasion of
producing the D chromatic scale:

>>> import pprint
>>> pprint.pprint(chromatic('D'))
[['C##', 'D', 'Ebb'],
 ['D#', 'Eb', 'Fbb'],
 ['D##', 'E', 'Fb'],
 ['E#', 'F', 'Gbb'],
 ['E##', 'F#', 'Gb'],
 ['F##', 'G', 'Abb'],
 ['G#', 'Ab'],
 ['G##', 'A', 'Bbb'],
 ['A#', 'Bb', 'Cbb'],
 ['A##', 'B', 'Cb'],
 ['B#', 'C', 'Dbb'],
 ['B##', 'C#', 'Db']]

For chromatic scales, one usually makes use of sharps
when ascending and residences when descending.
On the alternative hand, for now, we scoot away enharmonic equivalents correct as they're; we're in a place to see
straightforward rob the lawful show masks to spend later.

Intervals

Intervals specify the relative distance between notes.

The notes of a chromatic scale can therfore be given names primarily based totally on their relative distance from
the tonic or root show masks. Beneath are the normal names for each and every show masks, ordered
equal to the indexes inside the notes guidelines:

intervals=[
    ['P1', 'd2'],  
    ['m2', 'A1'],  
    ['M2', 'd3'],  
    ['m3', 'A2'],  
    ['M3', 'd4'],  
    ['P4', 'A3'],  
    ['d5', 'A4'],  
    ['P5', 'd6'],  
    ['m6', 'A5'],  
    ['M6', 'd7'],  
    ['m7', 'A6'],  
    ['M7', 'd8'],  
    ['P8', 'A7'],  
]

All as quickly as extra, the an identical show masks can devour utterly completely different interval names. As an occasion,
the foundation show masks is most seemingly conception to be as a supreme unison or an diminished
2nd.

Picking Out Notes from Enharmonic Equivalents

Given a chromatic scale in a given key, and an interval title inside the above
array, we're in a place to pin level the particular show masks to spend (and filter it out from a dwelling
of enharmonic equivalents). Let’s see on the elemental resolution to supply this.

As an occasion, let’s see at straightforward get dangle of the show masks equal to M3, or
the elemental third interval, from the D chromatic scale.

  1. From our intervals array, we're in a place to see that the index at which we get dangle of M3 is 4. That is 'M3' in intervals[4]==Exact.
  2. Now we see on the an identical index in our D chromatic scale (modulo its size). We uncover that chromatic('D')[4] is the guidelines of notes ['E##', 'F#', 'Gb'].
  3. The amount in M3 (i.e., the three) signifies the alphabet we now must spend, with 1 indicating the foundation alphabet. So as an illustration, for the elemental of D, 1=D, 2=E, 3=F, 4=G, 5=A, 6=B, 7=C, 8=D… and many others. So we now must detect a show masks in our guidelines of notes (['E##', 'F#', 'Gb']) containing the alphabet F. That’s the show masks F#.
  4. Conclusion: the elemental third (M3) relative to D is F#.

Programmatically Labeling Intervals for a Given Key

We are in a place to write down a reasonably straightforward attribute to use this common sense for us programmatically, and provides us a dict mapping all interval names to the merely show masks names in a given key:

def make_intervals_standard(key): 
    
    labels={}
    
    
    chromatic_scale=chromatic(key)
    
    
    alphabet_key=rotate(alphabet, find_note_index(alphabet, key[0]))
    
    
    for index, interval_list in enumerate(intervals):
    
        
        notes_to_search=chromatic_scale[index % len(chromatic_scale)]
        
        for interval_name in interval_list:
            
            diploma=int(interval_name[1]) - 1 
            
            
            alphabet_to_search=alphabet_key[degree % len(alphabet_key)]
            
            try:
                show masks=[x for x in notes_to_search if x[0]==alphabet_to_search][0]
            excluding:
                show masks=notes_to_search[0]
            
            labels[interval_name]=show masks

    return labels

And right here is the dict we win assist for the elemental of C:

>>> import pprint
>>> pprint.pprint(make_intervals_standard('C'), sort_dicts=Spurious)
{'P1': 'C',
 'd2': 'Dbb',
 'm2': 'Db',
 'A1': 'C#',
 'M2': 'D',
 'd3': 'Ebb',
 'm3': 'Eb',
 'A2': 'D#',
 'M3': 'E',
 'd4': 'Fb',
 'P4': 'F',
 'A3': 'E#',
 'd5': 'Gb',
 'A4': 'F#',
 'P5': 'G',
 'd6': 'Abb',
 'm6': 'Ab',
 'A5': 'G#',
 'M6': 'A',
 'd7': 'Bbb',
 'm7': 'Bb',
 'A6': 'A#',
 'M7': 'B',
 'd8': 'Cb',
 'P8': 'C',
 'A7': 'B#'}

Interval Formulas

We are in a place to now specify system, or teams of notes, the utilization of interval names, and
be able to contrivance them to any key that we would like:

def make_formula(components, labeled): 
    '''
    Given a comma-separated interval components, and a dwelling of labeled
    notes in a key, return the notes of the components.
    '''
    return [labeled[x] for x in components.cut up(',')]

Valuable Scale Formula

As an occasion, the components for a basic scale is:

components='P1,M2,M3,P4,P5,M6,M7,P8'

We are in a place to spend this to generate the elemental scale with out issues for a amount of keys as proven beneath:

>>> for key in alphabet:
>>>     print(key, make_formula(components, make_intervals_standard(key)))
C ['C', 'D', 'E', 'F', 'G', 'A', 'B', 'C']
D ['D', 'E', 'F#', 'G', 'A', 'B', 'C#', 'D']
E ['E', 'F#', 'G#', 'A', 'B', 'C#', 'D#', 'E']
F ['F', 'G', 'A', 'Bb', 'C', 'D', 'E', 'F']
G ['G', 'A', 'B', 'C', 'D', 'E', 'F#', 'G']
A ['A', 'B', 'C#', 'D', 'E', 'F#', 'G#', 'A']
B ['B', 'C#', 'D#', 'E', 'F#', 'G#', 'A#', 'B']

Prettifying Scales

Let’s additionally quickly write a attribute to print scales in a nicer contrivance:

def dump(scale, separator=' '): 
    '''
    Ravishing-print the notes of a scale. Replaces b and # characters
    for unicode flat and sharp symbols.
    '''
    return separator.be a part of(['{:.format(x) for x in scale]) 
                    .change('b', 'u266d') 
                    .change('#', 'u266f')

Here is a nicer output the utilization of the lawful unicode characters:

>>> for key in alphabet:
>>>     scale=make_formula(components, make_intervals_standard(key))
>>>     print('{}: {}'.format(key, dump(scale)))
C: C   D   E   F   G   A   B   C
D: D   E   F♯  G   A   B   C♯  D
E: E   F♯  G♯  A   B   C♯  D♯  E
F: F   G   A   B♭  C   D   E   F
G: G   A   B   C   D   E   F♯  G
A: A   B   C♯  D   E   F♯  G♯  A
B: B   C♯  D♯  E   F♯  G♯  A♯  B

The utilization of Valuable-Scale Intervals for Formulas

Read More

Similar Products:

Recent Content