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