Skip to content

Instantly share code, notes, and snippets.

@logicplace
Created October 9, 2019 20:28
Show Gist options
  • Save logicplace/765fc135311b3cc8f4391c6d85f3e1df to your computer and use it in GitHub Desktop.
Save logicplace/765fc135311b3cc8f4391c6d85f3e1df to your computer and use it in GitHub Desktop.
Some music theory stuff you can import into the REPL and play around with
from collections import OrderedDict
# Ontologies
notes = ("C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B")
len_notes = len(notes)
flats = {
"C#": "Db",
"D#": "Eb",
"F#": "Gb",
"G#": "Ab",
"A#": "Bb",
}
sharps = {y: x for x, y in flats.items()}
roman_lower = ("i", "ii", "iii", "iv", "v", "vi", "vii", "viii", "ix", "x")
roman_upper = ("I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X")
class Note(str):
def __new__(cls, letter: str):
return str.__new__(cls, letter.capitalize())
def __eq__(self, other):
l = str(self)
o = str(other).capitalize()
return flats.get(l, l) == flats.get(o, o)
def __repr__(self):
return self
def __hash__(self):
l = str(self)
if l in sharps:
return hash(sharps[l])
return super().__hash__()
def down(self, semitones: int):
note = sharps.get(self, self)
shifted = notes.index(note) - semitones
return Note(notes[shifted % len_notes])
def up(self, semitones: int):
return self.down(-semitones)
def as_flat(self):
return Note(flats[self]) if self in flats else self
def as_sharp(self):
return Note(sharps[self]) if self in sharps else self
class Scale(tuple):
returns = False
def __new__(cls, *notes: Note):
if notes[0] == notes[-1]:
ret = tuple.__new__(cls, notes[:-1])
ret.returns = True
return ret
return tuple.__new__(cls, notes)
def __getitem__(self, idx: int):
return super().__getitem__(idx % len(self))
def __repr__(self):
if self.returns:
return f"({' '.join(self)} {self[0]})"
return f"({' '.join(self)})"
def triad(self, idx):
return Chord(self[idx], self[idx+2], self[idx+4])
class Chord(frozenset):
__slots__ = ("_notes", )
def __new__(cls, *notes: Note):
return frozenset.__new__(cls, notes)
def __init__(self, *notes: Note):
super().__init__()
self._notes = notes
def __repr__(self):
return "{%s}" % (",".join(self._notes),)
def __iter__(self):
return iter(self._notes)
class NoteKeyedDict(dict):
def __init__(self, d={}):
super().__init__()
for k, v in d.items():
self[k] = v
def __getitem__(self, key):
if isinstance(key, Note):
return super().__getitem__(key)
else:
return super().__getitem__(Note(key))
def __setitem__(self, key, value):
if isinstance(key, Note):
super().__setitem__(key, value)
else:
super().__setitem__(Note(key), value)
def reletter(scale):
scale = tuple(scale)
if any(x in scale[i+1] for i, x in enumerate(scale[:-1])):
return Scale(*(x.as_flat() for x in scale))
return Scale(*scale)
# Generate major keys
majors = NoteKeyedDict()
for i in range(len_notes):
# whole whole half whole whole whole half
major = reletter(
Note(notes[(i+j) % len_notes])
for j in (0, 2, 4, 5, 7, 9, 11, 12)
)
majors[major[0]] = major
# Generate minor keys
minors = NoteKeyedDict()
for scale in majors.values():
minor = reletter(x.down(3) for x in scale)
minors[minor[0]] = minor
# Generate triad chords
# Generate major triads (4, 3)
major_triads = NoteKeyedDict()
for x in notes:
n = Note(x)
major_triads[x] = Chord(n, n.up(4), n.up(7))
# Generate minor triads (3, 4)
minor_triads = NoteKeyedDict()
for x in notes:
n = Note(x)
minor_triads[x] = Chord(n, n.up(3), n.up(7))
def roman_of(i, triad):
if triad in major_triads:
return roman_upper[i], triad
elif triad in minor_triads:
return roman_lower[i], triad
return str(i), triad
# Major/minor scale to triad chords
triads_by_major_key, triads_by_minor_key = (
NoteKeyedDict({
letter: OrderedDict(
roman_of(i, scale.triad(i))
for i in range(len(scale))
)
for letter, scale in keys.items()
})
for keys in (majors, minors)
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment