1CHORD_ERROR_MESSAGE = "Cannot have a chord made of only 1 unique tone"
2INVALID_OPERATION_MESSAGE = "Invalid operation"
3TONES = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]
4INTERVALS = ["unison", "minor 2nd", "major 2nd", "minor 3rd", "major 3rd", "perfect 4th", "diminished 5th", "perfect 5th", "minor 6th", "major 6th", "minor 7th", "major 7th"]
5
6class Tone:
7 def __init__(self, tone):
8 self.tone = tone
9
10 def __str__(self):
11 return str(self.tone)
12
13 def __iter__(self):
14 return iter(self.tone)
15
16 def __add__(self, other):
17 if isinstance(other, Tone):
18 return Chord(self, other)
19 if isinstance(other, Interval):
20 return Tone(TONES[(TONES.index(str(self)) + other.num_of_semitones) % len(TONES)])
21
22 def __sub__(self, other):
23 if isinstance(other, Tone):
24 return Interval(abs(TONES.index(str(self)) - TONES.index(str(other))))
25 if isinstance(other, Interval):
26 return Tone(TONES[(TONES.index(str(self)) - other.num_of_semitones) % len(TONES)])
27
28
29class Interval:
30 def __init__(self, num_of_tones):
31 self.is_positive = num_of_tones >= 0
32 num_of_tones = abs(num_of_tones)
33 self.num_of_semitones = num_of_tones % len(INTERVALS)
34
35 def __str__(self):
36 return str(INTERVALS[self.num_of_semitones])
37
38 def __int__(self):
39 return self.num_of_semitones
40
41 def __add__(self, other):
42 if isinstance(other, Tone):
43 raise TypeError(INVALID_OPERATION_MESSAGE)
44 if isinstance(other, Interval):
45 return Interval(int(self) + int(other))
46
47 def __sub__(self, other):
48 if isinstance(other, Tone):
49 raise TypeError(INVALID_OPERATION_MESSAGE)
50
51 def __neg__(self):
52 return Interval(-self.num_of_semitones)
53
54class Chord:
55 def __init__(self, *tones):
56 self.tones = []
57 for tone in tones:
58 if str(tone) not in self.tones:
59 self.tones.append(str(tone))
60 self.root = self.tones[0]
61 if len(self.tones) == 1:
62 raise TypeError(CHORD_ERROR_MESSAGE)
63 tone_index = TONES.index(str(self.root))
64 self.scale = TONES[tone_index:] + TONES[:tone_index]
65 self.tones = sorted(self.tones, key=lambda tone: self.scale.index(str(tone)))
66
67 def __str__(self):
68 return '-'.join(map(str, self.tones))
69
70 def is_major(self):
71 for tone in self.tones:
72 if abs(TONES.index(str(self.root)) - TONES.index(str(tone))) == INTERVALS.index("major 3rd"):
73 return True
74 return False
75
76 def is_minor(self):
77 for tone in self.tones:
78 if abs(TONES.index(str(self.root)) - TONES.index(str(tone))) == INTERVALS.index("minor 3rd"):
79 return True
80 return False
81
82 def is_power_chord(self):
83 if self.is_minor() or self.is_major():
84 return False
85 return True
86
87 def __add__(self, other):
88 if isinstance(other, Tone):
89 new_tones = self.tones + [other]
90 if isinstance(other, Chord):
91 new_tones = self.tones + other.tones
92 return Chord(*new_tones)
93
94 def __sub__(self, other):
95 if isinstance(other, Tone):
96 if str(other) not in self.tones:
97 raise TypeError(f"Cannot remove tone {other} from chord {str(self)}")
98 if len(self.tones) < 3:
99 raise TypeError("Cannot have a chord made of only 1 unique tone")
100 to_return = Chord(*self.tones)
101 if str(other) == str(to_return.root):
102 to_return.root = to_return.tones[1]
103 del to_return.tones[0]
104 else:
105 other_index = to_return.tones.index(str(other))
106 del to_return.tones[other_index]
107 return to_return
108
109 def transposed(self, interval):
110 if isinstance(interval, Interval):
111 new_tones = []
112 direction = 1 if interval.is_positive else -1
113 for tone in self.tones:
114 new_index = self.scale.index(str(tone)) + direction*int(interval)
115 new_tones.append(str(self.scale[new_index]))
116 return Chord(*new_tones)
....FFF...EF.......................F.
======================================================================
ERROR: test_transposed_overflow (test.TestBasicChordFunctionality.test_transposed_overflow)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 143, in test_transposed_overflow
result_chord = d_major_chord.transposed(Interval(8))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/tmp/solution.py", line 115, in transposed
new_tones.append(str(self.scale[new_index]))
~~~~~~~~~~^^^^^^^^^^^
IndexError: list index out of range
======================================================================
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_interval_negative (test.TestBasicIntervalFunctionality.test_interval_negative)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 51, in test_interval_negative
self.assertEqual(str(minor_2nd), "minor 2nd")
AssertionError: 'major 7th' != 'minor 2nd'
- major 7th
+ minor 2nd
======================================================================
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, errors=1)
f | 1 | CHORD_ERROR_MESSAGE = "Cannot have a chord made of only 1 unique tone" | f | 1 | CHORD_ERROR_MESSAGE = "Cannot have a chord made of only 1 unique tone" |
2 | INVALID_OPERATION_MESSAGE = "Invalid operation" | 2 | INVALID_OPERATION_MESSAGE = "Invalid operation" | ||
3 | TONES = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"] | 3 | TONES = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"] | ||
4 | INTERVALS = ["unison", "minor 2nd", "major 2nd", "minor 3rd", "major 3rd", "perfect 4th", "diminished 5th", "perfect 5th", "minor 6th", "major 6th", "minor 7th", "major 7th"] | 4 | INTERVALS = ["unison", "minor 2nd", "major 2nd", "minor 3rd", "major 3rd", "perfect 4th", "diminished 5th", "perfect 5th", "minor 6th", "major 6th", "minor 7th", "major 7th"] | ||
5 | 5 | ||||
6 | class Tone: | 6 | class Tone: | ||
7 | def __init__(self, tone): | 7 | def __init__(self, tone): | ||
8 | self.tone = tone | 8 | self.tone = tone | ||
9 | 9 | ||||
10 | def __str__(self): | 10 | def __str__(self): | ||
11 | return str(self.tone) | 11 | return str(self.tone) | ||
12 | 12 | ||||
13 | def __iter__(self): | 13 | def __iter__(self): | ||
14 | return iter(self.tone) | 14 | return iter(self.tone) | ||
15 | 15 | ||||
16 | def __add__(self, other): | 16 | def __add__(self, other): | ||
17 | if isinstance(other, Tone): | 17 | if isinstance(other, Tone): | ||
18 | return Chord(self, other) | 18 | return Chord(self, other) | ||
19 | if isinstance(other, Interval): | 19 | if isinstance(other, Interval): | ||
n | 20 | return Tone(TONES[(TONES.index(str(self)) + other.num_of_semitones)%len(TONES)]) | n | 20 | return Tone(TONES[(TONES.index(str(self)) + other.num_of_semitones) % len(TONES)]) |
21 | 21 | ||||
22 | def __sub__(self, other): | 22 | def __sub__(self, other): | ||
23 | if isinstance(other, Tone): | 23 | if isinstance(other, Tone): | ||
n | 24 | return Interval(abs(TONES.index(str(self))-TONES.index(str(other)))) | n | 24 | return Interval(abs(TONES.index(str(self)) - TONES.index(str(other)))) |
25 | if isinstance(other, Interval): | 25 | if isinstance(other, Interval): | ||
n | 26 | return Tone(TONES[(TONES.index(str(self)) - other.num_of_semitones)%len(TONES)]) | n | 26 | return Tone(TONES[(TONES.index(str(self)) - other.num_of_semitones) % len(TONES)]) |
27 | 27 | ||||
28 | 28 | ||||
29 | class Interval: | 29 | class Interval: | ||
30 | def __init__(self, num_of_tones): | 30 | def __init__(self, num_of_tones): | ||
n | 31 | if num_of_tones >= 0: | n | 31 | self.is_positive = num_of_tones >= 0 |
32 | self.is_positive = True | ||||
33 | else: | ||||
34 | self.is_positive = False | ||||
35 | num_of_tones = abs(num_of_tones) | 32 | num_of_tones = abs(num_of_tones) | ||
36 | self.num_of_semitones = num_of_tones % len(INTERVALS) | 33 | self.num_of_semitones = num_of_tones % len(INTERVALS) | ||
37 | 34 | ||||
38 | def __str__(self): | 35 | def __str__(self): | ||
39 | return str(INTERVALS[self.num_of_semitones]) | 36 | return str(INTERVALS[self.num_of_semitones]) | ||
40 | 37 | ||||
41 | def __int__(self): | 38 | def __int__(self): | ||
42 | return self.num_of_semitones | 39 | return self.num_of_semitones | ||
43 | 40 | ||||
44 | def __add__(self, other): | 41 | def __add__(self, other): | ||
45 | if isinstance(other, Tone): | 42 | if isinstance(other, Tone): | ||
46 | raise TypeError(INVALID_OPERATION_MESSAGE) | 43 | raise TypeError(INVALID_OPERATION_MESSAGE) | ||
47 | if isinstance(other, Interval): | 44 | if isinstance(other, Interval): | ||
48 | return Interval(int(self) + int(other)) | 45 | return Interval(int(self) + int(other)) | ||
49 | 46 | ||||
50 | def __sub__(self, other): | 47 | def __sub__(self, other): | ||
51 | if isinstance(other, Tone): | 48 | if isinstance(other, Tone): | ||
52 | raise TypeError(INVALID_OPERATION_MESSAGE) | 49 | raise TypeError(INVALID_OPERATION_MESSAGE) | ||
53 | 50 | ||||
54 | def __neg__(self): | 51 | def __neg__(self): | ||
n | 55 | return Interval(-1*(self.num_of_semitones)) | n | 52 | return Interval(-self.num_of_semitones) |
56 | 53 | ||||
57 | class Chord: | 54 | class Chord: | ||
58 | def __init__(self, *tones): | 55 | def __init__(self, *tones): | ||
59 | self.tones = [] | 56 | self.tones = [] | ||
60 | for tone in tones: | 57 | for tone in tones: | ||
61 | if str(tone) not in self.tones: | 58 | if str(tone) not in self.tones: | ||
62 | self.tones.append(str(tone)) | 59 | self.tones.append(str(tone)) | ||
63 | self.root = self.tones[0] | 60 | self.root = self.tones[0] | ||
64 | if len(self.tones) == 1: | 61 | if len(self.tones) == 1: | ||
65 | raise TypeError(CHORD_ERROR_MESSAGE) | 62 | raise TypeError(CHORD_ERROR_MESSAGE) | ||
66 | tone_index = TONES.index(str(self.root)) | 63 | tone_index = TONES.index(str(self.root)) | ||
67 | self.scale = TONES[tone_index:] + TONES[:tone_index] | 64 | self.scale = TONES[tone_index:] + TONES[:tone_index] | ||
68 | self.tones = sorted(self.tones, key=lambda tone: self.scale.index(str(tone))) | 65 | self.tones = sorted(self.tones, key=lambda tone: self.scale.index(str(tone))) | ||
69 | 66 | ||||
n | 70 | def sort_tones(self): | n | ||
71 | self.tones = sorted(self.tones, key=lambda tone: self.scale.index(str(tone))) | ||||
72 | |||||
73 | def __str__(self): | 67 | def __str__(self): | ||
74 | return '-'.join(map(str, self.tones)) | 68 | return '-'.join(map(str, self.tones)) | ||
75 | 69 | ||||
76 | def is_major(self): | 70 | def is_major(self): | ||
77 | for tone in self.tones: | 71 | for tone in self.tones: | ||
78 | if abs(TONES.index(str(self.root)) - TONES.index(str(tone))) == INTERVALS.index("major 3rd"): | 72 | if abs(TONES.index(str(self.root)) - TONES.index(str(tone))) == INTERVALS.index("major 3rd"): | ||
79 | return True | 73 | return True | ||
80 | return False | 74 | return False | ||
81 | 75 | ||||
82 | def is_minor(self): | 76 | def is_minor(self): | ||
83 | for tone in self.tones: | 77 | for tone in self.tones: | ||
84 | if abs(TONES.index(str(self.root)) - TONES.index(str(tone))) == INTERVALS.index("minor 3rd"): | 78 | if abs(TONES.index(str(self.root)) - TONES.index(str(tone))) == INTERVALS.index("minor 3rd"): | ||
85 | return True | 79 | return True | ||
86 | return False | 80 | return False | ||
87 | 81 | ||||
88 | def is_power_chord(self): | 82 | def is_power_chord(self): | ||
89 | if self.is_minor() or self.is_major(): | 83 | if self.is_minor() or self.is_major(): | ||
90 | return False | 84 | return False | ||
91 | return True | 85 | return True | ||
92 | 86 | ||||
93 | def __add__(self, other): | 87 | def __add__(self, other): | ||
94 | if isinstance(other, Tone): | 88 | if isinstance(other, Tone): | ||
95 | new_tones = self.tones + [other] | 89 | new_tones = self.tones + [other] | ||
n | 96 | return Chord(*new_tones) | n | ||
97 | if isinstance(other, Chord): | 90 | if isinstance(other, Chord): | ||
98 | new_tones = self.tones + other.tones | 91 | new_tones = self.tones + other.tones | ||
n | 99 | return Chord(*new_tones) | n | 92 | return Chord(*new_tones) |
100 | 93 | ||||
101 | def __sub__(self, other): | 94 | def __sub__(self, other): | ||
102 | if isinstance(other, Tone): | 95 | if isinstance(other, Tone): | ||
103 | if str(other) not in self.tones: | 96 | if str(other) not in self.tones: | ||
104 | raise TypeError(f"Cannot remove tone {other} from chord {str(self)}") | 97 | raise TypeError(f"Cannot remove tone {other} from chord {str(self)}") | ||
105 | if len(self.tones) < 3: | 98 | if len(self.tones) < 3: | ||
106 | raise TypeError("Cannot have a chord made of only 1 unique tone") | 99 | raise TypeError("Cannot have a chord made of only 1 unique tone") | ||
107 | to_return = Chord(*self.tones) | 100 | to_return = Chord(*self.tones) | ||
108 | if str(other) == str(to_return.root): | 101 | if str(other) == str(to_return.root): | ||
109 | to_return.root = to_return.tones[1] | 102 | to_return.root = to_return.tones[1] | ||
110 | del to_return.tones[0] | 103 | del to_return.tones[0] | ||
111 | else: | 104 | else: | ||
112 | other_index = to_return.tones.index(str(other)) | 105 | other_index = to_return.tones.index(str(other)) | ||
113 | del to_return.tones[other_index] | 106 | del to_return.tones[other_index] | ||
114 | return to_return | 107 | return to_return | ||
115 | 108 | ||||
116 | def transposed(self, interval): | 109 | def transposed(self, interval): | ||
117 | if isinstance(interval, Interval): | 110 | if isinstance(interval, Interval): | ||
118 | new_tones = [] | 111 | new_tones = [] | ||
t | 119 | if interval.is_positive: | t | 112 | direction = 1 if interval.is_positive else -1 |
120 | for tone in self.tones: | 113 | for tone in self.tones: | ||
121 | new_index = self.scale.index(str(tone)) + int(interval) | 114 | new_index = self.scale.index(str(tone)) + direction*int(interval) | ||
122 | new_tones.append(str(self.scale[new_index])) | 115 | new_tones.append(str(self.scale[new_index])) | ||
123 | else: | ||||
124 | for tone in self.tones: | ||||
125 | new_index = self.scale.index(str(tone)) - int(interval) | ||||
126 | new_tones.append(str(self.scale[new_index])) | ||||
127 | return Chord(*new_tones) | 116 | return Chord(*new_tones) |
Legends | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
|