from __future__ import generators import sys class Note: names = ["C", "Db", "D", "Eb", "E", "F", "Gb", "G", "Ab", "A", "Bb", "B"] def __init__(self, value): if type(value) is str: self.value = self.names.index(value) else: self.value = value % 12 # It doesn't make sense to add two Notes, but you can add an interval. def __add__(self, r): return Note(self.value + r) def __cmp__(self, r): return cmp(self.value, r.value) def name(self): return self.names[self.value] def __str__(self): return self.name() def __repr__(self): return "Note(" + self.name() + ")" # This uses the octave starting at middle C. def frequency(self): return 261.625 * (1.05946309434 ** self.value) class Chord: def __init__(self, name, label, notes): self.name = name self.label = label self.notes = notes def getnotes(self, root): return [root] + [root + n for n in self.notes] def btsearch(length, values, test, provided = []): fill = [None] * length for i in values: p = provided + [i] if not test((p + fill)[:length]): continue if len(p) == length: yield p else: for v in btsearch(length, values, test, p): yield v class Stringed: def notes(self, fingering): ns = [] for (s, f) in zip(self.strings, fingering): if f is not None: ns.append(s + f) return ns def playable(self, fingering): return True def search(self, notes): def t(f): if not self.playable(f): return False for n in self.notes(f): if not n in notes: return False return True for f in btsearch(len(self.strings), range(self.scale), t): yield f def showfingering(self, fingering): ns = self.notes(fingering) for i in range(len(self.strings) - 1, -1, -1): b = ["-"] * self.scale if fingering[i] != 0: b[fingering[i] - 1] = "X" s = ("%5s |" + "".join(b) + " %2d %s") % (self.strings[i], fingering[i], ns[i]) print s def fingers(fingering): return [f for f in fingering if f is not None and f != 0] def monotonic(fs): if len(fs) < 2: return True dr = 0 for i in range(len(fs) - 1): d = cmp(fs[i], fs[i + 1]) if d != 0: if dr != 0 and dr != d: return False dr = d return True def canstretch(fs, max): if len(fs) < 2: return True ff = fs + [] ff.sort() return (ff[-1] - ff[0]) <= max def numfingers(fs, max): seen = {} for f in fs: seen[f] = 1 if len(seen.keys()) > max: return False return True class GuitarLike(Stringed): def playable(self, fingering): fs = fingers(fingering) return monotonic(fs) and canstretch(fs, self.stretch) and numfingers(fs, self.fingers) class Mandolin(GuitarLike): name = "Mandolin" strings = [Note("G"), Note("D"), Note("A"), Note("E")] scale = 12 stretch = 4 fingers = 2 class Mandola(GuitarLike): name = "Mandola" #strings = [Note("C"), Note("G"), Note("D"), Note("G")] strings = [Note("C"), Note("G"), Note("D"), Note("A")] scale = 12 stretch = 4 fingers = 3 class Guitar(GuitarLike): name = "Guitar" strings = [Note("E"), Note("A"), Note("D"), Note("G"), Note("B"), Note("E")] scale = 16 stretch = 5 fingers = 3 chords = [ Chord("major", "", [4, 7]), Chord("sustained fourth", "sus4", [5, 7]), Chord("open fifth", "5", [7]), Chord("minor", "m", [3, 7]), Chord("seventh", "7", [4, 7, 10]), Chord("seventh sustained fourth", "7sus4", [5, 7, 10]), Chord("seventh minor", "m7", [3, 7, 10]), Chord("sixth", "6", [4, 7, 9]), Chord("sixth minor", "m6", [3, 7, 9]), Chord("major seventh", "M7", [4, 7, 11]), Chord("diminished", "dim", [3, 6]), Chord("diminished #7", "dim#7", [3, 6, 11]), ] def getchord(name): if len(name) > 1 and name[1] == "b": root = Note(name[:2]) name = name[2:] else: root = Note(name[0]) name = name[1:] for c in chords: if c.label == name: return (root, c) print "can't parse chord", name sys.exit(20) if __name__ == "__main__": m = Mandola() (root, c) = getchord(sys.argv[1]) print "Chord:", str(root) + c.label for f in m.search(c.getnotes(root)): m.showfingering(f) print