1NOTE_ORDER = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B']
2
3class Tone:
4 def __init__(self, name):
5 if name in NOTE_ORDER:
6 self.name = name
7 else:
8 raise TypeError('Invalid note')
9
10 def __str__(self):
11 return self.name
12
13 # We use these two methods cause apparently the set doesn't work without them
14 def __eq__(self, other):
15 return self.name == other.name
16
17 def __hash__(self):
18 return hash(self.name)
19
20 def __add__(self, other):
21 if isinstance(other, Tone):
22 return Chord(self, other)
23 if isinstance(other, Interval):
24 tone_index = (NOTE_ORDER.index(self.name) + other.number_of_semitones) % 12
25 return Tone(NOTE_ORDER[tone_index])
26 return 'Invalid operation'
27
28 def __radd__(self, other):
29 raise TypeError('Invalid operation')
30
31 def __sub__(self, other):
32 if isinstance(other, Tone):
33 interval = Interval(abs(NOTE_ORDER.index(self.name) - NOTE_ORDER.index(other.name)))
34 return interval
35 if isinstance(other, Interval):
36 tone_index = abs((NOTE_ORDER.index(self.name) - other.number_of_semitones)) % 12
37 return Tone(NOTE_ORDER[tone_index])
38 return 'Invalid operation'
39
40 def __rsub__(self, other):
41 raise TypeError('Invalid operation')
42
43
44class Interval:
45 result = ('unison', 'minor 2nd', 'major 2nd', 'minor 3rd',
46 'major 3rd', 'perfect 4th', 'diminished 5th',
47 'perfect 5th', 'minor 6th', 'major 6th',
48 'minor 7th', 'major 7th')
49
50 def __init__(self, number_of_semitones):
51 # If semitone is above 11 and in order to keep the cycle
52 self.number_of_semitones = number_of_semitones % 12
53
54 def __str__(self):
55 return self.result[self.number_of_semitones]
56
57 def __add__(self, other):
58 return Interval(self.number_of_semitones + other.number_of_semitones)
59
60 def __neg__(self):
61 return Interval(-self.number_of_semitones)
62
63
64class Chord:
65 def __init__(self, pos_tone, *args):
66 tone_set = set((pos_tone, *args))
67 self.pos_tone = pos_tone
68 if len(tone_set) == 1:
69 raise TypeError('Cannot have a chord made of only 1 unique tone')
70 sorted_tones = [pos_tone] + sorted(tone_set - {pos_tone}, key=lambda tone: NOTE_ORDER.index(tone.name))
71 self.tones = sorted_tones
72
73 def __str__(self):
74 return '-'.join(map(str, self.tones))
75
76 def __add__(self, other):
77 if isinstance(other, Tone):
78 if other not in self.tones:
79 new_tones = self.tones[:]
80 new_tones.append(other)
81 return Chord(*new_tones)
82 if isinstance(other, Chord):
83 new_tones = self.tones[:]
84 for tone in other.tones:
85 new_tones.append(tone)
86 return Chord(*new_tones)
87 return 'Invalid operation'
88
89 def __sub__(self, other):
90 if other not in self.tones:
91 raise TypeError(f'Cannot remove tone {other} from chord {self}')
92 if len(self.tones) < 3:
93 raise TypeError('Cannot have a chord made of only 1 unique tone')
94 if other == self.tones[0]:
95 new_tones = self.tones[1:]
96 return Chord(*new_tones)
97
98 new_tones = self.tones[:]
99 new_tones.remove(other)
100 return Chord(*new_tones)
101
102 def get_interval(self, pos_tone, other_tone):
103 tone_index = NOTE_ORDER.index(other_tone.name)
104 pos_tone_index = NOTE_ORDER.index(self.pos_tone.name)
105
106 return abs(tone_index - pos_tone_index)
107
108 def is_minor(self):
109 for tone in self.tones:
110 if tone != self.pos_tone:
111 if self.get_interval(self.pos_tone, tone) == 3:
112 return True
113
114 return False
115
116 def is_major(self):
117 for tone in self.tones:
118 if tone != self.pos_tone:
119 if self.get_interval(self.pos_tone, tone) == 4:
120 return True
121
122 return False
123
124 def is_power_chord(self):
125 return not self.is_major() and not self.is_minor()
126
127 def transposed(self, interval):
128 return Chord(*[tone + interval for tone in self.tones])
.F..FFF..F........E..FEF....F......F.
======================================================================
ERROR: test_add_interval_to_tone_left_side_error (test.TestOperations.test_add_interval_to_tone_left_side_error)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 210, in test_add_interval_to_tone_left_side_error
Interval(2) + g
~~~~~~~~~~~~^~~
File "/tmp/solution.py", line 58, in __add__
return Interval(self.number_of_semitones + other.number_of_semitones)
^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'Tone' object has no attribute 'number_of_semitones'
======================================================================
ERROR: test_add_tone_to_chord_existing_tone (test.TestOperations.test_add_tone_to_chord_existing_tone)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 267, in test_add_tone_to_chord_existing_tone
result_chord = result_chord + Tone("G#")
~~~~~~~~~~~~~^~~~~~~~~~~~
File "/tmp/solution.py", line 29, in __radd__
raise TypeError('Invalid operation')
TypeError: Invalid operation
======================================================================
FAIL: test_chord_ordering (test.TestBasicChordFunctionality.test_chord_ordering)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 65, in test_chord_ordering
self.assertEqual(str(f_sixth_ninth_chord), "F-A-C-E")
AssertionError: 'F-C-E-A' != 'F-A-C-E'
- F-C-E-A
? --
+ F-A-C-E
? ++
======================================================================
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_transposed_negative_overflow (test.TestBasicChordFunctionality.test_transposed_negative_overflow)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 152, in test_transposed_negative_overflow
self.assertEqual(str(result_chord), "G#-B-D#")
AssertionError: 'G#-D#-B' != 'G#-B-D#'
- G#-D#-B
? --
+ G#-B-D#
? ++
======================================================================
FAIL: test_add_tone_to_chord (test.TestOperations.test_add_tone_to_chord)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 254, in test_add_tone_to_chord
self.assertEqual(str(result_chord), "F-A-C-D")
AssertionError: 'F-C-D-A' != 'F-A-C-D'
- F-C-D-A
? --
+ F-A-C-D
? ++
======================================================================
FAIL: test_add_tone_to_chord_order (test.TestOperations.test_add_tone_to_chord_order)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 261, in test_add_tone_to_chord_order
self.assertEqual(str(result_chord), "F-G-A-C")
AssertionError: 'F-C-G-A' != 'F-G-A-C'
- F-C-G-A
? --
+ F-G-A-C
? ++
======================================================================
FAIL: test_subtract_interval_from_tone_oveflow (test.TestOperations.test_subtract_interval_from_tone_oveflow)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 229, in test_subtract_interval_from_tone_oveflow
self.assertEqual(str(g), "G")
AssertionError: 'F' != 'G'
- F
+ G
======================================================================
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.004s
FAILED (failures=9, errors=2)
n | 1 | note_order = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'] | n | 1 | NOTE_ORDER = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'] |
2 | 2 | ||||
3 | class Tone: | 3 | class Tone: | ||
4 | def __init__(self, name): | 4 | def __init__(self, name): | ||
n | 5 | if name in note_order: | n | 5 | if name in NOTE_ORDER: |
6 | self.name = name; | 6 | self.name = name | ||
7 | else: | ||||
8 | raise TypeError('Invalid note') | ||||
7 | 9 | ||||
8 | def __str__(self): | 10 | def __str__(self): | ||
n | 9 | return self.name; | n | 11 | return self.name |
10 | 12 | ||||
11 | # We use these two methods cause apparently the set doesn't work without them | 13 | # We use these two methods cause apparently the set doesn't work without them | ||
12 | def __eq__(self, other): | 14 | def __eq__(self, other): | ||
13 | return self.name == other.name | 15 | return self.name == other.name | ||
14 | 16 | ||||
15 | def __hash__(self): | 17 | def __hash__(self): | ||
16 | return hash(self.name) | 18 | return hash(self.name) | ||
17 | 19 | ||||
18 | def __add__(self, other): | 20 | def __add__(self, other): | ||
19 | if isinstance(other, Tone): | 21 | if isinstance(other, Tone): | ||
20 | return Chord(self, other) | 22 | return Chord(self, other) | ||
21 | if isinstance(other, Interval): | 23 | if isinstance(other, Interval): | ||
n | 22 | tone_index = (note_order.index(self.name) + other.number_of_semitones) % 12 | n | 24 | tone_index = (NOTE_ORDER.index(self.name) + other.number_of_semitones) % 12 |
23 | return Tone(note_order[tone_index]) | 25 | return Tone(NOTE_ORDER[tone_index]) | ||
24 | return 'Invalid operation' | 26 | return 'Invalid operation' | ||
25 | 27 | ||||
26 | def __radd__(self, other): | 28 | def __radd__(self, other): | ||
27 | raise TypeError('Invalid operation') | 29 | raise TypeError('Invalid operation') | ||
28 | 30 | ||||
29 | def __sub__(self, other): | 31 | def __sub__(self, other): | ||
30 | if isinstance(other, Tone): | 32 | if isinstance(other, Tone): | ||
n | 31 | interval = Interval(abs(note_order.index(self.name) - note_order.index(other.name))) | n | 33 | interval = Interval(abs(NOTE_ORDER.index(self.name) - NOTE_ORDER.index(other.name))) |
32 | return interval | 34 | return interval | ||
33 | if isinstance(other, Interval): | 35 | if isinstance(other, Interval): | ||
n | 34 | tone_index = abs((note_order.index(self.name) - other.number_of_semitones)) % 12 | n | 36 | tone_index = abs((NOTE_ORDER.index(self.name) - other.number_of_semitones)) % 12 |
35 | return Tone(note_order[tone_index]) | 37 | return Tone(NOTE_ORDER[tone_index]) | ||
36 | return 'Invalid operation' | 38 | return 'Invalid operation' | ||
37 | 39 | ||||
38 | def __rsub__(self, other): | 40 | def __rsub__(self, other): | ||
39 | raise TypeError('Invalid operation') | 41 | raise TypeError('Invalid operation') | ||
40 | 42 | ||||
41 | 43 | ||||
42 | class Interval: | 44 | class Interval: | ||
43 | result = ('unison', 'minor 2nd', 'major 2nd', 'minor 3rd', | 45 | result = ('unison', 'minor 2nd', 'major 2nd', 'minor 3rd', | ||
44 | 'major 3rd', 'perfect 4th', 'diminished 5th', | 46 | 'major 3rd', 'perfect 4th', 'diminished 5th', | ||
45 | 'perfect 5th', 'minor 6th', 'major 6th', | 47 | 'perfect 5th', 'minor 6th', 'major 6th', | ||
46 | 'minor 7th', 'major 7th') | 48 | 'minor 7th', 'major 7th') | ||
47 | 49 | ||||
48 | def __init__(self, number_of_semitones): | 50 | def __init__(self, number_of_semitones): | ||
49 | # If semitone is above 11 and in order to keep the cycle | 51 | # If semitone is above 11 and in order to keep the cycle | ||
50 | self.number_of_semitones = number_of_semitones % 12 | 52 | self.number_of_semitones = number_of_semitones % 12 | ||
51 | 53 | ||||
52 | def __str__(self): | 54 | def __str__(self): | ||
53 | return self.result[self.number_of_semitones] | 55 | return self.result[self.number_of_semitones] | ||
54 | 56 | ||||
55 | def __add__(self, other): | 57 | def __add__(self, other): | ||
56 | return Interval(self.number_of_semitones + other.number_of_semitones) | 58 | return Interval(self.number_of_semitones + other.number_of_semitones) | ||
57 | 59 | ||||
58 | def __neg__(self): | 60 | def __neg__(self): | ||
59 | return Interval(-self.number_of_semitones) | 61 | return Interval(-self.number_of_semitones) | ||
60 | 62 | ||||
61 | 63 | ||||
62 | class Chord: | 64 | class Chord: | ||
63 | def __init__(self, pos_tone, *args): | 65 | def __init__(self, pos_tone, *args): | ||
64 | tone_set = set((pos_tone, *args)) | 66 | tone_set = set((pos_tone, *args)) | ||
65 | self.pos_tone = pos_tone | 67 | self.pos_tone = pos_tone | ||
n | 66 | if(len(tone_set) == 1): | n | 68 | if len(tone_set) == 1: |
67 | raise TypeError('Cannot have a chord made of only 1 unique tone') | 69 | raise TypeError('Cannot have a chord made of only 1 unique tone') | ||
n | 68 | sorted_tones = [pos_tone] + sorted(tone_set - {pos_tone}, key=lambda tone: note_order.index(tone.name)) | n | 70 | sorted_tones = [pos_tone] + sorted(tone_set - {pos_tone}, key=lambda tone: NOTE_ORDER.index(tone.name)) |
69 | self.tones = sorted_tones | 71 | self.tones = sorted_tones | ||
70 | 72 | ||||
71 | def __str__(self): | 73 | def __str__(self): | ||
72 | return '-'.join(map(str, self.tones)) | 74 | return '-'.join(map(str, self.tones)) | ||
73 | 75 | ||||
74 | def __add__(self, other): | 76 | def __add__(self, other): | ||
75 | if isinstance(other, Tone): | 77 | if isinstance(other, Tone): | ||
76 | if other not in self.tones: | 78 | if other not in self.tones: | ||
n | 77 | new_tones = list(self.tones) | n | 79 | new_tones = self.tones[:] |
78 | new_tones.append(other) | 80 | new_tones.append(other) | ||
n | 79 | return Chord(new_tones[0], *new_tones[1:]) | n | 81 | return Chord(*new_tones) |
80 | if isinstance(other, Chord): | 82 | if isinstance(other, Chord): | ||
n | 81 | new_tones = list(self.tones) | n | 83 | new_tones = self.tones[:] |
82 | for tone in other.tones: | 84 | for tone in other.tones: | ||
n | 83 | if tone not in self.tones: | n | ||
84 | new_tones.append(tone) | 85 | new_tones.append(tone) | ||
85 | return Chord(new_tones[0], *new_tones[1:]) | 86 | return Chord(*new_tones) | ||
86 | return 'Invalid operation' | 87 | return 'Invalid operation' | ||
87 | 88 | ||||
88 | def __sub__(self, other): | 89 | def __sub__(self, other): | ||
89 | if other not in self.tones: | 90 | if other not in self.tones: | ||
90 | raise TypeError(f'Cannot remove tone {other} from chord {self}') | 91 | raise TypeError(f'Cannot remove tone {other} from chord {self}') | ||
91 | if len(self.tones) < 3: | 92 | if len(self.tones) < 3: | ||
92 | raise TypeError('Cannot have a chord made of only 1 unique tone') | 93 | raise TypeError('Cannot have a chord made of only 1 unique tone') | ||
93 | if other == self.tones[0]: | 94 | if other == self.tones[0]: | ||
94 | new_tones = self.tones[1:] | 95 | new_tones = self.tones[1:] | ||
n | 95 | return Chord(new_tones[0], *new_tones[1:]) | n | 96 | return Chord(*new_tones) |
96 | 97 | ||||
n | 97 | new_tones = list(self.tones) | n | 98 | new_tones = self.tones[:] |
98 | new_tones.remove(other) | 99 | new_tones.remove(other) | ||
n | 99 | return Chord(new_tones[0], *new_tones[1:]) | n | 100 | return Chord(*new_tones) |
100 | 101 | ||||
101 | def get_interval(self, pos_tone, other_tone): | 102 | def get_interval(self, pos_tone, other_tone): | ||
n | 102 | tone_index = note_order.index(other_tone.name) | n | 103 | tone_index = NOTE_ORDER.index(other_tone.name) |
103 | pos_tone_index = note_order.index(self.pos_tone.name) | 104 | pos_tone_index = NOTE_ORDER.index(self.pos_tone.name) | ||
104 | 105 | ||||
105 | return abs(tone_index - pos_tone_index) | 106 | return abs(tone_index - pos_tone_index) | ||
106 | 107 | ||||
107 | def is_minor(self): | 108 | def is_minor(self): | ||
108 | for tone in self.tones: | 109 | for tone in self.tones: | ||
109 | if tone != self.pos_tone: | 110 | if tone != self.pos_tone: | ||
110 | if self.get_interval(self.pos_tone, tone) == 3: | 111 | if self.get_interval(self.pos_tone, tone) == 3: | ||
111 | return True | 112 | return True | ||
112 | 113 | ||||
113 | return False | 114 | return False | ||
114 | 115 | ||||
115 | def is_major(self): | 116 | def is_major(self): | ||
116 | for tone in self.tones: | 117 | for tone in self.tones: | ||
117 | if tone != self.pos_tone: | 118 | if tone != self.pos_tone: | ||
118 | if self.get_interval(self.pos_tone, tone) == 4: | 119 | if self.get_interval(self.pos_tone, tone) == 4: | ||
119 | return True | 120 | return True | ||
120 | 121 | ||||
121 | return False | 122 | return False | ||
122 | 123 | ||||
123 | def is_power_chord(self): | 124 | def is_power_chord(self): | ||
124 | return not self.is_major() and not self.is_minor() | 125 | return not self.is_major() and not self.is_minor() | ||
125 | 126 | ||||
126 | def transposed(self, interval): | 127 | def transposed(self, interval): | ||
t | 127 | new_tones = [] | t | 128 | return Chord(*[tone + interval for tone in self.tones]) |
128 | for tone in self.tones: | ||||
129 | new_tones.append(tone + interval) | ||||
130 | return Chord(new_tones[0], *new_tones[1:]) |
Legends | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
|