1ACCEPTED_TONES = ("C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B")
2INTERVAL_VALUES = ("unison", "minor 2nd", "major 2nd", "minor 3rd", "major 3rd", "perfect 4th",
3 "diminished 5th", "perfect 5th", "minor 6th", "major 6th", "minor 7th", "major 7th")
4
5
6class Tone:
7
8 def __init__(self, tone):
9 if tone not in ACCEPTED_TONES:
10 raise TypeError("Tone is not accepted")
11 self.tone = tone
12
13 def __str__(self):
14 return self.tone
15
16 def __eq__(self, other):
17 return self.tone == other.tone
18
19 def __hash__(self):
20 return hash(self.tone)
21
22 def __add__(self, other):
23 if isinstance(other, Tone):
24 return Chord(self, other)
25
26 if isinstance(other, Interval):
27 return Tone(ACCEPTED_TONES[(ACCEPTED_TONES.index(self.tone) + other.length) % len(ACCEPTED_TONES)])
28
29 raise TypeError("Invalid operation")
30
31 def __sub__(self, other):
32 if isinstance(other, Tone):
33 return Interval(abs(ACCEPTED_TONES.index(self.tone) - ACCEPTED_TONES.index(other.tone)))
34
35 if isinstance(other, Interval):
36 return Tone(ACCEPTED_TONES[(ACCEPTED_TONES.index(self.tone) - other.length) % len(ACCEPTED_TONES)])
37
38 raise TypeError("Invalid operation")
39
40
41class Interval:
42
43 def __init__(self, interval_length):
44 self.length = interval_length % len(INTERVAL_VALUES)
45
46 def __str__(self):
47 return INTERVAL_VALUES[self.length]
48
49 def __add__(self, other):
50
51 if isinstance(other, Interval):
52 return Interval(self.length + other.length)
53
54 raise TypeError("Invalid operation")
55
56 def __neg__(self):
57 return Interval(-self.length)
58
59
60class Chord:
61
62 def __init__(self, root_tone, *other_tone_args):
63
64 self.root_tone = root_tone
65 self.tone_set = set()
66 self.tone_set.add(root_tone)
67
68 for arg in other_tone_args:
69 if isinstance(arg, set):
70 for tone in arg:
71 self.tone_set.add(tone)
72 else:
73 self.tone_set.add(arg)
74
75 if len(self.tone_set) < 2:
76 raise TypeError("Cannot have a chord made of only 1 unique tone")
77
78 def __str__(self):
79 list_of_tone_indexes = list(map(lambda x: ACCEPTED_TONES.index(x.tone), list(self.tone_set)))
80 list_of_tone_indexes.sort()
81
82 root_tone_index = ACCEPTED_TONES.index(self.root_tone.tone)
83
84 tone_indexes_before_root = list(filter(lambda x: x < root_tone_index, list_of_tone_indexes))
85
86 for tone in tone_indexes_before_root:
87 list_of_tone_indexes.remove(tone)
88
89 result = "-".join(list(map(lambda x: ACCEPTED_TONES[x], list_of_tone_indexes)) +
90 list(map(lambda x: ACCEPTED_TONES[x], tone_indexes_before_root)))
91
92 return result
93
94 def __add__(self, other):
95
96 if isinstance(other, Tone):
97 return Chord(self.root_tone, self.tone_set, other)
98
99 if isinstance(other, Chord):
100 return Chord(self.root_tone, self.tone_set, other.tone_set)
101
102 raise TypeError("Invalid operation")
103
104 def find_next_tone(self, reference_tone, tone_set):
105
106 reference_tone_index = ACCEPTED_TONES.index(reference_tone.tone)
107
108 for i in range(reference_tone_index + 1, len(ACCEPTED_TONES)):
109 potential_tone = Tone(ACCEPTED_TONES[i])
110 if potential_tone in tone_set:
111 return potential_tone
112
113 for i in range(0, reference_tone_index):
114 potential_tone = Tone(ACCEPTED_TONES[i])
115 if potential_tone in tone_set:
116 return potential_tone
117
118 def __sub__(self, other):
119 if isinstance(other, Tone):
120 if other not in self.tone_set:
121 raise TypeError(f"Cannot remove tone {other} from chord {self}")
122
123 new_tone_set = self.tone_set.copy()
124 new_tone_set.remove(other)
125
126 if other == self.root_tone:
127 new_root_tone = self.find_next_tone(self.root_tone, self.tone_set)
128 return Chord(new_root_tone, new_tone_set)
129
130 return Chord(self.root_tone, new_tone_set)
131
132 raise TypeError("Invalid operation")
133
134 def is_minor(self):
135 minor_third_index = INTERVAL_VALUES.index("minor 3rd")
136 root_tone_index = ACCEPTED_TONES.index(self.root_tone.tone)
137
138 for tone in self.tone_set:
139 if abs(root_tone_index - ACCEPTED_TONES.index(tone.tone)) == minor_third_index:
140 return True
141
142 return False
143
144 def is_major(self):
145 minor_third_index = INTERVAL_VALUES.index("major 3rd")
146 root_tone_index = ACCEPTED_TONES.index(self.root_tone.tone)
147
148 for tone in self.tone_set:
149 if abs(root_tone_index - ACCEPTED_TONES.index(tone.tone)) == minor_third_index:
150 return True
151
152 return False
153
154 def is_power_chord(self):
155 return not self.is_minor() and not self.is_major()
156
157 def transposed(self, interval):
158 return Chord(self.root_tone + interval, set(map(lambda x: x + interval, list(self.tone_set))))
....FFF....................F.......F.
======================================================================
FAIL: test_is_major (test.TestBasicChordFunctionality.test_is_major)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 107, in test_is_major
self.assertTrue(a_major_chord.is_major())
AssertionError: False is not true
======================================================================
FAIL: test_is_minor (test.TestBasicChordFunctionality.test_is_minor)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 90, in test_is_minor
self.assertTrue(a_minor_chord.is_minor())
AssertionError: False is not true
======================================================================
FAIL: test_is_power_chord (test.TestBasicChordFunctionality.test_is_power_chord)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 116, in test_is_power_chord
self.assertFalse(a_minor_chord.is_power_chord())
AssertionError: True is not false
======================================================================
FAIL: test_subtract_interval_from_tone_left_side_error (test.TestOperations.test_subtract_interval_from_tone_left_side_error)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 235, in test_subtract_interval_from_tone_left_side_error
self.assertEqual(str(err.exception), INVALID_OPERATION)
AssertionError: "unsupported operand type(s) for -: 'Interval' and 'Tone'" != 'Invalid operation'
- unsupported operand type(s) for -: 'Interval' and 'Tone'
+ Invalid operation
======================================================================
FAIL: test_tone_subtraction_inverse (test.TestOperations.test_tone_subtraction_inverse)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 178, in test_tone_subtraction_inverse
self.assertEqual(str(perfect_4th), "perfect 4th")
AssertionError: 'perfect 5th' != 'perfect 4th'
- perfect 5th
? ^
+ perfect 4th
? ^
----------------------------------------------------------------------
Ran 37 tests in 0.003s
FAILED (failures=5)
Георги Балтиев
06.11.2024 12:11добавих вече функциите които ми липсваха
|
t | 1 | t | |||
2 | import unittest | ||||
3 | 1 | ||||
4 | ACCEPTED_TONES = ("C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B") | 2 | ACCEPTED_TONES = ("C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B") | ||
5 | INTERVAL_VALUES = ("unison", "minor 2nd", "major 2nd", "minor 3rd", "major 3rd", "perfect 4th", | 3 | INTERVAL_VALUES = ("unison", "minor 2nd", "major 2nd", "minor 3rd", "major 3rd", "perfect 4th", | ||
6 | "diminished 5th", "perfect 5th", "minor 6th", "major 6th", "minor 7th", "major 7th") | 4 | "diminished 5th", "perfect 5th", "minor 6th", "major 6th", "minor 7th", "major 7th") | ||
7 | 5 | ||||
8 | 6 | ||||
9 | class Tone: | 7 | class Tone: | ||
10 | 8 | ||||
11 | def __init__(self, tone): | 9 | def __init__(self, tone): | ||
12 | if tone not in ACCEPTED_TONES: | 10 | if tone not in ACCEPTED_TONES: | ||
13 | raise TypeError("Tone is not accepted") | 11 | raise TypeError("Tone is not accepted") | ||
14 | self.tone = tone | 12 | self.tone = tone | ||
15 | 13 | ||||
16 | def __str__(self): | 14 | def __str__(self): | ||
17 | return self.tone | 15 | return self.tone | ||
18 | 16 | ||||
19 | def __eq__(self, other): | 17 | def __eq__(self, other): | ||
20 | return self.tone == other.tone | 18 | return self.tone == other.tone | ||
21 | 19 | ||||
22 | def __hash__(self): | 20 | def __hash__(self): | ||
23 | return hash(self.tone) | 21 | return hash(self.tone) | ||
24 | 22 | ||||
25 | def __add__(self, other): | 23 | def __add__(self, other): | ||
26 | if isinstance(other, Tone): | 24 | if isinstance(other, Tone): | ||
27 | return Chord(self, other) | 25 | return Chord(self, other) | ||
28 | 26 | ||||
29 | if isinstance(other, Interval): | 27 | if isinstance(other, Interval): | ||
30 | return Tone(ACCEPTED_TONES[(ACCEPTED_TONES.index(self.tone) + other.length) % len(ACCEPTED_TONES)]) | 28 | return Tone(ACCEPTED_TONES[(ACCEPTED_TONES.index(self.tone) + other.length) % len(ACCEPTED_TONES)]) | ||
31 | 29 | ||||
32 | raise TypeError("Invalid operation") | 30 | raise TypeError("Invalid operation") | ||
33 | 31 | ||||
34 | def __sub__(self, other): | 32 | def __sub__(self, other): | ||
35 | if isinstance(other, Tone): | 33 | if isinstance(other, Tone): | ||
36 | return Interval(abs(ACCEPTED_TONES.index(self.tone) - ACCEPTED_TONES.index(other.tone))) | 34 | return Interval(abs(ACCEPTED_TONES.index(self.tone) - ACCEPTED_TONES.index(other.tone))) | ||
37 | 35 | ||||
38 | if isinstance(other, Interval): | 36 | if isinstance(other, Interval): | ||
39 | return Tone(ACCEPTED_TONES[(ACCEPTED_TONES.index(self.tone) - other.length) % len(ACCEPTED_TONES)]) | 37 | return Tone(ACCEPTED_TONES[(ACCEPTED_TONES.index(self.tone) - other.length) % len(ACCEPTED_TONES)]) | ||
40 | 38 | ||||
41 | raise TypeError("Invalid operation") | 39 | raise TypeError("Invalid operation") | ||
42 | 40 | ||||
43 | 41 | ||||
44 | class Interval: | 42 | class Interval: | ||
45 | 43 | ||||
46 | def __init__(self, interval_length): | 44 | def __init__(self, interval_length): | ||
47 | self.length = interval_length % len(INTERVAL_VALUES) | 45 | self.length = interval_length % len(INTERVAL_VALUES) | ||
48 | 46 | ||||
49 | def __str__(self): | 47 | def __str__(self): | ||
50 | return INTERVAL_VALUES[self.length] | 48 | return INTERVAL_VALUES[self.length] | ||
51 | 49 | ||||
52 | def __add__(self, other): | 50 | def __add__(self, other): | ||
53 | 51 | ||||
54 | if isinstance(other, Interval): | 52 | if isinstance(other, Interval): | ||
55 | return Interval(self.length + other.length) | 53 | return Interval(self.length + other.length) | ||
56 | 54 | ||||
57 | raise TypeError("Invalid operation") | 55 | raise TypeError("Invalid operation") | ||
58 | 56 | ||||
59 | def __neg__(self): | 57 | def __neg__(self): | ||
60 | return Interval(-self.length) | 58 | return Interval(-self.length) | ||
61 | 59 | ||||
62 | 60 | ||||
63 | class Chord: | 61 | class Chord: | ||
64 | 62 | ||||
65 | def __init__(self, root_tone, *other_tone_args): | 63 | def __init__(self, root_tone, *other_tone_args): | ||
66 | 64 | ||||
67 | self.root_tone = root_tone | 65 | self.root_tone = root_tone | ||
68 | self.tone_set = set() | 66 | self.tone_set = set() | ||
69 | self.tone_set.add(root_tone) | 67 | self.tone_set.add(root_tone) | ||
70 | 68 | ||||
71 | for arg in other_tone_args: | 69 | for arg in other_tone_args: | ||
72 | if isinstance(arg, set): | 70 | if isinstance(arg, set): | ||
73 | for tone in arg: | 71 | for tone in arg: | ||
74 | self.tone_set.add(tone) | 72 | self.tone_set.add(tone) | ||
75 | else: | 73 | else: | ||
76 | self.tone_set.add(arg) | 74 | self.tone_set.add(arg) | ||
77 | 75 | ||||
78 | if len(self.tone_set) < 2: | 76 | if len(self.tone_set) < 2: | ||
79 | raise TypeError("Cannot have a chord made of only 1 unique tone") | 77 | raise TypeError("Cannot have a chord made of only 1 unique tone") | ||
80 | 78 | ||||
81 | def __str__(self): | 79 | def __str__(self): | ||
82 | list_of_tone_indexes = list(map(lambda x: ACCEPTED_TONES.index(x.tone), list(self.tone_set))) | 80 | list_of_tone_indexes = list(map(lambda x: ACCEPTED_TONES.index(x.tone), list(self.tone_set))) | ||
83 | list_of_tone_indexes.sort() | 81 | list_of_tone_indexes.sort() | ||
84 | 82 | ||||
85 | root_tone_index = ACCEPTED_TONES.index(self.root_tone.tone) | 83 | root_tone_index = ACCEPTED_TONES.index(self.root_tone.tone) | ||
86 | 84 | ||||
87 | tone_indexes_before_root = list(filter(lambda x: x < root_tone_index, list_of_tone_indexes)) | 85 | tone_indexes_before_root = list(filter(lambda x: x < root_tone_index, list_of_tone_indexes)) | ||
88 | 86 | ||||
89 | for tone in tone_indexes_before_root: | 87 | for tone in tone_indexes_before_root: | ||
90 | list_of_tone_indexes.remove(tone) | 88 | list_of_tone_indexes.remove(tone) | ||
91 | 89 | ||||
92 | result = "-".join(list(map(lambda x: ACCEPTED_TONES[x], list_of_tone_indexes)) + | 90 | result = "-".join(list(map(lambda x: ACCEPTED_TONES[x], list_of_tone_indexes)) + | ||
93 | list(map(lambda x: ACCEPTED_TONES[x], tone_indexes_before_root))) | 91 | list(map(lambda x: ACCEPTED_TONES[x], tone_indexes_before_root))) | ||
94 | 92 | ||||
95 | return result | 93 | return result | ||
96 | 94 | ||||
97 | def __add__(self, other): | 95 | def __add__(self, other): | ||
98 | 96 | ||||
99 | if isinstance(other, Tone): | 97 | if isinstance(other, Tone): | ||
100 | return Chord(self.root_tone, self.tone_set, other) | 98 | return Chord(self.root_tone, self.tone_set, other) | ||
101 | 99 | ||||
102 | if isinstance(other, Chord): | 100 | if isinstance(other, Chord): | ||
103 | return Chord(self.root_tone, self.tone_set, other.tone_set) | 101 | return Chord(self.root_tone, self.tone_set, other.tone_set) | ||
104 | 102 | ||||
105 | raise TypeError("Invalid operation") | 103 | raise TypeError("Invalid operation") | ||
106 | 104 | ||||
107 | def find_next_tone(self, reference_tone, tone_set): | 105 | def find_next_tone(self, reference_tone, tone_set): | ||
108 | 106 | ||||
109 | reference_tone_index = ACCEPTED_TONES.index(reference_tone.tone) | 107 | reference_tone_index = ACCEPTED_TONES.index(reference_tone.tone) | ||
110 | 108 | ||||
111 | for i in range(reference_tone_index + 1, len(ACCEPTED_TONES)): | 109 | for i in range(reference_tone_index + 1, len(ACCEPTED_TONES)): | ||
112 | potential_tone = Tone(ACCEPTED_TONES[i]) | 110 | potential_tone = Tone(ACCEPTED_TONES[i]) | ||
113 | if potential_tone in tone_set: | 111 | if potential_tone in tone_set: | ||
114 | return potential_tone | 112 | return potential_tone | ||
115 | 113 | ||||
116 | for i in range(0, reference_tone_index): | 114 | for i in range(0, reference_tone_index): | ||
117 | potential_tone = Tone(ACCEPTED_TONES[i]) | 115 | potential_tone = Tone(ACCEPTED_TONES[i]) | ||
118 | if potential_tone in tone_set: | 116 | if potential_tone in tone_set: | ||
119 | return potential_tone | 117 | return potential_tone | ||
120 | 118 | ||||
121 | def __sub__(self, other): | 119 | def __sub__(self, other): | ||
122 | if isinstance(other, Tone): | 120 | if isinstance(other, Tone): | ||
123 | if other not in self.tone_set: | 121 | if other not in self.tone_set: | ||
124 | raise TypeError(f"Cannot remove tone {other} from chord {self}") | 122 | raise TypeError(f"Cannot remove tone {other} from chord {self}") | ||
125 | 123 | ||||
126 | new_tone_set = self.tone_set.copy() | 124 | new_tone_set = self.tone_set.copy() | ||
127 | new_tone_set.remove(other) | 125 | new_tone_set.remove(other) | ||
128 | 126 | ||||
129 | if other == self.root_tone: | 127 | if other == self.root_tone: | ||
130 | new_root_tone = self.find_next_tone(self.root_tone, self.tone_set) | 128 | new_root_tone = self.find_next_tone(self.root_tone, self.tone_set) | ||
131 | return Chord(new_root_tone, new_tone_set) | 129 | return Chord(new_root_tone, new_tone_set) | ||
132 | 130 | ||||
133 | return Chord(self.root_tone, new_tone_set) | 131 | return Chord(self.root_tone, new_tone_set) | ||
134 | 132 | ||||
135 | raise TypeError("Invalid operation") | 133 | raise TypeError("Invalid operation") | ||
136 | 134 | ||||
137 | def is_minor(self): | 135 | def is_minor(self): | ||
138 | minor_third_index = INTERVAL_VALUES.index("minor 3rd") | 136 | minor_third_index = INTERVAL_VALUES.index("minor 3rd") | ||
139 | root_tone_index = ACCEPTED_TONES.index(self.root_tone.tone) | 137 | root_tone_index = ACCEPTED_TONES.index(self.root_tone.tone) | ||
140 | 138 | ||||
141 | for tone in self.tone_set: | 139 | for tone in self.tone_set: | ||
142 | if abs(root_tone_index - ACCEPTED_TONES.index(tone.tone)) == minor_third_index: | 140 | if abs(root_tone_index - ACCEPTED_TONES.index(tone.tone)) == minor_third_index: | ||
143 | return True | 141 | return True | ||
144 | 142 | ||||
145 | return False | 143 | return False | ||
146 | 144 | ||||
147 | def is_major(self): | 145 | def is_major(self): | ||
148 | minor_third_index = INTERVAL_VALUES.index("major 3rd") | 146 | minor_third_index = INTERVAL_VALUES.index("major 3rd") | ||
149 | root_tone_index = ACCEPTED_TONES.index(self.root_tone.tone) | 147 | root_tone_index = ACCEPTED_TONES.index(self.root_tone.tone) | ||
150 | 148 | ||||
151 | for tone in self.tone_set: | 149 | for tone in self.tone_set: | ||
152 | if abs(root_tone_index - ACCEPTED_TONES.index(tone.tone)) == minor_third_index: | 150 | if abs(root_tone_index - ACCEPTED_TONES.index(tone.tone)) == minor_third_index: | ||
153 | return True | 151 | return True | ||
154 | 152 | ||||
155 | return False | 153 | return False | ||
156 | 154 | ||||
157 | def is_power_chord(self): | 155 | def is_power_chord(self): | ||
158 | return not self.is_minor() and not self.is_major() | 156 | return not self.is_minor() and not self.is_major() | ||
159 | 157 | ||||
160 | def transposed(self, interval): | 158 | def transposed(self, interval): | ||
161 | return Chord(self.root_tone + interval, set(map(lambda x: x + interval, list(self.tone_set)))) | 159 | return Chord(self.root_tone + interval, set(map(lambda x: x + interval, list(self.tone_set)))) |
Legends | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
|
n | n | 1 | |||
2 | import unittest | ||||
1 | 3 | ||||
2 | ACCEPTED_TONES = ("C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B") | 4 | ACCEPTED_TONES = ("C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B") | ||
3 | INTERVAL_VALUES = ("unison", "minor 2nd", "major 2nd", "minor 3rd", "major 3rd", "perfect 4th", | 5 | INTERVAL_VALUES = ("unison", "minor 2nd", "major 2nd", "minor 3rd", "major 3rd", "perfect 4th", | ||
4 | "diminished 5th", "perfect 5th", "minor 6th", "major 6th", "minor 7th", "major 7th") | 6 | "diminished 5th", "perfect 5th", "minor 6th", "major 6th", "minor 7th", "major 7th") | ||
5 | 7 | ||||
6 | 8 | ||||
7 | class Tone: | 9 | class Tone: | ||
8 | 10 | ||||
9 | def __init__(self, tone): | 11 | def __init__(self, tone): | ||
10 | if tone not in ACCEPTED_TONES: | 12 | if tone not in ACCEPTED_TONES: | ||
11 | raise TypeError("Tone is not accepted") | 13 | raise TypeError("Tone is not accepted") | ||
12 | self.tone = tone | 14 | self.tone = tone | ||
13 | 15 | ||||
14 | def __str__(self): | 16 | def __str__(self): | ||
15 | return self.tone | 17 | return self.tone | ||
16 | 18 | ||||
17 | def __eq__(self, other): | 19 | def __eq__(self, other): | ||
n | 18 | if type(other) is not Tone: | n | ||
19 | return False | ||||
20 | return self.tone == other.tone | 20 | return self.tone == other.tone | ||
21 | 21 | ||||
22 | def __hash__(self): | 22 | def __hash__(self): | ||
23 | return hash(self.tone) | 23 | return hash(self.tone) | ||
24 | 24 | ||||
25 | def __add__(self, other): | 25 | def __add__(self, other): | ||
n | 26 | if type(other) is Tone: | n | 26 | if isinstance(other, Tone): |
27 | return Chord(self, other) | 27 | return Chord(self, other) | ||
28 | 28 | ||||
n | 29 | if type(other) is Interval: | n | 29 | if isinstance(other, Interval): |
30 | return Tone(ACCEPTED_TONES[(ACCEPTED_TONES.index(self.tone) + other.length) % len(ACCEPTED_TONES)]) | 30 | return Tone(ACCEPTED_TONES[(ACCEPTED_TONES.index(self.tone) + other.length) % len(ACCEPTED_TONES)]) | ||
31 | 31 | ||||
32 | raise TypeError("Invalid operation") | 32 | raise TypeError("Invalid operation") | ||
33 | 33 | ||||
34 | def __sub__(self, other): | 34 | def __sub__(self, other): | ||
n | 35 | if type(other) is Tone: | n | 35 | if isinstance(other, Tone): |
36 | return Interval(abs(ACCEPTED_TONES.index(self.tone) - ACCEPTED_TONES.index(other.tone))) | 36 | return Interval(abs(ACCEPTED_TONES.index(self.tone) - ACCEPTED_TONES.index(other.tone))) | ||
37 | 37 | ||||
n | 38 | if type(other) is Interval: | n | 38 | if isinstance(other, Interval): |
39 | return Tone(ACCEPTED_TONES[(ACCEPTED_TONES.index(self.tone) - other.length) % len(ACCEPTED_TONES)]) | 39 | return Tone(ACCEPTED_TONES[(ACCEPTED_TONES.index(self.tone) - other.length) % len(ACCEPTED_TONES)]) | ||
40 | 40 | ||||
41 | raise TypeError("Invalid operation") | 41 | raise TypeError("Invalid operation") | ||
42 | 42 | ||||
43 | 43 | ||||
44 | class Interval: | 44 | class Interval: | ||
45 | 45 | ||||
46 | def __init__(self, interval_length): | 46 | def __init__(self, interval_length): | ||
n | 47 | if type(interval_length) is not int: | n | ||
48 | raise TypeError("Interval length is not int") | ||||
49 | |||||
50 | self.length = interval_length % len(INTERVAL_VALUES) | 47 | self.length = interval_length % len(INTERVAL_VALUES) | ||
51 | 48 | ||||
52 | def __str__(self): | 49 | def __str__(self): | ||
n | 53 | return INTERVAL_VALUES[abs(self.length)] | n | 50 | return INTERVAL_VALUES[self.length] |
54 | 51 | ||||
55 | def __add__(self, other): | 52 | def __add__(self, other): | ||
n | 56 | return INTERVAL_VALUES[(self.length + other.length) % len(INTERVAL_VALUES)] | n | 53 | |
54 | if isinstance(other, Interval): | ||||
55 | return Interval(self.length + other.length) | ||||
56 | |||||
57 | raise TypeError("Invalid operation") | ||||
57 | 58 | ||||
58 | def __neg__(self): | 59 | def __neg__(self): | ||
n | 59 | reference = Interval(self.length) | n | 60 | return Interval(-self.length) |
60 | reference.length *= -1 | ||||
61 | return reference | ||||
62 | 61 | ||||
63 | 62 | ||||
64 | class Chord: | 63 | class Chord: | ||
65 | 64 | ||||
66 | def __init__(self, root_tone, *other_tone_args): | 65 | def __init__(self, root_tone, *other_tone_args): | ||
67 | 66 | ||||
68 | self.root_tone = root_tone | 67 | self.root_tone = root_tone | ||
69 | self.tone_set = set() | 68 | self.tone_set = set() | ||
70 | self.tone_set.add(root_tone) | 69 | self.tone_set.add(root_tone) | ||
71 | 70 | ||||
72 | for arg in other_tone_args: | 71 | for arg in other_tone_args: | ||
n | 73 | if type(arg) is set: | n | 72 | if isinstance(arg, set): |
74 | for tone in arg: | 73 | for tone in arg: | ||
75 | self.tone_set.add(tone) | 74 | self.tone_set.add(tone) | ||
76 | else: | 75 | else: | ||
77 | self.tone_set.add(arg) | 76 | self.tone_set.add(arg) | ||
78 | 77 | ||||
79 | if len(self.tone_set) < 2: | 78 | if len(self.tone_set) < 2: | ||
80 | raise TypeError("Cannot have a chord made of only 1 unique tone") | 79 | raise TypeError("Cannot have a chord made of only 1 unique tone") | ||
81 | 80 | ||||
82 | def __str__(self): | 81 | def __str__(self): | ||
83 | list_of_tone_indexes = list(map(lambda x: ACCEPTED_TONES.index(x.tone), list(self.tone_set))) | 82 | list_of_tone_indexes = list(map(lambda x: ACCEPTED_TONES.index(x.tone), list(self.tone_set))) | ||
84 | list_of_tone_indexes.sort() | 83 | list_of_tone_indexes.sort() | ||
85 | 84 | ||||
86 | root_tone_index = ACCEPTED_TONES.index(self.root_tone.tone) | 85 | root_tone_index = ACCEPTED_TONES.index(self.root_tone.tone) | ||
87 | 86 | ||||
88 | tone_indexes_before_root = list(filter(lambda x: x < root_tone_index, list_of_tone_indexes)) | 87 | tone_indexes_before_root = list(filter(lambda x: x < root_tone_index, list_of_tone_indexes)) | ||
89 | 88 | ||||
90 | for tone in tone_indexes_before_root: | 89 | for tone in tone_indexes_before_root: | ||
91 | list_of_tone_indexes.remove(tone) | 90 | list_of_tone_indexes.remove(tone) | ||
92 | 91 | ||||
93 | result = "-".join(list(map(lambda x: ACCEPTED_TONES[x], list_of_tone_indexes)) + | 92 | result = "-".join(list(map(lambda x: ACCEPTED_TONES[x], list_of_tone_indexes)) + | ||
94 | list(map(lambda x: ACCEPTED_TONES[x], tone_indexes_before_root))) | 93 | list(map(lambda x: ACCEPTED_TONES[x], tone_indexes_before_root))) | ||
95 | 94 | ||||
96 | return result | 95 | return result | ||
97 | 96 | ||||
98 | def __add__(self, other): | 97 | def __add__(self, other): | ||
99 | 98 | ||||
n | 100 | if type(other) is Tone: | n | 99 | if isinstance(other, Tone): |
101 | return Chord(self.root_tone, self.tone_set, other) | 100 | return Chord(self.root_tone, self.tone_set, other) | ||
102 | 101 | ||||
n | 103 | if type(other) is Chord: | n | 102 | if isinstance(other, Chord): |
104 | return Chord(self.root_tone, self.tone_set, other.tone_set) | 103 | return Chord(self.root_tone, self.tone_set, other.tone_set) | ||
105 | 104 | ||||
106 | raise TypeError("Invalid operation") | 105 | raise TypeError("Invalid operation") | ||
107 | 106 | ||||
108 | def find_next_tone(self, reference_tone, tone_set): | 107 | def find_next_tone(self, reference_tone, tone_set): | ||
109 | 108 | ||||
110 | reference_tone_index = ACCEPTED_TONES.index(reference_tone.tone) | 109 | reference_tone_index = ACCEPTED_TONES.index(reference_tone.tone) | ||
111 | 110 | ||||
112 | for i in range(reference_tone_index + 1, len(ACCEPTED_TONES)): | 111 | for i in range(reference_tone_index + 1, len(ACCEPTED_TONES)): | ||
113 | potential_tone = Tone(ACCEPTED_TONES[i]) | 112 | potential_tone = Tone(ACCEPTED_TONES[i]) | ||
114 | if potential_tone in tone_set: | 113 | if potential_tone in tone_set: | ||
115 | return potential_tone | 114 | return potential_tone | ||
116 | 115 | ||||
117 | for i in range(0, reference_tone_index): | 116 | for i in range(0, reference_tone_index): | ||
118 | potential_tone = Tone(ACCEPTED_TONES[i]) | 117 | potential_tone = Tone(ACCEPTED_TONES[i]) | ||
119 | if potential_tone in tone_set: | 118 | if potential_tone in tone_set: | ||
120 | return potential_tone | 119 | return potential_tone | ||
121 | 120 | ||||
122 | def __sub__(self, other): | 121 | def __sub__(self, other): | ||
n | 123 | if type(other) is Tone: | n | 122 | if isinstance(other, Tone): |
124 | if other not in self.tone_set: | 123 | if other not in self.tone_set: | ||
t | 125 | raise TypeError(f"Cannot remove tone {str(other)} from chord {str(self)}") | t | 124 | raise TypeError(f"Cannot remove tone {other} from chord {self}") |
126 | 125 | ||||
127 | new_tone_set = self.tone_set.copy() | 126 | new_tone_set = self.tone_set.copy() | ||
128 | new_tone_set.remove(other) | 127 | new_tone_set.remove(other) | ||
129 | 128 | ||||
130 | if other == self.root_tone: | 129 | if other == self.root_tone: | ||
131 | new_root_tone = self.find_next_tone(self.root_tone, self.tone_set) | 130 | new_root_tone = self.find_next_tone(self.root_tone, self.tone_set) | ||
132 | return Chord(new_root_tone, new_tone_set) | 131 | return Chord(new_root_tone, new_tone_set) | ||
133 | 132 | ||||
134 | return Chord(self.root_tone, new_tone_set) | 133 | return Chord(self.root_tone, new_tone_set) | ||
135 | 134 | ||||
136 | raise TypeError("Invalid operation") | 135 | raise TypeError("Invalid operation") | ||
137 | 136 | ||||
138 | def is_minor(self): | 137 | def is_minor(self): | ||
139 | minor_third_index = INTERVAL_VALUES.index("minor 3rd") | 138 | minor_third_index = INTERVAL_VALUES.index("minor 3rd") | ||
140 | root_tone_index = ACCEPTED_TONES.index(self.root_tone.tone) | 139 | root_tone_index = ACCEPTED_TONES.index(self.root_tone.tone) | ||
141 | 140 | ||||
142 | for tone in self.tone_set: | 141 | for tone in self.tone_set: | ||
143 | if abs(root_tone_index - ACCEPTED_TONES.index(tone.tone)) == minor_third_index: | 142 | if abs(root_tone_index - ACCEPTED_TONES.index(tone.tone)) == minor_third_index: | ||
144 | return True | 143 | return True | ||
145 | 144 | ||||
146 | return False | 145 | return False | ||
147 | 146 | ||||
148 | def is_major(self): | 147 | def is_major(self): | ||
149 | minor_third_index = INTERVAL_VALUES.index("major 3rd") | 148 | minor_third_index = INTERVAL_VALUES.index("major 3rd") | ||
150 | root_tone_index = ACCEPTED_TONES.index(self.root_tone.tone) | 149 | root_tone_index = ACCEPTED_TONES.index(self.root_tone.tone) | ||
151 | 150 | ||||
152 | for tone in self.tone_set: | 151 | for tone in self.tone_set: | ||
153 | if abs(root_tone_index - ACCEPTED_TONES.index(tone.tone)) == minor_third_index: | 152 | if abs(root_tone_index - ACCEPTED_TONES.index(tone.tone)) == minor_third_index: | ||
154 | return True | 153 | return True | ||
155 | 154 | ||||
156 | return False | 155 | return False | ||
157 | 156 | ||||
158 | def is_power_chord(self): | 157 | def is_power_chord(self): | ||
159 | return not self.is_minor() and not self.is_major() | 158 | return not self.is_minor() and not self.is_major() | ||
160 | 159 | ||||
161 | def transposed(self, interval): | 160 | def transposed(self, interval): | ||
162 | return Chord(self.root_tone + interval, set(map(lambda x: x + interval, list(self.tone_set)))) | 161 | return Chord(self.root_tone + interval, set(map(lambda x: x + interval, list(self.tone_set)))) |
Legends | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
|
n | 1 | n | |||
2 | import unittest | ||||
3 | 1 | ||||
4 | ACCEPTED_TONES = ("C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B") | 2 | ACCEPTED_TONES = ("C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B") | ||
5 | INTERVAL_VALUES = ("unison", "minor 2nd", "major 2nd", "minor 3rd", "major 3rd", "perfect 4th", | 3 | INTERVAL_VALUES = ("unison", "minor 2nd", "major 2nd", "minor 3rd", "major 3rd", "perfect 4th", | ||
6 | "diminished 5th", "perfect 5th", "minor 6th", "major 6th", "minor 7th", "major 7th") | 4 | "diminished 5th", "perfect 5th", "minor 6th", "major 6th", "minor 7th", "major 7th") | ||
7 | 5 | ||||
8 | 6 | ||||
9 | class Tone: | 7 | class Tone: | ||
10 | 8 | ||||
11 | def __init__(self, tone): | 9 | def __init__(self, tone): | ||
12 | if tone not in ACCEPTED_TONES: | 10 | if tone not in ACCEPTED_TONES: | ||
13 | raise TypeError("Tone is not accepted") | 11 | raise TypeError("Tone is not accepted") | ||
14 | self.tone = tone | 12 | self.tone = tone | ||
15 | 13 | ||||
16 | def __str__(self): | 14 | def __str__(self): | ||
17 | return self.tone | 15 | return self.tone | ||
18 | 16 | ||||
19 | def __eq__(self, other): | 17 | def __eq__(self, other): | ||
20 | if type(other) is not Tone: | 18 | if type(other) is not Tone: | ||
21 | return False | 19 | return False | ||
22 | return self.tone == other.tone | 20 | return self.tone == other.tone | ||
23 | 21 | ||||
24 | def __hash__(self): | 22 | def __hash__(self): | ||
25 | return hash(self.tone) | 23 | return hash(self.tone) | ||
26 | 24 | ||||
27 | def __add__(self, other): | 25 | def __add__(self, other): | ||
28 | if type(other) is Tone: | 26 | if type(other) is Tone: | ||
29 | return Chord(self, other) | 27 | return Chord(self, other) | ||
30 | 28 | ||||
31 | if type(other) is Interval: | 29 | if type(other) is Interval: | ||
n | 32 | return Tone(ACCEPTED_TONES[(ACCEPTED_TONES.index(self.tone) + other.length)]) | n | 30 | return Tone(ACCEPTED_TONES[(ACCEPTED_TONES.index(self.tone) + other.length) % len(ACCEPTED_TONES)]) |
33 | 31 | ||||
34 | raise TypeError("Invalid operation") | 32 | raise TypeError("Invalid operation") | ||
35 | 33 | ||||
36 | def __sub__(self, other): | 34 | def __sub__(self, other): | ||
37 | if type(other) is Tone: | 35 | if type(other) is Tone: | ||
38 | return Interval(abs(ACCEPTED_TONES.index(self.tone) - ACCEPTED_TONES.index(other.tone))) | 36 | return Interval(abs(ACCEPTED_TONES.index(self.tone) - ACCEPTED_TONES.index(other.tone))) | ||
39 | 37 | ||||
40 | if type(other) is Interval: | 38 | if type(other) is Interval: | ||
41 | return Tone(ACCEPTED_TONES[(ACCEPTED_TONES.index(self.tone) - other.length) % len(ACCEPTED_TONES)]) | 39 | return Tone(ACCEPTED_TONES[(ACCEPTED_TONES.index(self.tone) - other.length) % len(ACCEPTED_TONES)]) | ||
42 | 40 | ||||
43 | raise TypeError("Invalid operation") | 41 | raise TypeError("Invalid operation") | ||
44 | 42 | ||||
45 | 43 | ||||
46 | class Interval: | 44 | class Interval: | ||
47 | 45 | ||||
48 | def __init__(self, interval_length): | 46 | def __init__(self, interval_length): | ||
49 | if type(interval_length) is not int: | 47 | if type(interval_length) is not int: | ||
50 | raise TypeError("Interval length is not int") | 48 | raise TypeError("Interval length is not int") | ||
51 | 49 | ||||
52 | self.length = interval_length % len(INTERVAL_VALUES) | 50 | self.length = interval_length % len(INTERVAL_VALUES) | ||
53 | 51 | ||||
54 | def __str__(self): | 52 | def __str__(self): | ||
n | 55 | return INTERVAL_VALUES[self.length] | n | 53 | return INTERVAL_VALUES[abs(self.length)] |
54 | |||||
55 | def __add__(self, other): | ||||
56 | return INTERVAL_VALUES[(self.length + other.length) % len(INTERVAL_VALUES)] | ||||
57 | |||||
58 | def __neg__(self): | ||||
59 | reference = Interval(self.length) | ||||
60 | reference.length *= -1 | ||||
61 | return reference | ||||
56 | 62 | ||||
57 | 63 | ||||
58 | class Chord: | 64 | class Chord: | ||
59 | 65 | ||||
60 | def __init__(self, root_tone, *other_tone_args): | 66 | def __init__(self, root_tone, *other_tone_args): | ||
61 | 67 | ||||
62 | self.root_tone = root_tone | 68 | self.root_tone = root_tone | ||
63 | self.tone_set = set() | 69 | self.tone_set = set() | ||
64 | self.tone_set.add(root_tone) | 70 | self.tone_set.add(root_tone) | ||
65 | 71 | ||||
66 | for arg in other_tone_args: | 72 | for arg in other_tone_args: | ||
67 | if type(arg) is set: | 73 | if type(arg) is set: | ||
68 | for tone in arg: | 74 | for tone in arg: | ||
69 | self.tone_set.add(tone) | 75 | self.tone_set.add(tone) | ||
70 | else: | 76 | else: | ||
71 | self.tone_set.add(arg) | 77 | self.tone_set.add(arg) | ||
72 | 78 | ||||
73 | if len(self.tone_set) < 2: | 79 | if len(self.tone_set) < 2: | ||
74 | raise TypeError("Cannot have a chord made of only 1 unique tone") | 80 | raise TypeError("Cannot have a chord made of only 1 unique tone") | ||
75 | 81 | ||||
76 | def __str__(self): | 82 | def __str__(self): | ||
77 | list_of_tone_indexes = list(map(lambda x: ACCEPTED_TONES.index(x.tone), list(self.tone_set))) | 83 | list_of_tone_indexes = list(map(lambda x: ACCEPTED_TONES.index(x.tone), list(self.tone_set))) | ||
78 | list_of_tone_indexes.sort() | 84 | list_of_tone_indexes.sort() | ||
79 | 85 | ||||
80 | root_tone_index = ACCEPTED_TONES.index(self.root_tone.tone) | 86 | root_tone_index = ACCEPTED_TONES.index(self.root_tone.tone) | ||
81 | 87 | ||||
82 | tone_indexes_before_root = list(filter(lambda x: x < root_tone_index, list_of_tone_indexes)) | 88 | tone_indexes_before_root = list(filter(lambda x: x < root_tone_index, list_of_tone_indexes)) | ||
83 | 89 | ||||
84 | for tone in tone_indexes_before_root: | 90 | for tone in tone_indexes_before_root: | ||
85 | list_of_tone_indexes.remove(tone) | 91 | list_of_tone_indexes.remove(tone) | ||
86 | 92 | ||||
87 | result = "-".join(list(map(lambda x: ACCEPTED_TONES[x], list_of_tone_indexes)) + | 93 | result = "-".join(list(map(lambda x: ACCEPTED_TONES[x], list_of_tone_indexes)) + | ||
88 | list(map(lambda x: ACCEPTED_TONES[x], tone_indexes_before_root))) | 94 | list(map(lambda x: ACCEPTED_TONES[x], tone_indexes_before_root))) | ||
89 | 95 | ||||
90 | return result | 96 | return result | ||
91 | 97 | ||||
92 | def __add__(self, other): | 98 | def __add__(self, other): | ||
93 | 99 | ||||
94 | if type(other) is Tone: | 100 | if type(other) is Tone: | ||
n | 95 | return Chord(self.root_tone, self.tone_set, other.tone) | n | 101 | return Chord(self.root_tone, self.tone_set, other) |
96 | 102 | ||||
97 | if type(other) is Chord: | 103 | if type(other) is Chord: | ||
n | 98 | return Chord(self.root_tone, self.tone_set.union(other.tone)) | n | 104 | return Chord(self.root_tone, self.tone_set, other.tone_set) |
99 | 105 | ||||
100 | raise TypeError("Invalid operation") | 106 | raise TypeError("Invalid operation") | ||
101 | 107 | ||||
102 | def find_next_tone(self, reference_tone, tone_set): | 108 | def find_next_tone(self, reference_tone, tone_set): | ||
103 | 109 | ||||
104 | reference_tone_index = ACCEPTED_TONES.index(reference_tone.tone) | 110 | reference_tone_index = ACCEPTED_TONES.index(reference_tone.tone) | ||
105 | 111 | ||||
106 | for i in range(reference_tone_index + 1, len(ACCEPTED_TONES)): | 112 | for i in range(reference_tone_index + 1, len(ACCEPTED_TONES)): | ||
107 | potential_tone = Tone(ACCEPTED_TONES[i]) | 113 | potential_tone = Tone(ACCEPTED_TONES[i]) | ||
108 | if potential_tone in tone_set: | 114 | if potential_tone in tone_set: | ||
109 | return potential_tone | 115 | return potential_tone | ||
110 | 116 | ||||
111 | for i in range(0, reference_tone_index): | 117 | for i in range(0, reference_tone_index): | ||
112 | potential_tone = Tone(ACCEPTED_TONES[i]) | 118 | potential_tone = Tone(ACCEPTED_TONES[i]) | ||
113 | if potential_tone in tone_set: | 119 | if potential_tone in tone_set: | ||
114 | return potential_tone | 120 | return potential_tone | ||
115 | 121 | ||||
116 | def __sub__(self, other): | 122 | def __sub__(self, other): | ||
117 | if type(other) is Tone: | 123 | if type(other) is Tone: | ||
118 | if other not in self.tone_set: | 124 | if other not in self.tone_set: | ||
119 | raise TypeError(f"Cannot remove tone {str(other)} from chord {str(self)}") | 125 | raise TypeError(f"Cannot remove tone {str(other)} from chord {str(self)}") | ||
120 | 126 | ||||
121 | new_tone_set = self.tone_set.copy() | 127 | new_tone_set = self.tone_set.copy() | ||
122 | new_tone_set.remove(other) | 128 | new_tone_set.remove(other) | ||
123 | 129 | ||||
124 | if other == self.root_tone: | 130 | if other == self.root_tone: | ||
125 | new_root_tone = self.find_next_tone(self.root_tone, self.tone_set) | 131 | new_root_tone = self.find_next_tone(self.root_tone, self.tone_set) | ||
126 | return Chord(new_root_tone, new_tone_set) | 132 | return Chord(new_root_tone, new_tone_set) | ||
127 | 133 | ||||
128 | return Chord(self.root_tone, new_tone_set) | 134 | return Chord(self.root_tone, new_tone_set) | ||
129 | 135 | ||||
130 | raise TypeError("Invalid operation") | 136 | raise TypeError("Invalid operation") | ||
131 | 137 | ||||
132 | def is_minor(self): | 138 | def is_minor(self): | ||
133 | minor_third_index = INTERVAL_VALUES.index("minor 3rd") | 139 | minor_third_index = INTERVAL_VALUES.index("minor 3rd") | ||
134 | root_tone_index = ACCEPTED_TONES.index(self.root_tone.tone) | 140 | root_tone_index = ACCEPTED_TONES.index(self.root_tone.tone) | ||
135 | 141 | ||||
136 | for tone in self.tone_set: | 142 | for tone in self.tone_set: | ||
137 | if abs(root_tone_index - ACCEPTED_TONES.index(tone.tone)) == minor_third_index: | 143 | if abs(root_tone_index - ACCEPTED_TONES.index(tone.tone)) == minor_third_index: | ||
138 | return True | 144 | return True | ||
139 | 145 | ||||
140 | return False | 146 | return False | ||
141 | 147 | ||||
142 | def is_major(self): | 148 | def is_major(self): | ||
143 | minor_third_index = INTERVAL_VALUES.index("major 3rd") | 149 | minor_third_index = INTERVAL_VALUES.index("major 3rd") | ||
144 | root_tone_index = ACCEPTED_TONES.index(self.root_tone.tone) | 150 | root_tone_index = ACCEPTED_TONES.index(self.root_tone.tone) | ||
145 | 151 | ||||
146 | for tone in self.tone_set: | 152 | for tone in self.tone_set: | ||
147 | if abs(root_tone_index - ACCEPTED_TONES.index(tone.tone)) == minor_third_index: | 153 | if abs(root_tone_index - ACCEPTED_TONES.index(tone.tone)) == minor_third_index: | ||
148 | return True | 154 | return True | ||
149 | 155 | ||||
150 | return False | 156 | return False | ||
151 | 157 | ||||
152 | def is_power_chord(self): | 158 | def is_power_chord(self): | ||
153 | return not self.is_minor() and not self.is_major() | 159 | return not self.is_minor() and not self.is_major() | ||
154 | 160 | ||||
155 | def transposed(self, interval): | 161 | def transposed(self, interval): | ||
156 | return Chord(self.root_tone + interval, set(map(lambda x: x + interval, list(self.tone_set)))) | 162 | return Chord(self.root_tone + interval, set(map(lambda x: x + interval, list(self.tone_set)))) | ||
t | 157 | t |
Legends | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
|
f | 1 | f | 1 | ||
2 | import unittest | 2 | import unittest | ||
3 | 3 | ||||
4 | ACCEPTED_TONES = ("C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B") | 4 | ACCEPTED_TONES = ("C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B") | ||
5 | INTERVAL_VALUES = ("unison", "minor 2nd", "major 2nd", "minor 3rd", "major 3rd", "perfect 4th", | 5 | INTERVAL_VALUES = ("unison", "minor 2nd", "major 2nd", "minor 3rd", "major 3rd", "perfect 4th", | ||
6 | "diminished 5th", "perfect 5th", "minor 6th", "major 6th", "minor 7th", "major 7th") | 6 | "diminished 5th", "perfect 5th", "minor 6th", "major 6th", "minor 7th", "major 7th") | ||
7 | 7 | ||||
8 | 8 | ||||
9 | class Tone: | 9 | class Tone: | ||
10 | 10 | ||||
11 | def __init__(self, tone): | 11 | def __init__(self, tone): | ||
12 | if tone not in ACCEPTED_TONES: | 12 | if tone not in ACCEPTED_TONES: | ||
13 | raise TypeError("Tone is not accepted") | 13 | raise TypeError("Tone is not accepted") | ||
14 | self.tone = tone | 14 | self.tone = tone | ||
15 | 15 | ||||
16 | def __str__(self): | 16 | def __str__(self): | ||
17 | return self.tone | 17 | return self.tone | ||
18 | 18 | ||||
19 | def __eq__(self, other): | 19 | def __eq__(self, other): | ||
20 | if type(other) is not Tone: | 20 | if type(other) is not Tone: | ||
21 | return False | 21 | return False | ||
22 | return self.tone == other.tone | 22 | return self.tone == other.tone | ||
23 | 23 | ||||
24 | def __hash__(self): | 24 | def __hash__(self): | ||
25 | return hash(self.tone) | 25 | return hash(self.tone) | ||
26 | 26 | ||||
27 | def __add__(self, other): | 27 | def __add__(self, other): | ||
28 | if type(other) is Tone: | 28 | if type(other) is Tone: | ||
29 | return Chord(self, other) | 29 | return Chord(self, other) | ||
30 | 30 | ||||
31 | if type(other) is Interval: | 31 | if type(other) is Interval: | ||
32 | return Tone(ACCEPTED_TONES[(ACCEPTED_TONES.index(self.tone) + other.length)]) | 32 | return Tone(ACCEPTED_TONES[(ACCEPTED_TONES.index(self.tone) + other.length)]) | ||
33 | 33 | ||||
34 | raise TypeError("Invalid operation") | 34 | raise TypeError("Invalid operation") | ||
35 | 35 | ||||
36 | def __sub__(self, other): | 36 | def __sub__(self, other): | ||
37 | if type(other) is Tone: | 37 | if type(other) is Tone: | ||
38 | return Interval(abs(ACCEPTED_TONES.index(self.tone) - ACCEPTED_TONES.index(other.tone))) | 38 | return Interval(abs(ACCEPTED_TONES.index(self.tone) - ACCEPTED_TONES.index(other.tone))) | ||
39 | 39 | ||||
40 | if type(other) is Interval: | 40 | if type(other) is Interval: | ||
41 | return Tone(ACCEPTED_TONES[(ACCEPTED_TONES.index(self.tone) - other.length) % len(ACCEPTED_TONES)]) | 41 | return Tone(ACCEPTED_TONES[(ACCEPTED_TONES.index(self.tone) - other.length) % len(ACCEPTED_TONES)]) | ||
42 | 42 | ||||
43 | raise TypeError("Invalid operation") | 43 | raise TypeError("Invalid operation") | ||
44 | 44 | ||||
45 | 45 | ||||
46 | class Interval: | 46 | class Interval: | ||
47 | 47 | ||||
48 | def __init__(self, interval_length): | 48 | def __init__(self, interval_length): | ||
49 | if type(interval_length) is not int: | 49 | if type(interval_length) is not int: | ||
50 | raise TypeError("Interval length is not int") | 50 | raise TypeError("Interval length is not int") | ||
51 | 51 | ||||
52 | self.length = interval_length % len(INTERVAL_VALUES) | 52 | self.length = interval_length % len(INTERVAL_VALUES) | ||
53 | 53 | ||||
54 | def __str__(self): | 54 | def __str__(self): | ||
55 | return INTERVAL_VALUES[self.length] | 55 | return INTERVAL_VALUES[self.length] | ||
56 | 56 | ||||
57 | 57 | ||||
58 | class Chord: | 58 | class Chord: | ||
59 | 59 | ||||
60 | def __init__(self, root_tone, *other_tone_args): | 60 | def __init__(self, root_tone, *other_tone_args): | ||
61 | 61 | ||||
62 | self.root_tone = root_tone | 62 | self.root_tone = root_tone | ||
63 | self.tone_set = set() | 63 | self.tone_set = set() | ||
64 | self.tone_set.add(root_tone) | 64 | self.tone_set.add(root_tone) | ||
65 | 65 | ||||
66 | for arg in other_tone_args: | 66 | for arg in other_tone_args: | ||
67 | if type(arg) is set: | 67 | if type(arg) is set: | ||
68 | for tone in arg: | 68 | for tone in arg: | ||
69 | self.tone_set.add(tone) | 69 | self.tone_set.add(tone) | ||
70 | else: | 70 | else: | ||
71 | self.tone_set.add(arg) | 71 | self.tone_set.add(arg) | ||
72 | 72 | ||||
73 | if len(self.tone_set) < 2: | 73 | if len(self.tone_set) < 2: | ||
74 | raise TypeError("Cannot have a chord made of only 1 unique tone") | 74 | raise TypeError("Cannot have a chord made of only 1 unique tone") | ||
75 | 75 | ||||
76 | def __str__(self): | 76 | def __str__(self): | ||
77 | list_of_tone_indexes = list(map(lambda x: ACCEPTED_TONES.index(x.tone), list(self.tone_set))) | 77 | list_of_tone_indexes = list(map(lambda x: ACCEPTED_TONES.index(x.tone), list(self.tone_set))) | ||
78 | list_of_tone_indexes.sort() | 78 | list_of_tone_indexes.sort() | ||
79 | 79 | ||||
80 | root_tone_index = ACCEPTED_TONES.index(self.root_tone.tone) | 80 | root_tone_index = ACCEPTED_TONES.index(self.root_tone.tone) | ||
81 | 81 | ||||
82 | tone_indexes_before_root = list(filter(lambda x: x < root_tone_index, list_of_tone_indexes)) | 82 | tone_indexes_before_root = list(filter(lambda x: x < root_tone_index, list_of_tone_indexes)) | ||
83 | 83 | ||||
84 | for tone in tone_indexes_before_root: | 84 | for tone in tone_indexes_before_root: | ||
85 | list_of_tone_indexes.remove(tone) | 85 | list_of_tone_indexes.remove(tone) | ||
86 | 86 | ||||
87 | result = "-".join(list(map(lambda x: ACCEPTED_TONES[x], list_of_tone_indexes)) + | 87 | result = "-".join(list(map(lambda x: ACCEPTED_TONES[x], list_of_tone_indexes)) + | ||
88 | list(map(lambda x: ACCEPTED_TONES[x], tone_indexes_before_root))) | 88 | list(map(lambda x: ACCEPTED_TONES[x], tone_indexes_before_root))) | ||
89 | 89 | ||||
90 | return result | 90 | return result | ||
91 | 91 | ||||
92 | def __add__(self, other): | 92 | def __add__(self, other): | ||
93 | 93 | ||||
94 | if type(other) is Tone: | 94 | if type(other) is Tone: | ||
95 | return Chord(self.root_tone, self.tone_set, other.tone) | 95 | return Chord(self.root_tone, self.tone_set, other.tone) | ||
96 | 96 | ||||
97 | if type(other) is Chord: | 97 | if type(other) is Chord: | ||
n | 98 | return Chord(self.root_tone, self.tone_set.union(other.tone_set)) | n | 98 | return Chord(self.root_tone, self.tone_set.union(other.tone)) |
99 | 99 | ||||
100 | raise TypeError("Invalid operation") | 100 | raise TypeError("Invalid operation") | ||
101 | 101 | ||||
n | n | 102 | def find_next_tone(self, reference_tone, tone_set): | ||
103 | |||||
104 | reference_tone_index = ACCEPTED_TONES.index(reference_tone.tone) | ||||
105 | |||||
106 | for i in range(reference_tone_index + 1, len(ACCEPTED_TONES)): | ||||
107 | potential_tone = Tone(ACCEPTED_TONES[i]) | ||||
108 | if potential_tone in tone_set: | ||||
109 | return potential_tone | ||||
110 | |||||
111 | for i in range(0, reference_tone_index): | ||||
112 | potential_tone = Tone(ACCEPTED_TONES[i]) | ||||
113 | if potential_tone in tone_set: | ||||
114 | return potential_tone | ||||
115 | |||||
102 | def __sub__(self, other): | 116 | def __sub__(self, other): | ||
n | n | 117 | if type(other) is Tone: | ||
118 | if other not in self.tone_set: | ||||
119 | raise TypeError(f"Cannot remove tone {str(other)} from chord {str(self)}") | ||||
103 | 120 | ||||
n | 104 | if type(other) is Tone: | n | 121 | new_tone_set = self.tone_set.copy() |
105 | pass | 122 | new_tone_set.remove(other) | ||
123 | |||||
124 | if other == self.root_tone: | ||||
125 | new_root_tone = self.find_next_tone(self.root_tone, self.tone_set) | ||||
126 | return Chord(new_root_tone, new_tone_set) | ||||
127 | |||||
128 | return Chord(self.root_tone, new_tone_set) | ||||
106 | 129 | ||||
107 | raise TypeError("Invalid operation") | 130 | raise TypeError("Invalid operation") | ||
108 | 131 | ||||
109 | def is_minor(self): | 132 | def is_minor(self): | ||
110 | minor_third_index = INTERVAL_VALUES.index("minor 3rd") | 133 | minor_third_index = INTERVAL_VALUES.index("minor 3rd") | ||
111 | root_tone_index = ACCEPTED_TONES.index(self.root_tone.tone) | 134 | root_tone_index = ACCEPTED_TONES.index(self.root_tone.tone) | ||
112 | 135 | ||||
113 | for tone in self.tone_set: | 136 | for tone in self.tone_set: | ||
114 | if abs(root_tone_index - ACCEPTED_TONES.index(tone.tone)) == minor_third_index: | 137 | if abs(root_tone_index - ACCEPTED_TONES.index(tone.tone)) == minor_third_index: | ||
115 | return True | 138 | return True | ||
116 | 139 | ||||
117 | return False | 140 | return False | ||
118 | 141 | ||||
119 | def is_major(self): | 142 | def is_major(self): | ||
120 | minor_third_index = INTERVAL_VALUES.index("major 3rd") | 143 | minor_third_index = INTERVAL_VALUES.index("major 3rd") | ||
121 | root_tone_index = ACCEPTED_TONES.index(self.root_tone.tone) | 144 | root_tone_index = ACCEPTED_TONES.index(self.root_tone.tone) | ||
122 | 145 | ||||
123 | for tone in self.tone_set: | 146 | for tone in self.tone_set: | ||
124 | if abs(root_tone_index - ACCEPTED_TONES.index(tone.tone)) == minor_third_index: | 147 | if abs(root_tone_index - ACCEPTED_TONES.index(tone.tone)) == minor_third_index: | ||
125 | return True | 148 | return True | ||
126 | 149 | ||||
127 | return False | 150 | return False | ||
128 | 151 | ||||
129 | def is_power_chord(self): | 152 | def is_power_chord(self): | ||
130 | return not self.is_minor() and not self.is_major() | 153 | return not self.is_minor() and not self.is_major() | ||
t | t | 154 | |||
155 | def transposed(self, interval): | ||||
156 | return Chord(self.root_tone + interval, set(map(lambda x: x + interval, list(self.tone_set)))) | ||||
157 |
Legends | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
|
f | 1 | f | 1 | ||
2 | import unittest | 2 | import unittest | ||
3 | 3 | ||||
4 | ACCEPTED_TONES = ("C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B") | 4 | ACCEPTED_TONES = ("C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B") | ||
5 | INTERVAL_VALUES = ("unison", "minor 2nd", "major 2nd", "minor 3rd", "major 3rd", "perfect 4th", | 5 | INTERVAL_VALUES = ("unison", "minor 2nd", "major 2nd", "minor 3rd", "major 3rd", "perfect 4th", | ||
6 | "diminished 5th", "perfect 5th", "minor 6th", "major 6th", "minor 7th", "major 7th") | 6 | "diminished 5th", "perfect 5th", "minor 6th", "major 6th", "minor 7th", "major 7th") | ||
7 | 7 | ||||
8 | 8 | ||||
9 | class Tone: | 9 | class Tone: | ||
10 | 10 | ||||
11 | def __init__(self, tone): | 11 | def __init__(self, tone): | ||
12 | if tone not in ACCEPTED_TONES: | 12 | if tone not in ACCEPTED_TONES: | ||
13 | raise TypeError("Tone is not accepted") | 13 | raise TypeError("Tone is not accepted") | ||
14 | self.tone = tone | 14 | self.tone = tone | ||
15 | 15 | ||||
16 | def __str__(self): | 16 | def __str__(self): | ||
17 | return self.tone | 17 | return self.tone | ||
18 | 18 | ||||
19 | def __eq__(self, other): | 19 | def __eq__(self, other): | ||
20 | if type(other) is not Tone: | 20 | if type(other) is not Tone: | ||
21 | return False | 21 | return False | ||
22 | return self.tone == other.tone | 22 | return self.tone == other.tone | ||
23 | 23 | ||||
24 | def __hash__(self): | 24 | def __hash__(self): | ||
25 | return hash(self.tone) | 25 | return hash(self.tone) | ||
26 | 26 | ||||
27 | def __add__(self, other): | 27 | def __add__(self, other): | ||
28 | if type(other) is Tone: | 28 | if type(other) is Tone: | ||
29 | return Chord(self, other) | 29 | return Chord(self, other) | ||
30 | 30 | ||||
31 | if type(other) is Interval: | 31 | if type(other) is Interval: | ||
32 | return Tone(ACCEPTED_TONES[(ACCEPTED_TONES.index(self.tone) + other.length)]) | 32 | return Tone(ACCEPTED_TONES[(ACCEPTED_TONES.index(self.tone) + other.length)]) | ||
33 | 33 | ||||
34 | raise TypeError("Invalid operation") | 34 | raise TypeError("Invalid operation") | ||
35 | 35 | ||||
36 | def __sub__(self, other): | 36 | def __sub__(self, other): | ||
37 | if type(other) is Tone: | 37 | if type(other) is Tone: | ||
38 | return Interval(abs(ACCEPTED_TONES.index(self.tone) - ACCEPTED_TONES.index(other.tone))) | 38 | return Interval(abs(ACCEPTED_TONES.index(self.tone) - ACCEPTED_TONES.index(other.tone))) | ||
39 | 39 | ||||
40 | if type(other) is Interval: | 40 | if type(other) is Interval: | ||
41 | return Tone(ACCEPTED_TONES[(ACCEPTED_TONES.index(self.tone) - other.length) % len(ACCEPTED_TONES)]) | 41 | return Tone(ACCEPTED_TONES[(ACCEPTED_TONES.index(self.tone) - other.length) % len(ACCEPTED_TONES)]) | ||
42 | 42 | ||||
43 | raise TypeError("Invalid operation") | 43 | raise TypeError("Invalid operation") | ||
44 | 44 | ||||
45 | 45 | ||||
46 | class Interval: | 46 | class Interval: | ||
47 | 47 | ||||
48 | def __init__(self, interval_length): | 48 | def __init__(self, interval_length): | ||
49 | if type(interval_length) is not int: | 49 | if type(interval_length) is not int: | ||
50 | raise TypeError("Interval length is not int") | 50 | raise TypeError("Interval length is not int") | ||
51 | 51 | ||||
52 | self.length = interval_length % len(INTERVAL_VALUES) | 52 | self.length = interval_length % len(INTERVAL_VALUES) | ||
53 | 53 | ||||
54 | def __str__(self): | 54 | def __str__(self): | ||
55 | return INTERVAL_VALUES[self.length] | 55 | return INTERVAL_VALUES[self.length] | ||
56 | 56 | ||||
57 | 57 | ||||
58 | class Chord: | 58 | class Chord: | ||
59 | 59 | ||||
60 | def __init__(self, root_tone, *other_tone_args): | 60 | def __init__(self, root_tone, *other_tone_args): | ||
61 | 61 | ||||
62 | self.root_tone = root_tone | 62 | self.root_tone = root_tone | ||
63 | self.tone_set = set() | 63 | self.tone_set = set() | ||
64 | self.tone_set.add(root_tone) | 64 | self.tone_set.add(root_tone) | ||
65 | 65 | ||||
66 | for arg in other_tone_args: | 66 | for arg in other_tone_args: | ||
67 | if type(arg) is set: | 67 | if type(arg) is set: | ||
68 | for tone in arg: | 68 | for tone in arg: | ||
69 | self.tone_set.add(tone) | 69 | self.tone_set.add(tone) | ||
70 | else: | 70 | else: | ||
71 | self.tone_set.add(arg) | 71 | self.tone_set.add(arg) | ||
72 | 72 | ||||
73 | if len(self.tone_set) < 2: | 73 | if len(self.tone_set) < 2: | ||
74 | raise TypeError("Cannot have a chord made of only 1 unique tone") | 74 | raise TypeError("Cannot have a chord made of only 1 unique tone") | ||
75 | 75 | ||||
76 | def __str__(self): | 76 | def __str__(self): | ||
77 | list_of_tone_indexes = list(map(lambda x: ACCEPTED_TONES.index(x.tone), list(self.tone_set))) | 77 | list_of_tone_indexes = list(map(lambda x: ACCEPTED_TONES.index(x.tone), list(self.tone_set))) | ||
78 | list_of_tone_indexes.sort() | 78 | list_of_tone_indexes.sort() | ||
79 | 79 | ||||
80 | root_tone_index = ACCEPTED_TONES.index(self.root_tone.tone) | 80 | root_tone_index = ACCEPTED_TONES.index(self.root_tone.tone) | ||
81 | 81 | ||||
82 | tone_indexes_before_root = list(filter(lambda x: x < root_tone_index, list_of_tone_indexes)) | 82 | tone_indexes_before_root = list(filter(lambda x: x < root_tone_index, list_of_tone_indexes)) | ||
83 | 83 | ||||
84 | for tone in tone_indexes_before_root: | 84 | for tone in tone_indexes_before_root: | ||
85 | list_of_tone_indexes.remove(tone) | 85 | list_of_tone_indexes.remove(tone) | ||
86 | 86 | ||||
87 | result = "-".join(list(map(lambda x: ACCEPTED_TONES[x], list_of_tone_indexes)) + | 87 | result = "-".join(list(map(lambda x: ACCEPTED_TONES[x], list_of_tone_indexes)) + | ||
88 | list(map(lambda x: ACCEPTED_TONES[x], tone_indexes_before_root))) | 88 | list(map(lambda x: ACCEPTED_TONES[x], tone_indexes_before_root))) | ||
89 | 89 | ||||
90 | return result | 90 | return result | ||
91 | 91 | ||||
92 | def __add__(self, other): | 92 | def __add__(self, other): | ||
93 | 93 | ||||
94 | if type(other) is Tone: | 94 | if type(other) is Tone: | ||
95 | return Chord(self.root_tone, self.tone_set, other.tone) | 95 | return Chord(self.root_tone, self.tone_set, other.tone) | ||
96 | 96 | ||||
97 | if type(other) is Chord: | 97 | if type(other) is Chord: | ||
98 | return Chord(self.root_tone, self.tone_set.union(other.tone_set)) | 98 | return Chord(self.root_tone, self.tone_set.union(other.tone_set)) | ||
99 | 99 | ||||
100 | raise TypeError("Invalid operation") | 100 | raise TypeError("Invalid operation") | ||
101 | 101 | ||||
102 | def __sub__(self, other): | 102 | def __sub__(self, other): | ||
103 | 103 | ||||
104 | if type(other) is Tone: | 104 | if type(other) is Tone: | ||
105 | pass | 105 | pass | ||
106 | 106 | ||||
107 | raise TypeError("Invalid operation") | 107 | raise TypeError("Invalid operation") | ||
108 | 108 | ||||
109 | def is_minor(self): | 109 | def is_minor(self): | ||
110 | minor_third_index = INTERVAL_VALUES.index("minor 3rd") | 110 | minor_third_index = INTERVAL_VALUES.index("minor 3rd") | ||
111 | root_tone_index = ACCEPTED_TONES.index(self.root_tone.tone) | 111 | root_tone_index = ACCEPTED_TONES.index(self.root_tone.tone) | ||
112 | 112 | ||||
113 | for tone in self.tone_set: | 113 | for tone in self.tone_set: | ||
114 | if abs(root_tone_index - ACCEPTED_TONES.index(tone.tone)) == minor_third_index: | 114 | if abs(root_tone_index - ACCEPTED_TONES.index(tone.tone)) == minor_third_index: | ||
115 | return True | 115 | return True | ||
116 | 116 | ||||
117 | return False | 117 | return False | ||
118 | 118 | ||||
119 | def is_major(self): | 119 | def is_major(self): | ||
120 | minor_third_index = INTERVAL_VALUES.index("major 3rd") | 120 | minor_third_index = INTERVAL_VALUES.index("major 3rd") | ||
121 | root_tone_index = ACCEPTED_TONES.index(self.root_tone.tone) | 121 | root_tone_index = ACCEPTED_TONES.index(self.root_tone.tone) | ||
122 | 122 | ||||
123 | for tone in self.tone_set: | 123 | for tone in self.tone_set: | ||
124 | if abs(root_tone_index - ACCEPTED_TONES.index(tone.tone)) == minor_third_index: | 124 | if abs(root_tone_index - ACCEPTED_TONES.index(tone.tone)) == minor_third_index: | ||
125 | return True | 125 | return True | ||
126 | 126 | ||||
127 | return False | 127 | return False | ||
128 | 128 | ||||
129 | def is_power_chord(self): | 129 | def is_power_chord(self): | ||
130 | return not self.is_minor() and not self.is_major() | 130 | return not self.is_minor() and not self.is_major() | ||
t | 131 | t | |||
132 | |||||
133 | c5_chord = Chord(Tone("C"), Tone("G")) | ||||
134 | this_other_chord = Chord(Tone("A"), Tone("B")) | ||||
135 | result_chord = c5_chord + this_other_chord | ||||
136 | print(result_chord) |
Legends | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
|