Created
October 9, 2019 20:28
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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