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 | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
|