In [216]:
from music21 import *
from random import choice as rand_choice
In [379]:
def dfs(row, consonance, subsets, recurse=False, prev=[]):
# single element
if len(row) < 2 and consonance[0]:
if not recurse:
subsets.append([row[0]])
return [row[0]]
# consonant pair
subset = prev + [row[0]]
for idx, el in enumerate(row[1:]):
if consonance[el - row[0]]:
subset += dfs(row[idx + 1:], consonance, subsets, True, subset)
subsets.append(subset)
return []
def find_consonant_subsets(pcs, consonance):
subsets = []
for idx in range(len(pcs)):
row = pcs[idx:]
dfs(row, consonance, subsets) # dfs fills subsets
return subsets
def chord_to_intervals(ch):
intervals = []
for i in range(len(ch) - 1):
intervals.append(ch[i + 1] - ch[i])
return intervals
MAJOR = [0,4,7]
MINOR = [0,3,7]
DOMINANT = [0,4,7,10]
# If it's a major or minor triad, rotate the pcset to root position
def rotatetomode(pcs):
all_rotations = [pcs[i:] + pcs[:i] for i in range(len(pcs))]
for rotation in all_rotations:
rooted = [(x - rotation[0]) % 12 for x in rotation]
if rooted == MAJOR or rooted == MINOR or rooted == DOMINANT:
return rotation
return pcs
# Add non-base pcs as extensions
def addextensions(base, allpcs):
extensionpcs = list(set(allpcs) - set(base))
return base + [x + 12 for x in extensionpcs]
# Additional steps for GCT to ensure unique encodings
def optimalchoice(choices):
# Dyads: select 5th over 4th, 7th over 2nd
if len(choices[0][1]) == 2:
for choice in choices:
if sum(choice[1]) > 6: # 6 semitones
return choice
for choice in choices:
# Select dominant chords with the correct root
if choice[1] == DOMINANT:
return choice
# Prefer chords with intervals larger than major 2nd (i.e. to select minor 7ths or major chords with add 6th)
for choice in choices:
intervals = chord_to_intervals(choice[1])
if all(x > 2 for x in intervals):
return choice
return rand_choice(choices)
In [380]:
# GCT algorithm
# t = tonic pitch class
# v = binary consonance vector of length 12. v[i] denotes whether an interval of i semitones is consonant
# c = music21.chord.Chord
def GCT(t, v, c):
# Create pitch class set and remove duplicate pcs
pcset = list(set([x.midi % 12 for x in c.pitches]))
pcset.sort()
# Find consonant subsets using DFS
subsets = find_consonant_subsets(pcset, v)
# Sort by length and grab the longest ones
subsets.sort(key = len)
maxsubsets = [x for x in subsets if len(x) == len(subsets[-1])]
rotated = map(rotatetomode, maxsubsets)
# Special case: fully diminished chords
for i in range(len(rotated)):
ch = rotated[i]
if all(x == 3 for x in chord_to_intervals(ch)):
rotated += [ch[i:] + ch[:i] for i in range(len(ch))]
# Add extensions and select root
rotated = map(list, list(set(map(tuple,rotated))))
withextensions = [addextensions(x, pcset) for x in rotated]
withroot = [((x[0] - t) % 12, map(lambda e: (e - x[0]) % 12, x)) for x in withextensions]
print withroot
# Return the optimal choice to ensure unique encodings
return withroot[0] if len(withroot) == 1 else optimalchoice(withroot)
In [381]:
gmajor = 7
cmajor = 0
v = [1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0] # Consonance vector
c1 = chord.Chord([60, 62, 66, 69, 74])
c2 = chord.Chord([50, 60, 62, 65, 69])
c3 = chord.Chord([62, 68, 77, 71])
print GCT(gmajor, v, c1)
print GCT(cmajor, v, c2)
print GCT(cmajor, v, c3)
In [376]:
c3 = chord.Chord([50, 60, 62, 65, 69])
print GCT(cmajor, v, c3)
In [377]:
dmajor = 2
c4 = chord.Chord(['F#3', 'D4', 'A4'])
c5 = chord.Chord(['E3', 'G3', 'G4', 'C#5'])
c6 = chord.Chord(['D3', 'A3', 'F#4', 'D4'])
c7 = chord.Chord(['G3', 'B3', 'E4', 'E5'])
c8 = chord.Chord(['A3', 'C#4', 'G4', 'E5'])
c9 = chord.Chord(['D3', 'A3', 'F#4', 'D5'])
print GCT(dmajor, v, c4)
print GCT(dmajor, v, c5)
print GCT(dmajor, v, c6)
print GCT(dmajor, v, c7)
print GCT(dmajor, v, c8)
print GCT(dmajor, v, c9)
In [378]:
# Tough cases
c10 = chord.Chord(['G3', 'C4', 'G4', 'C5']) # Should choose a fifth
c11 = chord.Chord(['C3', 'D4'])
c12 = chord.Chord(['D3', 'D4'])
c13 = chord.Chord(['C4', 'C#4', 'D4', 'D#4'])
print GCT(cmajor, v, c10) # Should choose a fifth [0,7]
print GCT(cmajor, v, c11) # Should choose a minor seventh [0, 10]
print GCT(cmajor, v, c12) # Should choose unison
print GCT(cmajor, v, c13) # Should choose minor third with strange extensions
In [ ]:
In [ ]: