1CHROMATIC_SCALE = (
2 'C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'
3)
4
5TONES_COUNT = 12
6MINOR_THIRD_LEN = 3
7MAJOR_THIRD_LEN = 4
8
9
10class Tone:
11
12 def __init__(self, tone_name):
13 self.tone_name = tone_name
14
15 def __str__(self):
16 return self.tone_name
17
18 def __eq__(self, other):
19 return self.tone_name == other.tone_name
20
21 def __ne__(self, other):
22 return self.tone_name != other.tone_name
23
24 def __add__(self, other):
25 if isinstance(other, Tone):
26 return Chord(self,other)
27 elif isinstance(other, Interval):
28 new_tone_idx = (CHROMATIC_SCALE.index(self.tone_name) + other.semitones_count) % TONES_COUNT
29 return Tone(CHROMATIC_SCALE[new_tone_idx])
30
31 def __sub__(self, other):
32 if isinstance(other, Tone):
33 self_idx = CHROMATIC_SCALE.index(self.tone_name)
34 other_idx = CHROMATIC_SCALE.index(other.tone_name)
35 interval_length = (self_idx - other_idx) % TONES_COUNT
36 return Interval(interval_length)
37 elif isinstance(other, Interval):
38 new_tone_idx = (CHROMATIC_SCALE.index(self.tone_name) - other.semitones_count) % TONES_COUNT
39 return Tone(CHROMATIC_SCALE[new_tone_idx])
40
41
42class Interval:
43
44 _INTERVAL_NAMES = (
45 'unison', 'minor 2nd', 'major 2nd', 'minor 3rd', 'major 3rd',
46 'perfect 4th', 'diminished 5th', 'perfect 5th', 'minor 6th',
47 'major 6th', 'minor 7th', 'major 7th'
48 )
49
50 def __init__(self, semitones_count):
51 self.semitones_count = semitones_count
52
53 def __str__(self):
54 return self._INTERVAL_NAMES[self.semitones_count % TONES_COUNT]
55
56 def __add__(self, other):
57 if isinstance(other, Tone):
58 raise TypeError('Invalid operation')
59 elif isinstance(other, Interval):
60 new_count = (self.semitones_count + other.semitones_count)
61 return Interval(new_count)
62
63 def __sub__(self, other):
64 if isinstance(other, Tone):
65 raise TypeError('Invalid operation')
66
67 def __neg__(self):
68 return Interval(-self.semitones_count)
69
70
71class Chord:
72
73 def __init__(self, *tones):
74 if not self._are_valid_tones(*tones):
75 raise TypeError('Cannot have a chord made of only 1 unique tone')
76 self.sorted_tones = self._sort_tones(*tones)
77
78 @staticmethod
79 def _are_valid_tones(*tones):
80 tones_set = {str(tone) for tone in tones}
81 return len(tones_set) > 1
82
83 @staticmethod
84 def _sort_tones(*tones):
85 root = tones[0]
86 sorted_tones = [root]
87 end_idx = CHROMATIC_SCALE.index(str(root))
88 iter_idx = (end_idx + 1) % TONES_COUNT
89
90 while iter_idx != end_idx:
91 current_tone = Tone(CHROMATIC_SCALE[iter_idx])
92 if current_tone in tones and current_tone not in sorted_tones:
93 sorted_tones.append(current_tone)
94 iter_idx += 1
95 iter_idx %= TONES_COUNT
96 return sorted_tones
97
98 def __str__(self):
99 return '-'.join(str(tone) for tone in self.sorted_tones)
100
101 def is_minor(self):
102 root_idx = CHROMATIC_SCALE.index(str(self.sorted_tones[0]))
103 end_tone_idx = (root_idx + MINOR_THIRD_LEN) % TONES_COUNT
104 return Tone(CHROMATIC_SCALE[end_tone_idx]) in self.sorted_tones
105
106 def is_major(self):
107 root_idx = CHROMATIC_SCALE.index(str(self.sorted_tones[0]))
108 end_tone_idx = (root_idx + MAJOR_THIRD_LEN) % TONES_COUNT
109 return Tone(CHROMATIC_SCALE[end_tone_idx]) in self.sorted_tones
110
111 def is_power_chord(self):
112 return not self.is_minor() and not self.is_major()
113
114 def __add__(self, other):
115 if isinstance(other, Tone):
116 new_tones = self.sorted_tones + [other]
117 return Chord(*new_tones)
118 elif isinstance(other, Chord):
119 new_tones = self.sorted_tones[:]
120 new_tones.extend(other.sorted_tones)
121 return Chord(*new_tones)
122
123 def __sub__(self, other):
124 if isinstance(other, Tone):
125 if other not in self.sorted_tones:
126 raise TypeError(f'Cannot remove tone {str(other)} from chord {str(self)}')
127 new_tones = self.sorted_tones[:]
128 new_tones.remove(other)
129 return Chord(*new_tones)
130
131 def transposed(self, interval):
132 transpose = lambda tone : tone + interval
133 transposed_tones = list(map(transpose, self.sorted_tones))
134 return Chord(*transposed_tones)
.....................................
----------------------------------------------------------------------
Ran 37 tests in 0.001s
OK
| t | 1 | CHROMATIC_SCALE = ( | t | 1 | CHROMATIC_SCALE = ( |
| 2 | 'C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B' | 2 | 'C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B' | ||
| 3 | ) | 3 | ) | ||
| 4 | 4 | ||||
| 5 | TONES_COUNT = 12 | 5 | TONES_COUNT = 12 | ||
| 6 | MINOR_THIRD_LEN = 3 | 6 | MINOR_THIRD_LEN = 3 | ||
| 7 | MAJOR_THIRD_LEN = 4 | 7 | MAJOR_THIRD_LEN = 4 | ||
| 8 | 8 | ||||
| 9 | 9 | ||||
| 10 | class Tone: | 10 | class Tone: | ||
| 11 | 11 | ||||
| 12 | def __init__(self, tone_name): | 12 | def __init__(self, tone_name): | ||
| 13 | self.tone_name = tone_name | 13 | self.tone_name = tone_name | ||
| 14 | 14 | ||||
| 15 | def __str__(self): | 15 | def __str__(self): | ||
| 16 | return self.tone_name | 16 | return self.tone_name | ||
| 17 | 17 | ||||
| 18 | def __eq__(self, other): | 18 | def __eq__(self, other): | ||
| 19 | return self.tone_name == other.tone_name | 19 | return self.tone_name == other.tone_name | ||
| 20 | 20 | ||||
| 21 | def __ne__(self, other): | 21 | def __ne__(self, other): | ||
| 22 | return self.tone_name != other.tone_name | 22 | return self.tone_name != other.tone_name | ||
| 23 | 23 | ||||
| 24 | def __add__(self, other): | 24 | def __add__(self, other): | ||
| 25 | if isinstance(other, Tone): | 25 | if isinstance(other, Tone): | ||
| 26 | return Chord(self,other) | 26 | return Chord(self,other) | ||
| 27 | elif isinstance(other, Interval): | 27 | elif isinstance(other, Interval): | ||
| 28 | new_tone_idx = (CHROMATIC_SCALE.index(self.tone_name) + other.semitones_count) % TONES_COUNT | 28 | new_tone_idx = (CHROMATIC_SCALE.index(self.tone_name) + other.semitones_count) % TONES_COUNT | ||
| 29 | return Tone(CHROMATIC_SCALE[new_tone_idx]) | 29 | return Tone(CHROMATIC_SCALE[new_tone_idx]) | ||
| 30 | 30 | ||||
| 31 | def __sub__(self, other): | 31 | def __sub__(self, other): | ||
| 32 | if isinstance(other, Tone): | 32 | if isinstance(other, Tone): | ||
| 33 | self_idx = CHROMATIC_SCALE.index(self.tone_name) | 33 | self_idx = CHROMATIC_SCALE.index(self.tone_name) | ||
| 34 | other_idx = CHROMATIC_SCALE.index(other.tone_name) | 34 | other_idx = CHROMATIC_SCALE.index(other.tone_name) | ||
| 35 | interval_length = (self_idx - other_idx) % TONES_COUNT | 35 | interval_length = (self_idx - other_idx) % TONES_COUNT | ||
| 36 | return Interval(interval_length) | 36 | return Interval(interval_length) | ||
| 37 | elif isinstance(other, Interval): | 37 | elif isinstance(other, Interval): | ||
| 38 | new_tone_idx = (CHROMATIC_SCALE.index(self.tone_name) - other.semitones_count) % TONES_COUNT | 38 | new_tone_idx = (CHROMATIC_SCALE.index(self.tone_name) - other.semitones_count) % TONES_COUNT | ||
| 39 | return Tone(CHROMATIC_SCALE[new_tone_idx]) | 39 | return Tone(CHROMATIC_SCALE[new_tone_idx]) | ||
| 40 | 40 | ||||
| 41 | 41 | ||||
| 42 | class Interval: | 42 | class Interval: | ||
| 43 | 43 | ||||
| 44 | _INTERVAL_NAMES = ( | 44 | _INTERVAL_NAMES = ( | ||
| 45 | 'unison', 'minor 2nd', 'major 2nd', 'minor 3rd', 'major 3rd', | 45 | 'unison', 'minor 2nd', 'major 2nd', 'minor 3rd', 'major 3rd', | ||
| 46 | 'perfect 4th', 'diminished 5th', 'perfect 5th', 'minor 6th', | 46 | 'perfect 4th', 'diminished 5th', 'perfect 5th', 'minor 6th', | ||
| 47 | 'major 6th', 'minor 7th', 'major 7th' | 47 | 'major 6th', 'minor 7th', 'major 7th' | ||
| 48 | ) | 48 | ) | ||
| 49 | 49 | ||||
| 50 | def __init__(self, semitones_count): | 50 | def __init__(self, semitones_count): | ||
| 51 | self.semitones_count = semitones_count | 51 | self.semitones_count = semitones_count | ||
| 52 | 52 | ||||
| 53 | def __str__(self): | 53 | def __str__(self): | ||
| 54 | return self._INTERVAL_NAMES[self.semitones_count % TONES_COUNT] | 54 | return self._INTERVAL_NAMES[self.semitones_count % TONES_COUNT] | ||
| 55 | 55 | ||||
| 56 | def __add__(self, other): | 56 | def __add__(self, other): | ||
| 57 | if isinstance(other, Tone): | 57 | if isinstance(other, Tone): | ||
| 58 | raise TypeError('Invalid operation') | 58 | raise TypeError('Invalid operation') | ||
| 59 | elif isinstance(other, Interval): | 59 | elif isinstance(other, Interval): | ||
| 60 | new_count = (self.semitones_count + other.semitones_count) | 60 | new_count = (self.semitones_count + other.semitones_count) | ||
| 61 | return Interval(new_count) | 61 | return Interval(new_count) | ||
| 62 | 62 | ||||
| 63 | def __sub__(self, other): | 63 | def __sub__(self, other): | ||
| 64 | if isinstance(other, Tone): | 64 | if isinstance(other, Tone): | ||
| 65 | raise TypeError('Invalid operation') | 65 | raise TypeError('Invalid operation') | ||
| 66 | 66 | ||||
| 67 | def __neg__(self): | 67 | def __neg__(self): | ||
| 68 | return Interval(-self.semitones_count) | 68 | return Interval(-self.semitones_count) | ||
| 69 | 69 | ||||
| 70 | 70 | ||||
| 71 | class Chord: | 71 | class Chord: | ||
| 72 | 72 | ||||
| 73 | def __init__(self, *tones): | 73 | def __init__(self, *tones): | ||
| 74 | if not self._are_valid_tones(*tones): | 74 | if not self._are_valid_tones(*tones): | ||
| 75 | raise TypeError('Cannot have a chord made of only 1 unique tone') | 75 | raise TypeError('Cannot have a chord made of only 1 unique tone') | ||
| 76 | self.sorted_tones = self._sort_tones(*tones) | 76 | self.sorted_tones = self._sort_tones(*tones) | ||
| 77 | 77 | ||||
| 78 | @staticmethod | 78 | @staticmethod | ||
| 79 | def _are_valid_tones(*tones): | 79 | def _are_valid_tones(*tones): | ||
| 80 | tones_set = {str(tone) for tone in tones} | 80 | tones_set = {str(tone) for tone in tones} | ||
| 81 | return len(tones_set) > 1 | 81 | return len(tones_set) > 1 | ||
| 82 | 82 | ||||
| 83 | @staticmethod | 83 | @staticmethod | ||
| 84 | def _sort_tones(*tones): | 84 | def _sort_tones(*tones): | ||
| 85 | root = tones[0] | 85 | root = tones[0] | ||
| 86 | sorted_tones = [root] | 86 | sorted_tones = [root] | ||
| 87 | end_idx = CHROMATIC_SCALE.index(str(root)) | 87 | end_idx = CHROMATIC_SCALE.index(str(root)) | ||
| 88 | iter_idx = (end_idx + 1) % TONES_COUNT | 88 | iter_idx = (end_idx + 1) % TONES_COUNT | ||
| 89 | 89 | ||||
| 90 | while iter_idx != end_idx: | 90 | while iter_idx != end_idx: | ||
| 91 | current_tone = Tone(CHROMATIC_SCALE[iter_idx]) | 91 | current_tone = Tone(CHROMATIC_SCALE[iter_idx]) | ||
| 92 | if current_tone in tones and current_tone not in sorted_tones: | 92 | if current_tone in tones and current_tone not in sorted_tones: | ||
| 93 | sorted_tones.append(current_tone) | 93 | sorted_tones.append(current_tone) | ||
| 94 | iter_idx += 1 | 94 | iter_idx += 1 | ||
| 95 | iter_idx %= TONES_COUNT | 95 | iter_idx %= TONES_COUNT | ||
| 96 | return sorted_tones | 96 | return sorted_tones | ||
| 97 | 97 | ||||
| 98 | def __str__(self): | 98 | def __str__(self): | ||
| 99 | return '-'.join(str(tone) for tone in self.sorted_tones) | 99 | return '-'.join(str(tone) for tone in self.sorted_tones) | ||
| 100 | 100 | ||||
| 101 | def is_minor(self): | 101 | def is_minor(self): | ||
| 102 | root_idx = CHROMATIC_SCALE.index(str(self.sorted_tones[0])) | 102 | root_idx = CHROMATIC_SCALE.index(str(self.sorted_tones[0])) | ||
| 103 | end_tone_idx = (root_idx + MINOR_THIRD_LEN) % TONES_COUNT | 103 | end_tone_idx = (root_idx + MINOR_THIRD_LEN) % TONES_COUNT | ||
| 104 | return Tone(CHROMATIC_SCALE[end_tone_idx]) in self.sorted_tones | 104 | return Tone(CHROMATIC_SCALE[end_tone_idx]) in self.sorted_tones | ||
| 105 | 105 | ||||
| 106 | def is_major(self): | 106 | def is_major(self): | ||
| 107 | root_idx = CHROMATIC_SCALE.index(str(self.sorted_tones[0])) | 107 | root_idx = CHROMATIC_SCALE.index(str(self.sorted_tones[0])) | ||
| 108 | end_tone_idx = (root_idx + MAJOR_THIRD_LEN) % TONES_COUNT | 108 | end_tone_idx = (root_idx + MAJOR_THIRD_LEN) % TONES_COUNT | ||
| 109 | return Tone(CHROMATIC_SCALE[end_tone_idx]) in self.sorted_tones | 109 | return Tone(CHROMATIC_SCALE[end_tone_idx]) in self.sorted_tones | ||
| 110 | 110 | ||||
| 111 | def is_power_chord(self): | 111 | def is_power_chord(self): | ||
| 112 | return not self.is_minor() and not self.is_major() | 112 | return not self.is_minor() and not self.is_major() | ||
| 113 | 113 | ||||
| 114 | def __add__(self, other): | 114 | def __add__(self, other): | ||
| 115 | if isinstance(other, Tone): | 115 | if isinstance(other, Tone): | ||
| 116 | new_tones = self.sorted_tones + [other] | 116 | new_tones = self.sorted_tones + [other] | ||
| 117 | return Chord(*new_tones) | 117 | return Chord(*new_tones) | ||
| 118 | elif isinstance(other, Chord): | 118 | elif isinstance(other, Chord): | ||
| 119 | new_tones = self.sorted_tones[:] | 119 | new_tones = self.sorted_tones[:] | ||
| 120 | new_tones.extend(other.sorted_tones) | 120 | new_tones.extend(other.sorted_tones) | ||
| 121 | return Chord(*new_tones) | 121 | return Chord(*new_tones) | ||
| 122 | 122 | ||||
| 123 | def __sub__(self, other): | 123 | def __sub__(self, other): | ||
| 124 | if isinstance(other, Tone): | 124 | if isinstance(other, Tone): | ||
| 125 | if other not in self.sorted_tones: | 125 | if other not in self.sorted_tones: | ||
| 126 | raise TypeError(f'Cannot remove tone {str(other)} from chord {str(self)}') | 126 | raise TypeError(f'Cannot remove tone {str(other)} from chord {str(self)}') | ||
| 127 | new_tones = self.sorted_tones[:] | 127 | new_tones = self.sorted_tones[:] | ||
| 128 | new_tones.remove(other) | 128 | new_tones.remove(other) | ||
| 129 | return Chord(*new_tones) | 129 | return Chord(*new_tones) | ||
| 130 | 130 | ||||
| 131 | def transposed(self, interval): | 131 | def transposed(self, interval): | ||
| 132 | transpose = lambda tone : tone + interval | 132 | transpose = lambda tone : tone + interval | ||
| 133 | transposed_tones = list(map(transpose, self.sorted_tones)) | 133 | transposed_tones = list(map(transpose, self.sorted_tones)) | ||
| 134 | return Chord(*transposed_tones) | 134 | return Chord(*transposed_tones) |
| Legends | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
| |||||||||
| f | 1 | CHROMATIC_SCALE = ( | f | 1 | CHROMATIC_SCALE = ( |
| 2 | 'C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B' | 2 | 'C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B' | ||
| 3 | ) | 3 | ) | ||
| 4 | 4 | ||||
| 5 | TONES_COUNT = 12 | 5 | TONES_COUNT = 12 | ||
| 6 | MINOR_THIRD_LEN = 3 | 6 | MINOR_THIRD_LEN = 3 | ||
| 7 | MAJOR_THIRD_LEN = 4 | 7 | MAJOR_THIRD_LEN = 4 | ||
| 8 | 8 | ||||
| 9 | 9 | ||||
| 10 | class Tone: | 10 | class Tone: | ||
| 11 | 11 | ||||
| 12 | def __init__(self, tone_name): | 12 | def __init__(self, tone_name): | ||
| 13 | self.tone_name = tone_name | 13 | self.tone_name = tone_name | ||
| 14 | 14 | ||||
| 15 | def __str__(self): | 15 | def __str__(self): | ||
| 16 | return self.tone_name | 16 | return self.tone_name | ||
| 17 | 17 | ||||
| 18 | def __eq__(self, other): | 18 | def __eq__(self, other): | ||
| 19 | return self.tone_name == other.tone_name | 19 | return self.tone_name == other.tone_name | ||
| 20 | 20 | ||||
| 21 | def __ne__(self, other): | 21 | def __ne__(self, other): | ||
| 22 | return self.tone_name != other.tone_name | 22 | return self.tone_name != other.tone_name | ||
| 23 | 23 | ||||
| 24 | def __add__(self, other): | 24 | def __add__(self, other): | ||
| 25 | if isinstance(other, Tone): | 25 | if isinstance(other, Tone): | ||
| 26 | return Chord(self,other) | 26 | return Chord(self,other) | ||
| 27 | elif isinstance(other, Interval): | 27 | elif isinstance(other, Interval): | ||
| 28 | new_tone_idx = (CHROMATIC_SCALE.index(self.tone_name) + other.semitones_count) % TONES_COUNT | 28 | new_tone_idx = (CHROMATIC_SCALE.index(self.tone_name) + other.semitones_count) % TONES_COUNT | ||
| 29 | return Tone(CHROMATIC_SCALE[new_tone_idx]) | 29 | return Tone(CHROMATIC_SCALE[new_tone_idx]) | ||
| 30 | 30 | ||||
| 31 | def __sub__(self, other): | 31 | def __sub__(self, other): | ||
| 32 | if isinstance(other, Tone): | 32 | if isinstance(other, Tone): | ||
| 33 | self_idx = CHROMATIC_SCALE.index(self.tone_name) | 33 | self_idx = CHROMATIC_SCALE.index(self.tone_name) | ||
| 34 | other_idx = CHROMATIC_SCALE.index(other.tone_name) | 34 | other_idx = CHROMATIC_SCALE.index(other.tone_name) | ||
| n | 35 | interval_length = (self_idx - other_idx + TONES_COUNT) % TONES_COUNT | n | 35 | interval_length = (self_idx - other_idx) % TONES_COUNT |
| 36 | return Interval(interval_length) | 36 | return Interval(interval_length) | ||
| 37 | elif isinstance(other, Interval): | 37 | elif isinstance(other, Interval): | ||
| n | 38 | new_tone_idx = (CHROMATIC_SCALE.index(self.tone_name) - other.semitones_count % TONES_COUNT + TONES_COUNT) % TONES_COUNT | n | 38 | new_tone_idx = (CHROMATIC_SCALE.index(self.tone_name) - other.semitones_count) % TONES_COUNT |
| 39 | return Tone(CHROMATIC_SCALE[new_tone_idx]) | 39 | return Tone(CHROMATIC_SCALE[new_tone_idx]) | ||
| 40 | 40 | ||||
| 41 | 41 | ||||
| 42 | class Interval: | 42 | class Interval: | ||
| 43 | 43 | ||||
| 44 | _INTERVAL_NAMES = ( | 44 | _INTERVAL_NAMES = ( | ||
| 45 | 'unison', 'minor 2nd', 'major 2nd', 'minor 3rd', 'major 3rd', | 45 | 'unison', 'minor 2nd', 'major 2nd', 'minor 3rd', 'major 3rd', | ||
| 46 | 'perfect 4th', 'diminished 5th', 'perfect 5th', 'minor 6th', | 46 | 'perfect 4th', 'diminished 5th', 'perfect 5th', 'minor 6th', | ||
| 47 | 'major 6th', 'minor 7th', 'major 7th' | 47 | 'major 6th', 'minor 7th', 'major 7th' | ||
| 48 | ) | 48 | ) | ||
| 49 | 49 | ||||
| 50 | def __init__(self, semitones_count): | 50 | def __init__(self, semitones_count): | ||
| 51 | self.semitones_count = semitones_count | 51 | self.semitones_count = semitones_count | ||
| 52 | 52 | ||||
| 53 | def __str__(self): | 53 | def __str__(self): | ||
| 54 | return self._INTERVAL_NAMES[self.semitones_count % TONES_COUNT] | 54 | return self._INTERVAL_NAMES[self.semitones_count % TONES_COUNT] | ||
| 55 | 55 | ||||
| 56 | def __add__(self, other): | 56 | def __add__(self, other): | ||
| 57 | if isinstance(other, Tone): | 57 | if isinstance(other, Tone): | ||
| 58 | raise TypeError('Invalid operation') | 58 | raise TypeError('Invalid operation') | ||
| 59 | elif isinstance(other, Interval): | 59 | elif isinstance(other, Interval): | ||
| 60 | new_count = (self.semitones_count + other.semitones_count) | 60 | new_count = (self.semitones_count + other.semitones_count) | ||
| 61 | return Interval(new_count) | 61 | return Interval(new_count) | ||
| 62 | 62 | ||||
| 63 | def __sub__(self, other): | 63 | def __sub__(self, other): | ||
| 64 | if isinstance(other, Tone): | 64 | if isinstance(other, Tone): | ||
| 65 | raise TypeError('Invalid operation') | 65 | raise TypeError('Invalid operation') | ||
| 66 | 66 | ||||
| 67 | def __neg__(self): | 67 | def __neg__(self): | ||
| 68 | return Interval(-self.semitones_count) | 68 | return Interval(-self.semitones_count) | ||
| 69 | 69 | ||||
| 70 | 70 | ||||
| 71 | class Chord: | 71 | class Chord: | ||
| 72 | 72 | ||||
| 73 | def __init__(self, *tones): | 73 | def __init__(self, *tones): | ||
| 74 | if not self._are_valid_tones(*tones): | 74 | if not self._are_valid_tones(*tones): | ||
| 75 | raise TypeError('Cannot have a chord made of only 1 unique tone') | 75 | raise TypeError('Cannot have a chord made of only 1 unique tone') | ||
| 76 | self.sorted_tones = self._sort_tones(*tones) | 76 | self.sorted_tones = self._sort_tones(*tones) | ||
| 77 | 77 | ||||
| 78 | @staticmethod | 78 | @staticmethod | ||
| 79 | def _are_valid_tones(*tones): | 79 | def _are_valid_tones(*tones): | ||
| 80 | tones_set = {str(tone) for tone in tones} | 80 | tones_set = {str(tone) for tone in tones} | ||
| 81 | return len(tones_set) > 1 | 81 | return len(tones_set) > 1 | ||
| 82 | 82 | ||||
| 83 | @staticmethod | 83 | @staticmethod | ||
| 84 | def _sort_tones(*tones): | 84 | def _sort_tones(*tones): | ||
| 85 | root = tones[0] | 85 | root = tones[0] | ||
| 86 | sorted_tones = [root] | 86 | sorted_tones = [root] | ||
| 87 | end_idx = CHROMATIC_SCALE.index(str(root)) | 87 | end_idx = CHROMATIC_SCALE.index(str(root)) | ||
| 88 | iter_idx = (end_idx + 1) % TONES_COUNT | 88 | iter_idx = (end_idx + 1) % TONES_COUNT | ||
| 89 | 89 | ||||
| 90 | while iter_idx != end_idx: | 90 | while iter_idx != end_idx: | ||
| 91 | current_tone = Tone(CHROMATIC_SCALE[iter_idx]) | 91 | current_tone = Tone(CHROMATIC_SCALE[iter_idx]) | ||
| 92 | if current_tone in tones and current_tone not in sorted_tones: | 92 | if current_tone in tones and current_tone not in sorted_tones: | ||
| 93 | sorted_tones.append(current_tone) | 93 | sorted_tones.append(current_tone) | ||
| 94 | iter_idx += 1 | 94 | iter_idx += 1 | ||
| 95 | iter_idx %= TONES_COUNT | 95 | iter_idx %= TONES_COUNT | ||
| 96 | return sorted_tones | 96 | return sorted_tones | ||
| 97 | 97 | ||||
| 98 | def __str__(self): | 98 | def __str__(self): | ||
| 99 | return '-'.join(str(tone) for tone in self.sorted_tones) | 99 | return '-'.join(str(tone) for tone in self.sorted_tones) | ||
| 100 | 100 | ||||
| 101 | def is_minor(self): | 101 | def is_minor(self): | ||
| 102 | root_idx = CHROMATIC_SCALE.index(str(self.sorted_tones[0])) | 102 | root_idx = CHROMATIC_SCALE.index(str(self.sorted_tones[0])) | ||
| 103 | end_tone_idx = (root_idx + MINOR_THIRD_LEN) % TONES_COUNT | 103 | end_tone_idx = (root_idx + MINOR_THIRD_LEN) % TONES_COUNT | ||
| 104 | return Tone(CHROMATIC_SCALE[end_tone_idx]) in self.sorted_tones | 104 | return Tone(CHROMATIC_SCALE[end_tone_idx]) in self.sorted_tones | ||
| 105 | 105 | ||||
| 106 | def is_major(self): | 106 | def is_major(self): | ||
| 107 | root_idx = CHROMATIC_SCALE.index(str(self.sorted_tones[0])) | 107 | root_idx = CHROMATIC_SCALE.index(str(self.sorted_tones[0])) | ||
| 108 | end_tone_idx = (root_idx + MAJOR_THIRD_LEN) % TONES_COUNT | 108 | end_tone_idx = (root_idx + MAJOR_THIRD_LEN) % TONES_COUNT | ||
| 109 | return Tone(CHROMATIC_SCALE[end_tone_idx]) in self.sorted_tones | 109 | return Tone(CHROMATIC_SCALE[end_tone_idx]) in self.sorted_tones | ||
| 110 | 110 | ||||
| 111 | def is_power_chord(self): | 111 | def is_power_chord(self): | ||
| 112 | return not self.is_minor() and not self.is_major() | 112 | return not self.is_minor() and not self.is_major() | ||
| 113 | 113 | ||||
| 114 | def __add__(self, other): | 114 | def __add__(self, other): | ||
| 115 | if isinstance(other, Tone): | 115 | if isinstance(other, Tone): | ||
| 116 | new_tones = self.sorted_tones + [other] | 116 | new_tones = self.sorted_tones + [other] | ||
| 117 | return Chord(*new_tones) | 117 | return Chord(*new_tones) | ||
| 118 | elif isinstance(other, Chord): | 118 | elif isinstance(other, Chord): | ||
| 119 | new_tones = self.sorted_tones[:] | 119 | new_tones = self.sorted_tones[:] | ||
| 120 | new_tones.extend(other.sorted_tones) | 120 | new_tones.extend(other.sorted_tones) | ||
| 121 | return Chord(*new_tones) | 121 | return Chord(*new_tones) | ||
| 122 | 122 | ||||
| 123 | def __sub__(self, other): | 123 | def __sub__(self, other): | ||
| 124 | if isinstance(other, Tone): | 124 | if isinstance(other, Tone): | ||
| 125 | if other not in self.sorted_tones: | 125 | if other not in self.sorted_tones: | ||
| 126 | raise TypeError(f'Cannot remove tone {str(other)} from chord {str(self)}') | 126 | raise TypeError(f'Cannot remove tone {str(other)} from chord {str(self)}') | ||
| 127 | new_tones = self.sorted_tones[:] | 127 | new_tones = self.sorted_tones[:] | ||
| 128 | new_tones.remove(other) | 128 | new_tones.remove(other) | ||
| 129 | return Chord(*new_tones) | 129 | return Chord(*new_tones) | ||
| 130 | 130 | ||||
| 131 | def transposed(self, interval): | 131 | def transposed(self, interval): | ||
| t | 132 | transpose_with = interval.semitones_count | t | 132 | transpose = lambda tone : tone + interval |
| 133 | transpose = lambda tone : Tone(CHROMATIC_SCALE[(CHROMATIC_SCALE.index(str(tone)) + transpose_with + TONES_COUNT) % TONES_COUNT]) | ||||
| 134 | transposed_tones = list(map(transpose, self.sorted_tones)) | 133 | transposed_tones = list(map(transpose, self.sorted_tones)) | ||
| 135 | return Chord(*transposed_tones) | 134 | return Chord(*transposed_tones) |
| Legends | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
| |||||||||
| f | 1 | CHROMATIC_SCALE = ( | f | 1 | CHROMATIC_SCALE = ( |
| 2 | 'C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B' | 2 | 'C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B' | ||
| 3 | ) | 3 | ) | ||
| 4 | 4 | ||||
| 5 | TONES_COUNT = 12 | 5 | TONES_COUNT = 12 | ||
| 6 | MINOR_THIRD_LEN = 3 | 6 | MINOR_THIRD_LEN = 3 | ||
| 7 | MAJOR_THIRD_LEN = 4 | 7 | MAJOR_THIRD_LEN = 4 | ||
| 8 | 8 | ||||
| 9 | 9 | ||||
| 10 | class Tone: | 10 | class Tone: | ||
| 11 | 11 | ||||
| 12 | def __init__(self, tone_name): | 12 | def __init__(self, tone_name): | ||
| 13 | self.tone_name = tone_name | 13 | self.tone_name = tone_name | ||
| 14 | 14 | ||||
| 15 | def __str__(self): | 15 | def __str__(self): | ||
| 16 | return self.tone_name | 16 | return self.tone_name | ||
| 17 | 17 | ||||
| 18 | def __eq__(self, other): | 18 | def __eq__(self, other): | ||
| 19 | return self.tone_name == other.tone_name | 19 | return self.tone_name == other.tone_name | ||
| 20 | 20 | ||||
| 21 | def __ne__(self, other): | 21 | def __ne__(self, other): | ||
| 22 | return self.tone_name != other.tone_name | 22 | return self.tone_name != other.tone_name | ||
| 23 | 23 | ||||
| 24 | def __add__(self, other): | 24 | def __add__(self, other): | ||
| 25 | if isinstance(other, Tone): | 25 | if isinstance(other, Tone): | ||
| 26 | return Chord(self,other) | 26 | return Chord(self,other) | ||
| 27 | elif isinstance(other, Interval): | 27 | elif isinstance(other, Interval): | ||
| 28 | new_tone_idx = (CHROMATIC_SCALE.index(self.tone_name) + other.semitones_count) % TONES_COUNT | 28 | new_tone_idx = (CHROMATIC_SCALE.index(self.tone_name) + other.semitones_count) % TONES_COUNT | ||
| 29 | return Tone(CHROMATIC_SCALE[new_tone_idx]) | 29 | return Tone(CHROMATIC_SCALE[new_tone_idx]) | ||
| 30 | 30 | ||||
| 31 | def __sub__(self, other): | 31 | def __sub__(self, other): | ||
| 32 | if isinstance(other, Tone): | 32 | if isinstance(other, Tone): | ||
| 33 | self_idx = CHROMATIC_SCALE.index(self.tone_name) | 33 | self_idx = CHROMATIC_SCALE.index(self.tone_name) | ||
| 34 | other_idx = CHROMATIC_SCALE.index(other.tone_name) | 34 | other_idx = CHROMATIC_SCALE.index(other.tone_name) | ||
| 35 | interval_length = (self_idx - other_idx + TONES_COUNT) % TONES_COUNT | 35 | interval_length = (self_idx - other_idx + TONES_COUNT) % TONES_COUNT | ||
| 36 | return Interval(interval_length) | 36 | return Interval(interval_length) | ||
| 37 | elif isinstance(other, Interval): | 37 | elif isinstance(other, Interval): | ||
| 38 | new_tone_idx = (CHROMATIC_SCALE.index(self.tone_name) - other.semitones_count % TONES_COUNT + TONES_COUNT) % TONES_COUNT | 38 | new_tone_idx = (CHROMATIC_SCALE.index(self.tone_name) - other.semitones_count % TONES_COUNT + TONES_COUNT) % TONES_COUNT | ||
| 39 | return Tone(CHROMATIC_SCALE[new_tone_idx]) | 39 | return Tone(CHROMATIC_SCALE[new_tone_idx]) | ||
| 40 | 40 | ||||
| 41 | 41 | ||||
| 42 | class Interval: | 42 | class Interval: | ||
| 43 | 43 | ||||
| 44 | _INTERVAL_NAMES = ( | 44 | _INTERVAL_NAMES = ( | ||
| 45 | 'unison', 'minor 2nd', 'major 2nd', 'minor 3rd', 'major 3rd', | 45 | 'unison', 'minor 2nd', 'major 2nd', 'minor 3rd', 'major 3rd', | ||
| 46 | 'perfect 4th', 'diminished 5th', 'perfect 5th', 'minor 6th', | 46 | 'perfect 4th', 'diminished 5th', 'perfect 5th', 'minor 6th', | ||
| 47 | 'major 6th', 'minor 7th', 'major 7th' | 47 | 'major 6th', 'minor 7th', 'major 7th' | ||
| 48 | ) | 48 | ) | ||
| 49 | 49 | ||||
| 50 | def __init__(self, semitones_count): | 50 | def __init__(self, semitones_count): | ||
| 51 | self.semitones_count = semitones_count | 51 | self.semitones_count = semitones_count | ||
| 52 | 52 | ||||
| 53 | def __str__(self): | 53 | def __str__(self): | ||
| 54 | return self._INTERVAL_NAMES[self.semitones_count % TONES_COUNT] | 54 | return self._INTERVAL_NAMES[self.semitones_count % TONES_COUNT] | ||
| 55 | 55 | ||||
| 56 | def __add__(self, other): | 56 | def __add__(self, other): | ||
| 57 | if isinstance(other, Tone): | 57 | if isinstance(other, Tone): | ||
| 58 | raise TypeError('Invalid operation') | 58 | raise TypeError('Invalid operation') | ||
| 59 | elif isinstance(other, Interval): | 59 | elif isinstance(other, Interval): | ||
| 60 | new_count = (self.semitones_count + other.semitones_count) | 60 | new_count = (self.semitones_count + other.semitones_count) | ||
| 61 | return Interval(new_count) | 61 | return Interval(new_count) | ||
| 62 | 62 | ||||
| 63 | def __sub__(self, other): | 63 | def __sub__(self, other): | ||
| 64 | if isinstance(other, Tone): | 64 | if isinstance(other, Tone): | ||
| 65 | raise TypeError('Invalid operation') | 65 | raise TypeError('Invalid operation') | ||
| 66 | 66 | ||||
| 67 | def __neg__(self): | 67 | def __neg__(self): | ||
| 68 | return Interval(-self.semitones_count) | 68 | return Interval(-self.semitones_count) | ||
| 69 | 69 | ||||
| 70 | 70 | ||||
| 71 | class Chord: | 71 | class Chord: | ||
| 72 | 72 | ||||
| 73 | def __init__(self, *tones): | 73 | def __init__(self, *tones): | ||
| 74 | if not self._are_valid_tones(*tones): | 74 | if not self._are_valid_tones(*tones): | ||
| 75 | raise TypeError('Cannot have a chord made of only 1 unique tone') | 75 | raise TypeError('Cannot have a chord made of only 1 unique tone') | ||
| 76 | self.sorted_tones = self._sort_tones(*tones) | 76 | self.sorted_tones = self._sort_tones(*tones) | ||
| 77 | 77 | ||||
| 78 | @staticmethod | 78 | @staticmethod | ||
| 79 | def _are_valid_tones(*tones): | 79 | def _are_valid_tones(*tones): | ||
| n | 80 | tones_set = set() | n | 80 | tones_set = {str(tone) for tone in tones} |
| 81 | |||||
| 82 | for tone in tones: | ||||
| 83 | tones_set.add(str(tone)) | ||||
| 84 | |||||
| 85 | return len(tones_set) > 1 | 81 | return len(tones_set) > 1 | ||
| 86 | 82 | ||||
| 87 | @staticmethod | 83 | @staticmethod | ||
| 88 | def _sort_tones(*tones): | 84 | def _sort_tones(*tones): | ||
| 89 | root = tones[0] | 85 | root = tones[0] | ||
| 90 | sorted_tones = [root] | 86 | sorted_tones = [root] | ||
| 91 | end_idx = CHROMATIC_SCALE.index(str(root)) | 87 | end_idx = CHROMATIC_SCALE.index(str(root)) | ||
| 92 | iter_idx = (end_idx + 1) % TONES_COUNT | 88 | iter_idx = (end_idx + 1) % TONES_COUNT | ||
| 93 | 89 | ||||
| 94 | while iter_idx != end_idx: | 90 | while iter_idx != end_idx: | ||
| n | 95 | current_tone = Tone(CHROMATIC_SCALE[iter_idx]) | n | 91 | current_tone = Tone(CHROMATIC_SCALE[iter_idx]) |
| 96 | if current_tone in tones and current_tone not in sorted_tones: | 92 | if current_tone in tones and current_tone not in sorted_tones: | ||
| 97 | sorted_tones.append(current_tone) | 93 | sorted_tones.append(current_tone) | ||
| n | 98 | iter_idx+=1 | n | 94 | iter_idx += 1 |
| 99 | iter_idx %= TONES_COUNT | 95 | iter_idx %= TONES_COUNT | ||
| 100 | return sorted_tones | 96 | return sorted_tones | ||
| 101 | 97 | ||||
| 102 | def __str__(self): | 98 | def __str__(self): | ||
| 103 | return '-'.join(str(tone) for tone in self.sorted_tones) | 99 | return '-'.join(str(tone) for tone in self.sorted_tones) | ||
| 104 | 100 | ||||
| 105 | def is_minor(self): | 101 | def is_minor(self): | ||
| 106 | root_idx = CHROMATIC_SCALE.index(str(self.sorted_tones[0])) | 102 | root_idx = CHROMATIC_SCALE.index(str(self.sorted_tones[0])) | ||
| 107 | end_tone_idx = (root_idx + MINOR_THIRD_LEN) % TONES_COUNT | 103 | end_tone_idx = (root_idx + MINOR_THIRD_LEN) % TONES_COUNT | ||
| 108 | return Tone(CHROMATIC_SCALE[end_tone_idx]) in self.sorted_tones | 104 | return Tone(CHROMATIC_SCALE[end_tone_idx]) in self.sorted_tones | ||
| 109 | 105 | ||||
| 110 | def is_major(self): | 106 | def is_major(self): | ||
| 111 | root_idx = CHROMATIC_SCALE.index(str(self.sorted_tones[0])) | 107 | root_idx = CHROMATIC_SCALE.index(str(self.sorted_tones[0])) | ||
| 112 | end_tone_idx = (root_idx + MAJOR_THIRD_LEN) % TONES_COUNT | 108 | end_tone_idx = (root_idx + MAJOR_THIRD_LEN) % TONES_COUNT | ||
| 113 | return Tone(CHROMATIC_SCALE[end_tone_idx]) in self.sorted_tones | 109 | return Tone(CHROMATIC_SCALE[end_tone_idx]) in self.sorted_tones | ||
| 114 | 110 | ||||
| 115 | def is_power_chord(self): | 111 | def is_power_chord(self): | ||
| 116 | return not self.is_minor() and not self.is_major() | 112 | return not self.is_minor() and not self.is_major() | ||
| 117 | 113 | ||||
| 118 | def __add__(self, other): | 114 | def __add__(self, other): | ||
| 119 | if isinstance(other, Tone): | 115 | if isinstance(other, Tone): | ||
| 120 | new_tones = self.sorted_tones + [other] | 116 | new_tones = self.sorted_tones + [other] | ||
| 121 | return Chord(*new_tones) | 117 | return Chord(*new_tones) | ||
| 122 | elif isinstance(other, Chord): | 118 | elif isinstance(other, Chord): | ||
| n | 123 | new_tones = self.sorted_tones.copy() | n | 119 | new_tones = self.sorted_tones[:] |
| 124 | new_tones.extend(other.sorted_tones) | 120 | new_tones.extend(other.sorted_tones) | ||
| 125 | return Chord(*new_tones) | 121 | return Chord(*new_tones) | ||
| 126 | 122 | ||||
| 127 | def __sub__(self, other): | 123 | def __sub__(self, other): | ||
| 128 | if isinstance(other, Tone): | 124 | if isinstance(other, Tone): | ||
| 129 | if other not in self.sorted_tones: | 125 | if other not in self.sorted_tones: | ||
| 130 | raise TypeError(f'Cannot remove tone {str(other)} from chord {str(self)}') | 126 | raise TypeError(f'Cannot remove tone {str(other)} from chord {str(self)}') | ||
| t | 131 | new_tones = self.sorted_tones.copy() | t | 127 | new_tones = self.sorted_tones[:] |
| 132 | new_tones.remove(other) | 128 | new_tones.remove(other) | ||
| 133 | return Chord(*new_tones) | 129 | return Chord(*new_tones) | ||
| 134 | 130 | ||||
| 135 | def transposed(self, interval): | 131 | def transposed(self, interval): | ||
| 136 | transpose_with = interval.semitones_count | 132 | transpose_with = interval.semitones_count | ||
| 137 | transpose = lambda tone : Tone(CHROMATIC_SCALE[(CHROMATIC_SCALE.index(str(tone)) + transpose_with + TONES_COUNT) % TONES_COUNT]) | 133 | transpose = lambda tone : Tone(CHROMATIC_SCALE[(CHROMATIC_SCALE.index(str(tone)) + transpose_with + TONES_COUNT) % TONES_COUNT]) | ||
| 138 | transposed_tones = list(map(transpose, self.sorted_tones)) | 134 | transposed_tones = list(map(transpose, self.sorted_tones)) | ||
| 139 | return Chord(*transposed_tones) | 135 | return Chord(*transposed_tones) |
| Legends | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
| |||||||||