Transposing chord progressions with Python

by Jon

As part of my ukulele practice, I’ve begun creating a small song and exercise book for my own reference. Whenever I start learning a new song, I add the lyrics and chords to my songbook. In the same vein, whenever I find an interesting chord progression, I create a page for it, and list the progression in every key.

For example, I wanted to add a page for the Hawaiian Vamp, or turnaround, which in C is D7 // G7 // C ////. The problem is it’s tedious work, and the tools online, at best, only let you transpose into one new key at a time. So I wrote a Python script to do bulk transpositions:

#!/usr/bin/env python

"""
tChords.py <https://jonthysell.com/>

Copyright 2013 Jon Thysell <thysell@gmail.com>

This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software.

Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions:

1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
"""

import sys
import string

_flats = ["Ab", "A", "Bb", "B", "C", "Db", "D", "Eb", "E", "F", "Gb", "G"]
_sharps = ["G#", "A", "A#", "B", "C", "C#", "D", "D#", "E", "F", "F#", "G"]
_preferFlats = True

def transposeChords(chords, index):
    """Transpose the given chords by the given index"""
    global _flats, _sharps, _preferFlats
    newChords = []
    notes = _flats if _preferFlats else _sharps

    for chord in chords:
        noteParts = (chord[:1], chord[1:])
        if (chord.find("b") == 1 or chord.find("#") == 1):
            noteParts = (chord[:2], chord[2:])

        oldNoteIndex = -1
        if (noteParts[0] in _flats):
            oldNoteIndex = _flats.index(noteParts[0])
        elif (noteParts[0] in _sharps):
            oldNoteIndex = _sharps.index(noteParts[0])

        if (oldNoteIndex == -1): # Not a note
            newChords.append("".join(noteParts))
        else:
            newNote = notes[(oldNoteIndex + index) % len(notes)]
            newChords.append("".join([newNote, noteParts[1]]))

    return newChords

def main(args):
    """Take a progression of chords and print them out transposed."""
    if (len(args) == 0):
        print "tChords.py [list of chords seperated by spaces]"
        print "ex:"
        print "tChords.py C C7 F Fm C G7 C"
    else:
        for i in range(0, len(_flats)):
            newChords = transposeChords(args, i)
            print " ".join(newChords)

if __name__ == "__main__":
    main(sys.argv[1:])

To run this you’ll need Python installed. Just save the text to a file named tChords.py, and launch it from a command line with:

python tChords.py

followed by a progression of chords you want transposed. For the Hawaiian Vamp above, I ran the following:

python tChords.py C: D7 // G7 // C ////

and the script outputted:

C: D7 // G7 // C ////
Db: Eb7 // Ab7 // Db ////
D: E7 // A7 // D ////
Eb: F7 // Bb7 // Eb ////
E: Gb7 // B7 // E ////
F: G7 // C7 // F ////
Gb: Ab7 // Db7 // Gb ////
G: A7 // D7 // G ////
Ab: Bb7 // Eb7 // Ab ////
A: B7 // E7 // A ////
Bb: C7 // F7 // Bb ////
B: Db7 // Gb7 // B ////

Which, with a quick verification against this post on the Hawaiian Vamp, looks correct! The script does come with following notes:

  • It doesn’t care about the starting key, since the goal was to output in all twelve keys.
  • It just blindly replaces the note portion of each chord and keeps whatever modifiers come after.
  • Tip: If you want to know what key each line is in, just begin your progression with the starting key like I did in the example above.
  • If the beginning of a item isn’t a note (capital letter, with or without a # or b), the entire item is kept in the transposition unchanged (like the strumming bars above).
  • By default the script uses flats over sharps (my preference), though you can change that with the _preferFlats flag.

The goal was a quick and dirty script; I only spent about 30 minutes on it, and like all good code, it’ll now make my life just a little bit easier going forward.

Do you find this script useful? Have suggestions or improvements? Say so in the comments!

/jon