Guess Scale or Chord from Notes

The music theory encoded in AutoMuse allows for some interesting applications. For example, automuse.guesser.guess_scale takes a set of notes, then guesses which scale or chords the notes may be from.

These tools were developed to help with transcription and reharmonisation. This awesome power is now yours to command.

This tutorial covers two functions, guess_scale and guess_chord.

[1]:
import random
random.seed(44313)
from automuse.guesser import guess_scale, guess_chord, tabulate_guess, MatchResult
from automuse.guesser import TEST_SCALES, TEST_SCALES_STANDARD

Note that guess takes two arguments: (a) a set of note names and (b) scales and chords to test against.

The module comes with several options for the latter:

Option

Included

TEST_SCALES_STANDARD

Major and minor

TEST_SCALES_MINORS

Natural, harmonic, and melodic minors

TEST_SCALES_EXPANDED

Augmented, diminished, pentatonic, and blues

TEST_SCALES_EXOTIC

Whole tone and chromatic scales

TEST_SCALES_MODES

From Ionian to Locrian

TEST_SCALES

Every scale. Probably has perfect matches for everything.

TEST_TRIAD_MODES

Triads

TEST_SEVENTH_MODES

Seventh chords

TEST_CHORDS

All chords

Suppose we hear a melody and identify that it contains \(\text{G}\), \(\text{E}\), and \(\text{C}\#\). To transcribe, reharmonise, or improv from it, we would like to assign it a scale.

To find which scale contains these notes, call guess_scale then give the output to tabulate_guess.

The result includes a list of possible scales. While several are equally likely, it does give us some interesting information: because many dissonant scales (e.g. Diminished, Locrian) are matched, we may choose to rock it up a little. Blues also looks feasible.

[2]:
tabulate_guess(guess_scale(["G", "E", "C#"],
                           TEST_SCALES,
                           match_order="any")[:10],
               tablefmt="html")
[2]:
Key Mode Matched Unmatched Unused Check Against
C Dim H-W ['C#', 'G', 'E'][] ['F#', 'D#', 'C', 'A', 'A#']['G', 'E', 'C#']['C', 'C#', 'D#', 'E', 'F#', 'G', 'A', 'A#']
C# Dim W-H ['C#', 'G', 'E'][] ['D#', 'F#', 'C', 'A', 'A#']['G', 'E', 'C#']['C#', 'D#', 'E', 'F#', 'G', 'A', 'A#', 'C']
C# Dim H-W ['C#', 'G', 'E'][] ['G#', 'D', 'F', 'B', 'A#'] ['G', 'E', 'C#']['C#', 'D', 'E', 'F', 'G', 'G#', 'A#', 'B']
C# m (Blues) ['C#', 'G', 'E'][] ['B', 'F#', 'G#'] ['G', 'E', 'C#']['C#', 'E', 'F#', 'G', 'G#', 'B']
C# Locrian ['C#', 'G', 'E'][] ['D', 'B', 'A', 'F#'] ['G', 'E', 'C#']['C#', 'D', 'E', 'F#', 'G', 'A', 'B']
D M ['C#', 'G', 'E'][] ['D', 'B', 'A', 'F#'] ['G', 'E', 'C#']['D', 'E', 'F#', 'G', 'A', 'B', 'C#']
D m (Harmonic)['C#', 'G', 'E'][] ['D', 'F', 'A#', 'A'] ['G', 'E', 'C#']['D', 'E', 'F', 'G', 'A', 'A#', 'C#']
D m (Melodic) ['C#', 'G', 'E'][] ['D', 'F', 'B', 'A'] ['G', 'E', 'C#']['D', 'E', 'F', 'G', 'A', 'B', 'C#']
D Dim W-H ['C#', 'G', 'E'][] ['G#', 'D', 'F', 'B', 'A#'] ['G', 'E', 'C#']['D', 'E', 'F', 'G', 'G#', 'A#', 'B', 'C#']
D Ionian ['C#', 'G', 'E'][] ['D', 'B', 'A', 'F#'] ['G', 'E', 'C#']['D', 'E', 'F#', 'G', 'A', 'B', 'C#']

Refining the Match

You, the musician, are in ultimate control of your creations. You can choose to only match against certain modes, or filter the result:

[3]:
tabulate_guess(guess_scale(["G", "E", "C#"],
                           TEST_SCALES_STANDARD,
                           match_order="any")[:5],
               tablefmt="html")
[3]:
Key Mode Matched Unmatched Unused Check Against
D M ['C#', 'G', 'E'][] ['D', 'B', 'A', 'F#'] ['G', 'E', 'C#']['D', 'E', 'F#', 'G', 'A', 'B', 'C#']
B m ['C#', 'G', 'E'][] ['D', 'B', 'A', 'F#'] ['G', 'E', 'C#']['B', 'C#', 'D', 'E', 'F#', 'G', 'A']
C M ['G', 'E'] ['C#'] ['D', 'C', 'F', 'B', 'A'] ['G', 'E', 'C#']['C', 'D', 'E', 'F', 'G', 'A', 'B']
C# m ['C#', 'E'] ['G'] ['D#', 'F#', 'G#', 'B', 'A']['G', 'E', 'C#']['C#', 'D#', 'E', 'F#', 'G#', 'A', 'B']
D m ['G', 'E'] ['C#'] ['D', 'F', 'C', 'A', 'A#'] ['G', 'E', 'C#']['D', 'E', 'F', 'G', 'A', 'A#', 'C']
[4]:
match_result: list[MatchResult] =\
    [x for x in guess_scale(["G", "E", "C#"],
                            TEST_SCALES,
                            match_order="any")
     if x.mode == "M"]
tabulate_guess(match_result[:5],
               tablefmt="html")
[4]:
Key Mode Matched Unmatched Unused Check Against
D M ['C#', 'G', 'E'][] ['D', 'B', 'A', 'F#'] ['G', 'E', 'C#']['D', 'E', 'F#', 'G', 'A', 'B', 'C#']
C M ['G', 'E'] ['C#'] ['D', 'C', 'F', 'B', 'A'] ['G', 'E', 'C#']['C', 'D', 'E', 'F', 'G', 'A', 'B']
E M ['C#', 'E'] ['G'] ['D#', 'F#', 'G#', 'B', 'A']['G', 'E', 'C#']['E', 'F#', 'G#', 'A', 'B', 'C#', 'D#']
F M ['G', 'E'] ['C#'] ['D', 'F', 'C', 'A', 'A#'] ['G', 'E', 'C#']['F', 'G', 'A', 'A#', 'C', 'D', 'E']
G M ['G', 'E'] ['C#'] ['F#', 'D', 'C', 'B', 'A'] ['G', 'E', 'C#']['G', 'A', 'B', 'C', 'D', 'E', 'F#']

Matching in Order

You might wish to match against a particular progression, there is an option for that. The match_order parameter can take one of three values:

  • "any", the default: No order required.

  • "order": Match notes in order; other notes between matched notes are allowed.

  • "substring": Match exact substrings.

[5]:
tabulate_guess(guess_scale(["C", "C#", "E"],
                           TEST_SCALES,
                           match_order="substring")[:5],
               tablefmt="html")
[5]:
Key Mode Matched Unmatched Unused Check Against
F m (Harmonic)['C', 'C#', 'E'][] ['F', 'G', 'G#', 'A#']['C', 'C#', 'E']['F', 'G', 'G#', 'A#', 'C', 'C#', 'E']
F A ['C', 'C#', 'E'][] ['F', 'G#', 'A'] ['C', 'C#', 'E']['F', 'G#', 'A', 'C', 'C#', 'E']
F# m (Blues) ['C', 'C#', 'E'][] ['F#', 'A', 'B'] ['C', 'C#', 'E']['F#', 'A', 'B', 'C', 'C#', 'E']
A A ['C', 'C#', 'E'][] ['A', 'F', 'G#'] ['C', 'C#', 'E']['A', 'C', 'C#', 'E', 'F', 'G#']
A M (Blues) ['C', 'C#', 'E'][] ['A', 'B', 'F#'] ['C', 'C#', 'E']['A', 'B', 'C', 'C#', 'E', 'F#']

Match Chords

The function guess_chord is a convenience function that matches notes against standard chords.

Suppose that you would like a chord that contains exact \(\text{B}\), \(\text{F}\), and \(\text{G}\#\). guess_chord is able to check these notes against all roman numerals, all modes, and all inversions, you can call guess_chord. Be aware that because the function needs to construct a chord for every case, it takes significantly longer to run.

[6]:
tabulate_guess(guess_chord(
    ["B", "F", "G#"],
    orders = [0],
    inversions = list(range(3)),
    match_order="substring")[:10],
    tablefmt="html")
[6]:
Key Mode Matched Unmatched Unused Check Against
F Dim W-H⁶₄(I) ['B', 'F', 'G#'][] [] ['B', 'F', 'G#']['B', 'F', 'G#']
F Dim H-W⁶₄(I) ['B', 'F', 'G#'][] [] ['B', 'F', 'G#']['B', 'F', 'G#']
F Locrian⁶₄(I) ['B', 'F', 'G#'][] [] ['B', 'F', 'G#']['B', 'F', 'G#']
F Dim W-H⁶(I) ['B', 'F'] ['G#'] ['G#'] ['B', 'F', 'G#']['G#', 'B', 'F']
F Dim W-H⁺(I) ['B', 'F'] ['G#'] ['F', 'G#']['B', 'F', 'G#']['F', 'G#', 'B', 'F']
F Dim W-H⁺⁶(I) ['B', 'F'] ['G#'] ['G#', 'F']['B', 'F', 'G#']['G#', 'B', 'F', 'F']
F Dim W-H⁺⁶₄(I)['B', 'F'] ['G#'] ['F', 'G#']['B', 'F', 'G#']['B', 'F', 'F', 'G#']
F Dim H-W⁶(I) ['B', 'F'] ['G#'] ['G#'] ['B', 'F', 'G#']['G#', 'B', 'F']
F Dim H-W⁺(I) ['B', 'F'] ['G#'] ['F', 'G#']['B', 'F', 'G#']['F', 'G#', 'B', 'F']
F Dim H-W⁺⁶(I) ['B', 'F'] ['G#'] ['G#', 'F']['B', 'F', 'G#']['G#', 'B', 'F', 'F']

It would appear that … the second inversion of the diminished whole-half scale in \(\text{F}\) is a good candidate. Who’d have thought. You can perhaps reharmonise with this information.