Skip to content

Instantly share code, notes, and snippets.

@rcalsaverini
Last active June 4, 2019 15:53
Show Gist options
  • Save rcalsaverini/5bcf569fadd58e5fd48e4b1ed4004245 to your computer and use it in GitHub Desktop.
Save rcalsaverini/5bcf569fadd58e5fd48e4b1ed4004245 to your computer and use it in GitHub Desktop.
Chords from scale
import pandas as pd
from itertools import product
from tqdm import tqdm
from pychord.analyzer import get_all_rotated_notes, notes_to_positions
from pychord.analyzer import find_quality
from pychord import Chord
COMPLEXITY_CLASSES = {
1: ["", "m", "5"],
2: ["7", "m7"],
3: ["M7", "mM7", "sus2", "sus4"],
4: ["6", "9", "7+9", "7-9", "7sus4", "9sus4", "m9", "m7-5", "add9", "M9"],
5: ["7+5", "7-5", "M7+5"],
6: ["11", "add11", "7+11", "7-13"],
10: ["aug", "dim", "dim6"],
}
COMPLEXITY = {
quality: complexity
for (complexity, qualities) in COMPLEXITY_CLASSES.items()
for quality in qualities
}
def note_to_chord(notes, raise_on_error=False):
root = notes[0]
root_and_positions = [
[rotated_notes[0], notes_to_positions(rotated_notes, rotated_notes[0])]
for rotated_notes in get_all_rotated_notes(notes)
]
for temp_root, positions in root_and_positions:
quality = find_quality(positions)
if quality is None:
continue
chord_name = (
"{}{}".format(root, quality)
if temp_root == root
else "{}{}/{}".format(temp_root, quality, root)
)
try:
chord = Chord(chord_name)
yield chord
except Exception as e:
if raise_on_error:
raise e
def relative_complexity(scale):
def complexity(chord):
degree = 1 + scale.index(chord.root)
quality_complexity = COMPLEXITY[chord.quality.quality]
is_inversion = int((chord.on is not None) and (chord.on != chord.root))
return 3 * is_inversion + quality_complexity
return complexity
def iterate_scale(scale, times, verbose=0):
iterator = product(*[scale for _ in range(times)])
if verbose == 0:
yield from iterator
else:
size = len(scale) ** times
desc = f"{scale}^{times}"
yield from tqdm(iterator, desc=desc, total=size)
def chords_from_notes(notes, inversions=False):
for chord in note_to_chord(notes):
is_inversion = (chord.on is not None) and (chord.on != chord.root)
if inversions or (not is_inversion):
yield chord
def chords_from_scale(scale, inversions=False, diversity=(2, 5), verbose=0):
min_diversity, max_diversity = diversity
complexity = relative_complexity(scale)
return pd.DataFrame(
[
(k, chord, chord.root, chord.on, complexity(chord))
for k in range(min_diversity, max_diversity + 1)
for notes in iterate_scale(scale, k, verbose=verbose)
for chord in chords_from_notes(notes, inversions=inversions)
],
columns=["diversity", "chord", "root", "bass", "complexity"],
)
def chord_table(scale, inversions=False, diversity=(2, 5), verbose=0, index=None, columns=None):
complexity = relative_complexity(scale)
def complexity_sort(xs):
sorted_chords = sorted(list(xs), key=complexity)
return ", ".join([chord.chord for chord in sorted_chords])
chords = chords_from_scale(scale, inversions=inversions, diversity=diversity, verbose=verbose)
return chords.pivot_table(
columns=columns or "complexity",
index=index or "root",
values="chord",
aggfunc=complexity_sort,
).fillna("")
if __name__ == "__main__":
scale = ["E", "F", "G#", "A#", "B", "C", "D"]
table = chord_table(scale, inversions=False, columns=["complexity", "diversity"]).loc[scale]
print(table)
@rcalsaverini
Copy link
Author

A script to generate all chords related to a given scale. It looks into all groupings of two, three, four, ... notes that have a name on the pychord library listing and create a pandas DataFrame (for organization and visualization purposes) pivoting the notes by root, bass note, number of notes ("diversity") and a notion of how harmonically complex the chord.

That last part is totally ad hoc and based on my own personal feeling. One possible improvement would be to use Euler's gradus function but... well. I'm too lazy to implement that and it doesn't make perfect sense in modern temperament systems. Instead, I grouped the chords and assigned a "complexity" number like this:

  1. major, minor triads, "powerchords" (fifth diads)
  2. major/minor triads + minor seventh
  3. major/minor triads + major seventh / sus2 or sus4
  4. major/minor triads + sixths, nines and any combination thereof
  5. half diminished and other fifth/seventh chords, etc...
  6. chords involving 11ths and 13ths
  7. any diminished or augmented chord

And I add 3 to this number if there's an inversion.

@rcalsaverini
Copy link
Author

rcalsaverini commented Jun 4, 2019

Example output:

> scale = ["E", "F", "G#", "A#", "B", "C", "D"]
> chord_table(scale, inversions=False, columns=["complexity", "diversity"]).loc[scale]

Will output:

complexity 1 2 3 4 6 10
diversity 2 3 4 3 4 4 5 4 5 3
root
C C5 C Csus2, Csus4 CM7 Cadd9, C6 CM9 Cadd11
D D5 Dm Dm7 Dsus2, Dsus4 D7sus4 Dm9, D9sus4 D11
E E5 Em Em7 Esus4 E7sus4
F F5 F Fsus2 FM7 F6, Fadd9 FM9
G G5 G G7 Gsus4, Gsus2 G7sus4, G6, Gadd9 G9sus4, G9 Gadd11 G11
A A5 Am Am7 Asus4, Asus2 A7sus4 Am9, A9sus4 A11
B Bm7-5 Bdim

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment