Home Projects Blog
  back to projects

Procedurally Generated Etudes


Introduction

I have recently decided to commit myself to learning to play the electric bass. It has been going well so far, and now I need some etudes to practice 2-5-1’s. Rather than find some online, I’ve decided to make them myself, and what better way to write a bunch of etudes than to trick a computer into doing it for me?

The 2-5-1 Progression and Etude Design

The 2-5-1 chord progression has been documented heavily elsewhere, so all I will write here is that it is the most common chord progression used in jazz, often being a smaller part of a larger progression. To practice this progression, I want an etude that has this progression in all twelve keys (not counting enharmonic keys, which arguably I should count). I also want the progression to be in context, so we should throw in some random other chords in the key.

iReal Pro

iReal Pro is an app that plays chord progressions. Conveniently, the file format that it uses is just a URL, and they have enough documentation that we can generate our own files. That makes iReal Pro a convenient tool to realize this project.

The Code

The source code for this is almost small enough to post the whole thing, but I’ll go for some highlights. First we set up some information:


 chords = {
        'B': ['B^7', 'C#-7', 'D#-7', 'E^7', 'F#7', 'G#-7', 'A#o7'],
        'E': ['E^7', 'F#-7', 'G#-7', 'A^7', 'B7', 'C#-7', 'D#o7'],
        'A': ['A^7', 'B-7', 'C#-7', 'D^7', 'E7', 'F#-7', 'G#o7'],
        'D': ['D^7', 'E-7', 'F#-7', 'G^7', 'A7', 'B-7', 'C#o7'],
        'G': ['G^7', 'A-7', 'B-7', 'C^7', 'D7', 'E-7', 'F#o7'],
        'C': ['C^7', 'D-7', 'E-7', 'F^7', 'G7', 'A-7', 'Bo7'],
        'F': ['F^7', 'G-7', 'A-7', 'Bb^7', 'C7', 'D-7', 'Eo7'],
        'Bb': ['Bb^7', 'C-7', 'D-7', 'Eb^7', 'F7', 'G-7', 'Ao7'],
        'Eb': ['Eb^7', 'F-7', 'G-7', 'Ab^7', 'Bb7', 'C-7', 'Do7'],
        'Ab': ['Ab^7', 'Bb-7', 'C-7', 'Db^7', 'Eb7', 'F-7', 'Go7'],
        'Db': ['Db^7', 'Eb-7', 'F-7', 'Gb^7', 'Ab7', 'Bb-7', 'Co7'],
        'Gb': ['Gb^7', 'Ab-7', 'Bb-7', 'Cb^7', 'Db7', 'Eb-7', 'Fo7'],
    }

    chord_ind = [0, 1, 2, 3, 4, 5, 6]
    sequence = [2, 5, 1, 4, 0]

    chord_combos = list(permutations(chord_ind, 2))
    for i in range(len(sequence) - 1):
        chord_combos.remove((sequence[i], sequence[i+1]))

The chords dict contains, for each key, the seven diatonic 7th chords. Since arrays in python are zero indexed (as they should be!), the indices of each chord (listed in chord_ind) go from 0 to 6. I’ve encoded the 2-5-1 pattern in the sequence list (with a bonus 3-6 thrown in the beginning there). Because of the zero indexing, each chord has one number lower.

We also set up chord_combos, which is a list of all combinations of two different diatonic chords. When we select the random chords after the 2-5-1, we will pick an unused combination of chords, so we can ensure a good variety of transitions between chords. We remove the ones in our 2-5-1 progression, because we’re going to be practicing those ones quite enough.


    song = []
    keys = list(chords.keys())
    random.shuffle(keys)

    for key in keys:
        i = random.randint(0, 2)
        key_chords = []
        # Add the 6-2-5-1 progression here
        for c in range(i, len(sequence)):
            key_chords.append(sequence[c])
        # Add a few pairs of other chords
        while len(chord_combos) > 0:
            combo = random.choice(chord_combos)
            for c in combo:
                key_chords.append(c)
                if tuple(key_chords[-2:-1]) in chord_combos:
                    chord_combos.remove(tuple(key_chords[-2:-1]))
            if random.random() < 0.666:
                break
        print(key, key_chords)
	...

For each key (chosen in a random order), we select either a 2-5-1, 6-2-5-1, or 3-6-2-5-1 at random (to keep us on our toes). Then we select a few not-yet-used pairs of diatonic chord from our list of combinations and add them to the song, striking off each pair we use (and any pairs that arise between the pairs). This algorithm can create pairs-between-the-pairs that we have already used, but it’s a small enough problem that I’ll let it slide for this project.

After each pair, we have a 2 in 3 chance of moving on to the next key. In theory, this means we can exhaust all of the combinations before we run out of 2-5-1’s, but that’s probably not likely.

        ....
        key_chords_text = []
        for i, c in enumerate(key_chords):
            k = chords[key][c]
            text = ""
            if annotated and i == 0:
                text += "{0}".format(key)
                if chord_numbers:
                    text += ": "
            if chord_numbers:
                text += str(c + 1)

            if len(text) > 0:
                k = "<{0}>{1}".format(text, k)

            key_chords_text.append(k)

        line = " |".join(key_chords_text)

        if repeats:
            line = "{" + line + " }"
        elif annotated:
            line = "[" + line + " ]"
        else:
            line = " |" + line

        song.append(line)

Here’s the other half of that loop, where we turn our chord numbers in the the actual chord names that iReal Pro expects. annotated, chord_numbers, and repeats are boolean variables selected based on the etude number, so that each etude I generate can be different from each other.

    song_string = sig + "".join(song) + " Z"

    link = "=".join([
        title,
        '',
        name,
        style,
        key,
        'n',
        song_string,
        '',
        str(tempo),
        str(num_repeats),
        '',
        ''
    ])

For near-completeness, here’s where we combine all of the lines together into a song signature, and then stick that into a link. This project is using the newer irealb:// format, which has a couple of extra parameters and is URI encoded.

The Results

Now that we have a function to generate an etude, what better use of electricity but to generate one hundred of them? Because the entire playlist can be encoded in a link, I can put that link in this post, and if you are reading this from a device that has iReal Pro installed, you can download the list. That’s actually the entire reason I brushed the dust off this website, because I want to load the playlist onto my phone, and this is the easiest way for me to do that.

If I make any other etudes, I probably won’t write up an explanation here, but I will add them to this list.