{ "cells": [ { "cell_type": "markdown", "id": "466074e1", "metadata": {}, "source": [ "# Guess Scale or Chord from Notes\n", "\n", "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.\n", "\n", "These tools were developed to help with transcription and reharmonisation. This awesome power is now yours to command.\n", "\n", "This tutorial covers two functions, `guess_scale` and `guess_chord`." ] }, { "cell_type": "code", "execution_count": 1, "id": "0354aadb", "metadata": {}, "outputs": [], "source": [ "import random\n", "random.seed(44313)\n", "from automuse.guesser import guess_scale, guess_chord, tabulate_guess, MatchResult\n", "from automuse.guesser import TEST_SCALES, TEST_SCALES_STANDARD" ] }, { "cell_type": "markdown", "id": "e0795076", "metadata": {}, "source": [ "Note that `guess` takes two arguments: (a) a set of note names and (b) scales and chords to test against.\n", "\n", "The module comes with several options for the latter:\n", "\n", "| Option | Included |\n", "| - | - |\n", "| `TEST_SCALES_STANDARD` | Major and minor |\n", "| `TEST_SCALES_MINORS` | Natural, harmonic, and melodic minors |\n", "| `TEST_SCALES_EXPANDED` | Augmented, diminished, pentatonic, and blues |\n", "| `TEST_SCALES_EXOTIC` | Whole tone and chromatic scales |\n", "| `TEST_SCALES_MODES` | From Ionian to Locrian |\n", "| `TEST_SCALES` | Every scale. Probably has perfect matches for everything. |\n", "| `TEST_TRIAD_MODES` | Triads |\n", "| `TEST_SEVENTH_MODES` | Seventh chords |\n", "| `TEST_CHORDS` | All chords |" ] }, { "cell_type": "markdown", "id": "f8a6c0ca", "metadata": {}, "source": [ "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.\n", "\n", "To find which scale contains these notes, call `guess_scale` then give the output to `tabulate_guess`.\n", "\n", "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." ] }, { "cell_type": "code", "execution_count": 2, "id": "eb42c1ec", "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
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#']
" ], "text/plain": [ "'\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n
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#']
'" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "tabulate_guess(guess_scale([\"G\", \"E\", \"C#\"],\n", " TEST_SCALES,\n", " match_order=\"any\")[:10],\n", " tablefmt=\"html\")" ] }, { "cell_type": "markdown", "id": "fd145a7b", "metadata": {}, "source": [ "### Refining the Match\n", "\n", "You, the musician, are in ultimate control of your creations. You can choose to only match against certain modes, or filter the result:" ] }, { "cell_type": "code", "execution_count": 3, "id": "29a00f52", "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
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']
" ], "text/plain": [ "'\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n
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']
'" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "tabulate_guess(guess_scale([\"G\", \"E\", \"C#\"],\n", " TEST_SCALES_STANDARD,\n", " match_order=\"any\")[:5],\n", " tablefmt=\"html\")" ] }, { "cell_type": "code", "execution_count": 4, "id": "84573585", "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
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#']
" ], "text/plain": [ "'\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n
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#']
'" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "match_result: list[MatchResult] =\\\n", " [x for x in guess_scale([\"G\", \"E\", \"C#\"],\n", " TEST_SCALES,\n", " match_order=\"any\")\n", " if x.mode == \"M\"]\n", "tabulate_guess(match_result[:5],\n", " tablefmt=\"html\")" ] }, { "cell_type": "markdown", "id": "7316bb1c", "metadata": {}, "source": [ "### Matching in Order\n", "\n", "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:\n", "\n", "* `\"any\"`, the default: No order required.\n", "\n", "* `\"order\"`: Match notes in order; other notes between matched notes are allowed.\n", "\n", "* `\"substring\"`: Match exact substrings." ] }, { "cell_type": "code", "execution_count": 5, "id": "fbd1be89", "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
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#']
" ], "text/plain": [ "'\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n
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#']
'" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "tabulate_guess(guess_scale([\"C\", \"C#\", \"E\"],\n", " TEST_SCALES,\n", " match_order=\"substring\")[:5],\n", " tablefmt=\"html\")" ] }, { "cell_type": "markdown", "id": "7e385865", "metadata": {}, "source": [ "## Match Chords\n", "\n", "The function `guess_chord` is a convenience function that matches notes against standard chords.\n", "\n", "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." ] }, { "cell_type": "code", "execution_count": 6, "id": "0942de0c", "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
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']
" ], "text/plain": [ "'\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n
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']
'" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "tabulate_guess(guess_chord(\n", " [\"B\", \"F\", \"G#\"],\n", " orders = [0],\n", " inversions = list(range(3)),\n", " match_order=\"substring\")[:10],\n", " tablefmt=\"html\")" ] }, { "cell_type": "markdown", "id": "fa4305fe", "metadata": {}, "source": [ "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." ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.10" } }, "nbformat": 4, "nbformat_minor": 5 }