n | tones_list = ('C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B') | n | TONES = ('C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B') |
| | | INTERVAL_NAMES = ('unison', 'minor 2nd', 'major 2nd', 'minor 3rd', 'major 3rd', 'perfect 4th', 'diminished 5th', 'perfect 5th', 'minor 6th', 'major 6th', 'minor 7th', 'major 7th') |
| tone_to_index = {tone: index for index, tone in enumerate(tones_list)} | | tone_to_index = {tone: index for index, tone in enumerate(TONES)} |
| index_to_tone = {index: tone for index, tone in enumerate(tones_list)} | | index_to_tone = {index: tone for index, tone in enumerate(TONES)} |
| | | |
| interval_names = { | | |
| 0: 'unison', | | |
| 1: 'minor 2nd', | | |
| 2: 'major 2nd', | | |
| 3: 'minor 3rd', | | |
| 4: 'major 3rd', | | |
| 5: 'perfect 4th', | | |
| 6: 'diminished 5th', | | |
| 7: 'perfect 5th', | | |
| 8: 'minor 6th', | | |
| 9: 'major 6th', | | |
| 10: 'minor 7th', | | |
| 11: 'major 7th' | | |
| } | | |
| | | |
| | | |
| class Tone: | | class Tone: |
| def __init__(self, name): | | def __init__(self, name): |
| if name not in tone_to_index: | | if name not in tone_to_index: |
| raise ValueError(f"Invalid tone name: {name}") | | raise ValueError(f"Invalid tone name: {name}") |
| self.name = name | | self.name = name |
| self.index = tone_to_index[name] | | self.index = tone_to_index[name] |
| | | |
| def __str__(self): | | def __str__(self): |
| return self.name | | return self.name |
| | | |
| def __eq__(self, other): | | def __eq__(self, other): |
| return isinstance(other, Tone) and self.index == other.index | | return isinstance(other, Tone) and self.index == other.index |
| | | |
| def __hash__(self): # We need it for sorting | | def __hash__(self): # We need it for sorting |
| return hash(self.index) | | return hash(self.index) |
| | | |
| def __add__(self, other): | | def __add__(self, other): |
| if isinstance(other, Tone): | | if isinstance(other, Tone): |
| return Chord(self, other) | | return Chord(self, other) |
| elif isinstance(other, Interval): | | elif isinstance(other, Interval): |
| new_index = (self.index + other.semitones) % 12 | | new_index = (self.index + other.semitones) % 12 |
| new_name = index_to_tone[new_index] | | new_name = index_to_tone[new_index] |
| return Tone(new_name) | | return Tone(new_name) |
| else: | | else: |
| raise TypeError("Invalid operation") | | raise TypeError("Invalid operation") |
| | | |
| def __sub__(self, other): | | def __sub__(self, other): |
| if isinstance(other, Tone): | | if isinstance(other, Tone): |
| semitones = (self.index - other.index) % 12 | | semitones = (self.index - other.index) % 12 |
| return Interval(semitones) | | return Interval(semitones) |
| elif isinstance(other, Interval): | | elif isinstance(other, Interval): |
| new_index = (self.index - other.semitones) % 12 | | new_index = (self.index - other.semitones) % 12 |
| new_name = index_to_tone[new_index] | | new_name = index_to_tone[new_index] |
| return Tone(new_name) | | return Tone(new_name) |
| else: | | else: |
| raise TypeError("Invalid operation") | | raise TypeError("Invalid operation") |
| | | |
| | | |
| class Interval: | | class Interval: |
| def __init__(self, semitones): | | def __init__(self, semitones): |
| self.semitones = semitones | | self.semitones = semitones |
| | | |
| def __str__(self): | | def __str__(self): |
| semitones_mod = self.semitones % 12 | | semitones_mod = self.semitones % 12 |
n | name = interval_names[semitones_mod] | n | name = INTERVAL_NAMES[semitones_mod] |
| return name | | return name |
| | | |
| def __add__(self, other): | | def __add__(self, other): |
| if isinstance(other, Interval): | | if isinstance(other, Interval): |
| total_semitones = self.semitones + other.semitones | | total_semitones = self.semitones + other.semitones |
| return Interval(total_semitones) | | return Interval(total_semitones) |
| else: | | else: |
| raise TypeError("Invalid operation") | | raise TypeError("Invalid operation") |
| | | |
| def __neg__(self): | | def __neg__(self): |
| # need it for a_sharp_minor_chord = d_minor_chord.transposed(-Interval(4)) case | | # need it for a_sharp_minor_chord = d_minor_chord.transposed(-Interval(4)) case |
| return Interval(-self.semitones) | | return Interval(-self.semitones) |
| | | |
| | | |
| class Chord: | | class Chord: |
| def __init__(self, root, *tones): | | def __init__(self, root, *tones): |
| if not isinstance(root, Tone): | | if not isinstance(root, Tone): |
| raise TypeError("Root must be a Tone") | | raise TypeError("Root must be a Tone") |
| self.root = root | | self.root = root |
| all_tones = {root} | | all_tones = {root} |
| for tone in tones: | | for tone in tones: |
| if not isinstance(tone, Tone): | | if not isinstance(tone, Tone): |
| raise TypeError("All tones must be Tone instances") | | raise TypeError("All tones must be Tone instances") |
| all_tones.add(tone) | | all_tones.add(tone) |
| if len(all_tones) < 2: | | if len(all_tones) < 2: |
| raise TypeError("Cannot have a chord made of only 1 unique tone") | | raise TypeError("Cannot have a chord made of only 1 unique tone") |
| self.tones = all_tones | | self.tones = all_tones |
| | | |
| def sort_tone(self, tone): | | def sort_tone(self, tone): |
n | | n | if tone.index >= self.root.index: |
| return (tone.index - self.root.index) % 12 | | return tone.index - self.root.index |
| | | |
| | | return tone.index + (12 - self.root.index) |
| | | |
| def __str__(self): | | def __str__(self): |
n | | n | tones_copy = self.tones.copy() |
| | | tones_copy.remove(self.root) |
| sorted_tones = sorted(self.tones, key=self.sort_tone) | | sorted_tones = sorted(tones_copy, key=self.sort_tone) |
| | | |
| | | sorted_tones = [self.root] + list(sorted_tones) |
| return '-'.join(str(tone) for tone in sorted_tones) | | return '-'.join(str(tone) for tone in sorted_tones) |
| | | |
| def is_minor(self): | | def is_minor(self): |
| for tone in self.tones: | | for tone in self.tones: |
| if tone == self.root: | | if tone == self.root: |
| continue | | continue |
| interval = (tone.index - self.root.index) % 12 | | interval = (tone.index - self.root.index) % 12 |
| if interval == 3: | | if interval == 3: |
| return True | | return True |
| return False | | return False |
| | | |
| def is_major(self): | | def is_major(self): |
| for tone in self.tones: | | for tone in self.tones: |
| if tone == self.root: | | if tone == self.root: |
| continue | | continue |
| interval = (tone.index - self.root.index) % 12 | | interval = (tone.index - self.root.index) % 12 |
| if interval == 4: | | if interval == 4: |
| return True | | return True |
| return False | | return False |
| | | |
| def is_power_chord(self): | | def is_power_chord(self): |
| return not self.is_minor() and not self.is_major() | | return not self.is_minor() and not self.is_major() |
| | | |
| def __add__(self, other): | | def __add__(self, other): |
| if isinstance(other, Tone): | | if isinstance(other, Tone): |
| new_tones = self.tones.copy() | | new_tones = self.tones.copy() |
| new_tones.add(other) | | new_tones.add(other) |
| return Chord(self.root, *(new_tones - {self.root})) | | return Chord(self.root, *(new_tones - {self.root})) |
| elif isinstance(other, Chord): | | elif isinstance(other, Chord): |
| new_tones = self.tones.union(other.tones) | | new_tones = self.tones.union(other.tones) |
| return Chord(self.root, *(new_tones - {self.root})) | | return Chord(self.root, *(new_tones - {self.root})) |
| else: | | else: |
| raise TypeError("Invalid operation") | | raise TypeError("Invalid operation") |
| | | |
| def __sub__(self, other): | | def __sub__(self, other): |
| if not isinstance(other, Tone): | | if not isinstance(other, Tone): |
| raise TypeError("Invalid operation") | | raise TypeError("Invalid operation") |
| if other not in self.tones: | | if other not in self.tones: |
| raise TypeError(f"Cannot remove tone {other} from chord {self}") | | raise TypeError(f"Cannot remove tone {other} from chord {self}") |
| new_tones = self.tones.copy() | | new_tones = self.tones.copy() |
| new_tones.remove(other) | | new_tones.remove(other) |
| if len(new_tones) < 2: | | if len(new_tones) < 2: |
| raise TypeError("Cannot have a chord made of only 1 unique tone") | | raise TypeError("Cannot have a chord made of only 1 unique tone") |
t | | t | new_root = self.root |
| | | if other == self.root: |
| | | new_root = next(iter(new_tones)) |
| | | new_tones.remove(new_root) |
| | | |
| return Chord(self.root, *(new_tones - {self.root})) | | return Chord(new_root, *(new_tones - {self.root})) |
| | | |
| def transposed(self, interval): | | def transposed(self, interval): |
| if not isinstance(interval, Interval): | | if not isinstance(interval, Interval): |
| raise TypeError("Interval must be an Interval instance") | | raise TypeError("Interval must be an Interval instance") |
| semitones = interval.semitones | | semitones = interval.semitones |
| new_tones = set() | | new_tones = set() |
| for tone in self.tones: | | for tone in self.tones: |
| new_index = (tone.index + semitones) % 12 | | new_index = (tone.index + semitones) % 12 |
| new_tone = Tone(index_to_tone[new_index]) | | new_tone = Tone(index_to_tone[new_index]) |
| new_tones.add(new_tone) | | new_tones.add(new_tone) |
| new_root_index = (self.root.index + semitones) % 12 | | new_root_index = (self.root.index + semitones) % 12 |
| new_root = Tone(index_to_tone[new_root_index]) | | new_root = Tone(index_to_tone[new_root_index]) |
| return Chord(new_root, *(new_tones - {new_root})) | | return Chord(new_root, *(new_tones - {new_root})) |
06.11.2024 14:23
06.11.2024 14:23