Spaces:
Running on Zero
Running on Zero
| _HUMAN_PN_TO_PC = { | |
| "C": 0, | |
| "D": 2, | |
| "E": 4, | |
| "F": 5, | |
| "G": 7, | |
| "A": 9, | |
| "B": 11, | |
| } | |
| _PC_TO_FLAT_HUMAN_PN = "C Db D Eb E F Gb G Ab A Bb B".split() | |
| _PC_TO_SHARP_HUMAN_PN = "C C# D D# E F F# G G# A A# B".split() | |
| class PitchClass(int): | |
| def __new__(cls, pc): | |
| if not isinstance(pc, int): | |
| raise TypeError() | |
| if pc < 0 or pc >= 12: | |
| raise ValueError() | |
| return super().__new__(cls, pc) | |
| def as_human_pitch_name(self, enharmonics="b"): | |
| if enharmonics == "b": | |
| d = _PC_TO_FLAT_HUMAN_PN | |
| elif enharmonics == "#": | |
| d = _PC_TO_SHARP_HUMAN_PN | |
| else: | |
| raise ValueError() | |
| return HumanPitchName(d[self]) | |
| def as_lily_pitch_name(self, enharmonics="es"): | |
| if enharmonics == "es": | |
| enharmonics = "b" | |
| elif enharmonics == "is": | |
| enharmonics = "#" | |
| else: | |
| raise ValueError() | |
| return self.as_human_pitch_name(enharmonics=enharmonics).as_lily_pitch_name() | |
| class PitchInterval(int): | |
| def __new__(cls, pi): | |
| if not isinstance(pi, int): | |
| raise TypeError() | |
| return super().__new__(cls, pi) | |
| class HumanPitchName(str): | |
| def __new__(cls, pn): | |
| if not isinstance(pn, str): | |
| raise TypeError() | |
| if len(pn) == 0: | |
| raise ValueError() | |
| if pn[0] not in _HUMAN_PN_TO_PC: | |
| raise ValueError() | |
| accidentals = list(pn[1:]) | |
| if any(a not in ["b", "#"] for a in accidentals): | |
| raise ValueError() | |
| if len(set(accidentals)) > 1: | |
| raise ValueError() | |
| if len(accidentals) > 2: | |
| raise ValueError() | |
| return super().__new__(cls, pn) | |
| def as_pitch_class(self): | |
| pc = _HUMAN_PN_TO_PC[self[0]] | |
| accidental = 0 | |
| for c in self[1:]: | |
| if c == "b": | |
| accidental -= 1 | |
| elif c == "#": | |
| accidental += 1 | |
| else: | |
| assert False | |
| return PitchClass((pc + accidental) % 12) | |
| def as_lily_pitch_name(self): | |
| pn = self[0].lower() | |
| for c in self[1:]: | |
| if c == "b": | |
| pn += "es" | |
| elif c == "#": | |
| pn += "is" | |
| else: | |
| assert False | |
| return LilyPitchName(pn) | |
| class LilyPitchName(str): | |
| def __new__(cls, pn): | |
| if not isinstance(pn, str): | |
| raise TypeError() | |
| if len(pn) == 0: | |
| raise ValueError() | |
| if pn.lower() != pn: | |
| raise ValueError() | |
| if pn[0].upper() not in _HUMAN_PN_TO_PC: | |
| raise ValueError() | |
| accidentals = pn[1:] | |
| if len(accidentals) % 2 != 0: | |
| raise ValueError() | |
| accidentals = [accidentals[i : i + 2] for i in range(0, len(accidentals), 2)] | |
| if any(a not in ["es", "is"] for a in accidentals): | |
| raise ValueError() | |
| if len(set(accidentals)) > 1: | |
| raise ValueError() | |
| if len(accidentals) > 3: | |
| raise ValueError() | |
| return super().__new__(cls, pn) | |
| def as_pitch_class(self): | |
| return self.as_human_pitch_name().as_pitch_class() | |
| def as_human_pitch_name(self): | |
| pn = self[0].upper() | |
| for i in range(1, len(self), 2): | |
| if self[i : i + 2] == "es": | |
| pn += "b" | |
| elif self[i : i + 2] == "is": | |
| pn += "#" | |
| else: | |
| assert False | |
| return HumanPitchName(pn) | |