1class Tone:
2 """Implement a tone."""
3
4 TONES = ('C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B')
5
6 def __key(self):
7 """Create a private key so tone is hashable."""
8 return str(self.tone)
9
10 def __init__(self, tone):
11 self.tone = tone
12
13 def __str__(self):
14 return self.tone
15
16 def __eq__(self, other):
17 return str(self) == str(other)
18
19 def __hash__(self):
20 return hash(self.__key())
21
22 def __add__(self, other):
23 """Add Tone + Tone or Tone + Interval."""
24 if isinstance(other, Tone):
25 return Chord(self, other)
26 elif isinstance(other, Interval):
27 idx = (Tone.TONES.index(self) + other.num_of_semitones) % len(Tone.TONES)
28 return Tone(Tone.TONES[idx])
29
30 def __sub__(self, other):
31 """Subtract Tone - Tone or Tone - Interval."""
32 if isinstance(other, Tone):
33 return Interval((Tone.TONES.index(self) - Tone.TONES.index(other)) % len(Tone.TONES))
34 elif isinstance(other, Interval):
35 idx = (Tone.TONES.index(self) - other.num_of_semitones) % len(Tone.TONES)
36 return Tone(Tone.TONES[idx])
37
38
39class Interval:
40 """Implement an interval."""
41
42 INTERVALS = {
43 0: 'unison',
44 1: 'minor 2nd',
45 2: 'major 2nd',
46 3: 'minor 3rd',
47 4: 'major 3rd',
48 5: 'perfect 4th',
49 6: 'diminished 5th',
50 7: 'perfect 5th',
51 8: 'minor 6th',
52 9: 'major 6th',
53 10: 'minor 7th',
54 11: 'major 7th'
55 }
56
57 def __init__(self, num_of_semitones):
58 self.num_of_semitones = num_of_semitones % 12
59
60 def __str__(self):
61 return self.INTERVALS[self.num_of_semitones]
62
63 def __add__(self, other):
64 """Add Interval + Interval."""
65 if isinstance(other, Tone):
66 raise TypeError("Invalid operation")
67 elif isinstance(other, Interval):
68 return Interval(self.num_of_semitones + other.num_of_semitones)
69
70 def __sub__(self, other):
71 """Invalidate subtraction Interval - Tone."""
72 if isinstance(other, Tone):
73 raise TypeError("Invalid operation")
74
75 def __neg__(self):
76 """Support the creation of such intervals: -Interval(5)."""
77 return Interval(len(Tone.TONES) - self.num_of_semitones)
78
79 @classmethod
80 def init_from_tones(cls, tone1, tone2):
81 """Initialize interval from 2 tones."""
82 return cls(abs(Tone.TONES.index(tone1) - Tone.TONES.index(tone2)))
83
84
85class Chord:
86 """Implement a chord."""
87
88 INDEX_MINOR = 3 # index in interval.INTERVALS for minor 3rd
89 INDEX_MAJOR = 4 # index in interval.INTERVALS for major 3rd
90
91 def __init__(self, main_tone, *args):
92 self.main_tone = main_tone
93 self.tones = list(args)
94 self.tones.append(self.main_tone)
95 self._sort_tones()
96 if len(self.tones) < 2:
97 raise TypeError('Cannot have a chord made of only 1 unique tone')
98
99 def __str__(self):
100 return f'{'-'.join(map(str, self.tones))}'
101
102 def __add__(self, other):
103 """Add Chord + Tone or Chord + Chord."""
104 if isinstance(other, Tone):
105 return Chord(self.main_tone, *(self.tones + [other]))
106 if isinstance(other, Chord):
107 return Chord(self.main_tone, *(self.tones + other.tones))
108
109 def __sub__(self, other):
110 """Subtract Chord - Tone."""
111 if isinstance(other, Tone):
112 if other in self.tones:
113 new_tones = self.tones[:]
114 new_tones.remove(other)
115 new_main_tone = new_tones[0]
116 return Chord(new_main_tone, *new_tones)
117 else:
118 raise TypeError(f'Cannot remove tone {other} from chord {self}')
119
120 def _sort_tones(self):
121 """Sort all tones in chord relative to the main tone."""
122 main_tone_idx = Tone.TONES.index(self.main_tone)
123 tones_unique = set(self.tones) # because of the hash func the tones are saved as strings
124 all_tones_sorted = Tone.TONES[main_tone_idx:] + Tone.TONES[:main_tone_idx]
125 self.tones = list(map(Tone, filter(lambda tone: tone in tones_unique, all_tones_sorted)))
126
127 def _check_for_special_tone(self, idx):
128 """Check if there is any tone that makes a special tone with root based on idx given."""
129 return any(str(Interval.init_from_tones(self.main_tone, tone)) == Interval.INTERVALS[idx] for tone in self.tones)
130
131 def is_minor(self):
132 """Check if chord is minor."""
133 return self._check_for_special_tone(self.INDEX_MINOR)
134
135 def is_major(self):
136 """Check if chord is major."""
137 return self._check_for_special_tone(self.INDEX_MAJOR)
138
139 def is_power_chord(self):
140 """Check if tone is power chord."""
141 return not self.is_minor() and not self.is_major()
142
143 def transposed(self, interval):
144 """Transpose by interval."""
145 return Chord(self.main_tone + interval, *list(map(lambda tone: tone + interval, self.tones)))
....FFF..............................
======================================================================
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
----------------------------------------------------------------------
Ran 37 tests in 0.002s
FAILED (failures=3)
f | 1 | class Tone: | f | 1 | class Tone: |
2 | """Implement a tone.""" | 2 | """Implement a tone.""" | ||
3 | 3 | ||||
4 | TONES = ('C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B') | 4 | TONES = ('C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B') | ||
5 | 5 | ||||
6 | def __key(self): | 6 | def __key(self): | ||
7 | """Create a private key so tone is hashable.""" | 7 | """Create a private key so tone is hashable.""" | ||
8 | return str(self.tone) | 8 | return str(self.tone) | ||
9 | 9 | ||||
10 | def __init__(self, tone): | 10 | def __init__(self, tone): | ||
11 | self.tone = tone | 11 | self.tone = tone | ||
12 | 12 | ||||
13 | def __str__(self): | 13 | def __str__(self): | ||
14 | return self.tone | 14 | return self.tone | ||
15 | 15 | ||||
16 | def __eq__(self, other): | 16 | def __eq__(self, other): | ||
17 | return str(self) == str(other) | 17 | return str(self) == str(other) | ||
18 | 18 | ||||
19 | def __hash__(self): | 19 | def __hash__(self): | ||
20 | return hash(self.__key()) | 20 | return hash(self.__key()) | ||
21 | 21 | ||||
22 | def __add__(self, other): | 22 | def __add__(self, other): | ||
23 | """Add Tone + Tone or Tone + Interval.""" | 23 | """Add Tone + Tone or Tone + Interval.""" | ||
24 | if isinstance(other, Tone): | 24 | if isinstance(other, Tone): | ||
25 | return Chord(self, other) | 25 | return Chord(self, other) | ||
26 | elif isinstance(other, Interval): | 26 | elif isinstance(other, Interval): | ||
27 | idx = (Tone.TONES.index(self) + other.num_of_semitones) % len(Tone.TONES) | 27 | idx = (Tone.TONES.index(self) + other.num_of_semitones) % len(Tone.TONES) | ||
28 | return Tone(Tone.TONES[idx]) | 28 | return Tone(Tone.TONES[idx]) | ||
29 | 29 | ||||
30 | def __sub__(self, other): | 30 | def __sub__(self, other): | ||
31 | """Subtract Tone - Tone or Tone - Interval.""" | 31 | """Subtract Tone - Tone or Tone - Interval.""" | ||
32 | if isinstance(other, Tone): | 32 | if isinstance(other, Tone): | ||
33 | return Interval((Tone.TONES.index(self) - Tone.TONES.index(other)) % len(Tone.TONES)) | 33 | return Interval((Tone.TONES.index(self) - Tone.TONES.index(other)) % len(Tone.TONES)) | ||
34 | elif isinstance(other, Interval): | 34 | elif isinstance(other, Interval): | ||
35 | idx = (Tone.TONES.index(self) - other.num_of_semitones) % len(Tone.TONES) | 35 | idx = (Tone.TONES.index(self) - other.num_of_semitones) % len(Tone.TONES) | ||
36 | return Tone(Tone.TONES[idx]) | 36 | return Tone(Tone.TONES[idx]) | ||
37 | 37 | ||||
38 | 38 | ||||
39 | class Interval: | 39 | class Interval: | ||
40 | """Implement an interval.""" | 40 | """Implement an interval.""" | ||
41 | 41 | ||||
42 | INTERVALS = { | 42 | INTERVALS = { | ||
43 | 0: 'unison', | 43 | 0: 'unison', | ||
44 | 1: 'minor 2nd', | 44 | 1: 'minor 2nd', | ||
45 | 2: 'major 2nd', | 45 | 2: 'major 2nd', | ||
46 | 3: 'minor 3rd', | 46 | 3: 'minor 3rd', | ||
47 | 4: 'major 3rd', | 47 | 4: 'major 3rd', | ||
48 | 5: 'perfect 4th', | 48 | 5: 'perfect 4th', | ||
49 | 6: 'diminished 5th', | 49 | 6: 'diminished 5th', | ||
50 | 7: 'perfect 5th', | 50 | 7: 'perfect 5th', | ||
51 | 8: 'minor 6th', | 51 | 8: 'minor 6th', | ||
52 | 9: 'major 6th', | 52 | 9: 'major 6th', | ||
53 | 10: 'minor 7th', | 53 | 10: 'minor 7th', | ||
54 | 11: 'major 7th' | 54 | 11: 'major 7th' | ||
55 | } | 55 | } | ||
56 | 56 | ||||
57 | def __init__(self, num_of_semitones): | 57 | def __init__(self, num_of_semitones): | ||
58 | self.num_of_semitones = num_of_semitones % 12 | 58 | self.num_of_semitones = num_of_semitones % 12 | ||
59 | 59 | ||||
60 | def __str__(self): | 60 | def __str__(self): | ||
61 | return self.INTERVALS[self.num_of_semitones] | 61 | return self.INTERVALS[self.num_of_semitones] | ||
62 | 62 | ||||
63 | def __add__(self, other): | 63 | def __add__(self, other): | ||
64 | """Add Interval + Interval.""" | 64 | """Add Interval + Interval.""" | ||
65 | if isinstance(other, Tone): | 65 | if isinstance(other, Tone): | ||
66 | raise TypeError("Invalid operation") | 66 | raise TypeError("Invalid operation") | ||
67 | elif isinstance(other, Interval): | 67 | elif isinstance(other, Interval): | ||
68 | return Interval(self.num_of_semitones + other.num_of_semitones) | 68 | return Interval(self.num_of_semitones + other.num_of_semitones) | ||
69 | 69 | ||||
70 | def __sub__(self, other): | 70 | def __sub__(self, other): | ||
71 | """Invalidate subtraction Interval - Tone.""" | 71 | """Invalidate subtraction Interval - Tone.""" | ||
72 | if isinstance(other, Tone): | 72 | if isinstance(other, Tone): | ||
73 | raise TypeError("Invalid operation") | 73 | raise TypeError("Invalid operation") | ||
74 | 74 | ||||
75 | def __neg__(self): | 75 | def __neg__(self): | ||
76 | """Support the creation of such intervals: -Interval(5).""" | 76 | """Support the creation of such intervals: -Interval(5).""" | ||
77 | return Interval(len(Tone.TONES) - self.num_of_semitones) | 77 | return Interval(len(Tone.TONES) - self.num_of_semitones) | ||
78 | 78 | ||||
79 | @classmethod | 79 | @classmethod | ||
80 | def init_from_tones(cls, tone1, tone2): | 80 | def init_from_tones(cls, tone1, tone2): | ||
81 | """Initialize interval from 2 tones.""" | 81 | """Initialize interval from 2 tones.""" | ||
82 | return cls(abs(Tone.TONES.index(tone1) - Tone.TONES.index(tone2))) | 82 | return cls(abs(Tone.TONES.index(tone1) - Tone.TONES.index(tone2))) | ||
83 | 83 | ||||
84 | 84 | ||||
85 | class Chord: | 85 | class Chord: | ||
86 | """Implement a chord.""" | 86 | """Implement a chord.""" | ||
87 | 87 | ||||
88 | INDEX_MINOR = 3 # index in interval.INTERVALS for minor 3rd | 88 | INDEX_MINOR = 3 # index in interval.INTERVALS for minor 3rd | ||
89 | INDEX_MAJOR = 4 # index in interval.INTERVALS for major 3rd | 89 | INDEX_MAJOR = 4 # index in interval.INTERVALS for major 3rd | ||
90 | 90 | ||||
91 | def __init__(self, main_tone, *args): | 91 | def __init__(self, main_tone, *args): | ||
92 | self.main_tone = main_tone | 92 | self.main_tone = main_tone | ||
93 | self.tones = list(args) | 93 | self.tones = list(args) | ||
94 | self.tones.append(self.main_tone) | 94 | self.tones.append(self.main_tone) | ||
95 | self._sort_tones() | 95 | self._sort_tones() | ||
96 | if len(self.tones) < 2: | 96 | if len(self.tones) < 2: | ||
97 | raise TypeError('Cannot have a chord made of only 1 unique tone') | 97 | raise TypeError('Cannot have a chord made of only 1 unique tone') | ||
98 | 98 | ||||
99 | def __str__(self): | 99 | def __str__(self): | ||
100 | return f'{'-'.join(map(str, self.tones))}' | 100 | return f'{'-'.join(map(str, self.tones))}' | ||
101 | 101 | ||||
102 | def __add__(self, other): | 102 | def __add__(self, other): | ||
103 | """Add Chord + Tone or Chord + Chord.""" | 103 | """Add Chord + Tone or Chord + Chord.""" | ||
104 | if isinstance(other, Tone): | 104 | if isinstance(other, Tone): | ||
105 | return Chord(self.main_tone, *(self.tones + [other])) | 105 | return Chord(self.main_tone, *(self.tones + [other])) | ||
106 | if isinstance(other, Chord): | 106 | if isinstance(other, Chord): | ||
107 | return Chord(self.main_tone, *(self.tones + other.tones)) | 107 | return Chord(self.main_tone, *(self.tones + other.tones)) | ||
108 | 108 | ||||
109 | def __sub__(self, other): | 109 | def __sub__(self, other): | ||
110 | """Subtract Chord - Tone.""" | 110 | """Subtract Chord - Tone.""" | ||
111 | if isinstance(other, Tone): | 111 | if isinstance(other, Tone): | ||
112 | if other in self.tones: | 112 | if other in self.tones: | ||
113 | new_tones = self.tones[:] | 113 | new_tones = self.tones[:] | ||
114 | new_tones.remove(other) | 114 | new_tones.remove(other) | ||
115 | new_main_tone = new_tones[0] | 115 | new_main_tone = new_tones[0] | ||
116 | return Chord(new_main_tone, *new_tones) | 116 | return Chord(new_main_tone, *new_tones) | ||
117 | else: | 117 | else: | ||
118 | raise TypeError(f'Cannot remove tone {other} from chord {self}') | 118 | raise TypeError(f'Cannot remove tone {other} from chord {self}') | ||
119 | 119 | ||||
120 | def _sort_tones(self): | 120 | def _sort_tones(self): | ||
121 | """Sort all tones in chord relative to the main tone.""" | 121 | """Sort all tones in chord relative to the main tone.""" | ||
122 | main_tone_idx = Tone.TONES.index(self.main_tone) | 122 | main_tone_idx = Tone.TONES.index(self.main_tone) | ||
123 | tones_unique = set(self.tones) # because of the hash func the tones are saved as strings | 123 | tones_unique = set(self.tones) # because of the hash func the tones are saved as strings | ||
124 | all_tones_sorted = Tone.TONES[main_tone_idx:] + Tone.TONES[:main_tone_idx] | 124 | all_tones_sorted = Tone.TONES[main_tone_idx:] + Tone.TONES[:main_tone_idx] | ||
125 | self.tones = list(map(Tone, filter(lambda tone: tone in tones_unique, all_tones_sorted))) | 125 | self.tones = list(map(Tone, filter(lambda tone: tone in tones_unique, all_tones_sorted))) | ||
126 | 126 | ||||
127 | def _check_for_special_tone(self, idx): | 127 | def _check_for_special_tone(self, idx): | ||
128 | """Check if there is any tone that makes a special tone with root based on idx given.""" | 128 | """Check if there is any tone that makes a special tone with root based on idx given.""" | ||
129 | return any(str(Interval.init_from_tones(self.main_tone, tone)) == Interval.INTERVALS[idx] for tone in self.tones) | 129 | return any(str(Interval.init_from_tones(self.main_tone, tone)) == Interval.INTERVALS[idx] for tone in self.tones) | ||
130 | 130 | ||||
131 | def is_minor(self): | 131 | def is_minor(self): | ||
132 | """Check if chord is minor.""" | 132 | """Check if chord is minor.""" | ||
133 | return self._check_for_special_tone(self.INDEX_MINOR) | 133 | return self._check_for_special_tone(self.INDEX_MINOR) | ||
134 | 134 | ||||
135 | def is_major(self): | 135 | def is_major(self): | ||
136 | """Check if chord is major.""" | 136 | """Check if chord is major.""" | ||
137 | return self._check_for_special_tone(self.INDEX_MAJOR) | 137 | return self._check_for_special_tone(self.INDEX_MAJOR) | ||
138 | 138 | ||||
139 | def is_power_chord(self): | 139 | def is_power_chord(self): | ||
140 | """Check if tone is power chord.""" | 140 | """Check if tone is power chord.""" | ||
141 | return not self.is_minor() and not self.is_major() | 141 | return not self.is_minor() and not self.is_major() | ||
142 | 142 | ||||
143 | def transposed(self, interval): | 143 | def transposed(self, interval): | ||
144 | """Transpose by interval.""" | 144 | """Transpose by interval.""" | ||
145 | return Chord(self.main_tone + interval, *list(map(lambda tone: tone + interval, self.tones))) | 145 | return Chord(self.main_tone + interval, *list(map(lambda tone: tone + interval, self.tones))) | ||
t | 146 | t | |||
147 | |||||
148 | c_power_chord = Chord(Tone("C"), Tone("G")) | ||||
149 | result_chord = c_power_chord - Tone("E") | ||||
150 | # C-G |
Legends | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
|
f | 1 | class Tone: | f | 1 | class Tone: |
2 | """Implement a tone.""" | 2 | """Implement a tone.""" | ||
3 | 3 | ||||
4 | TONES = ('C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B') | 4 | TONES = ('C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B') | ||
5 | 5 | ||||
6 | def __key(self): | 6 | def __key(self): | ||
7 | """Create a private key so tone is hashable.""" | 7 | """Create a private key so tone is hashable.""" | ||
8 | return str(self.tone) | 8 | return str(self.tone) | ||
9 | 9 | ||||
10 | def __init__(self, tone): | 10 | def __init__(self, tone): | ||
11 | self.tone = tone | 11 | self.tone = tone | ||
12 | 12 | ||||
13 | def __str__(self): | 13 | def __str__(self): | ||
14 | return self.tone | 14 | return self.tone | ||
15 | 15 | ||||
16 | def __eq__(self, other): | 16 | def __eq__(self, other): | ||
17 | return str(self) == str(other) | 17 | return str(self) == str(other) | ||
18 | 18 | ||||
19 | def __hash__(self): | 19 | def __hash__(self): | ||
20 | return hash(self.__key()) | 20 | return hash(self.__key()) | ||
21 | 21 | ||||
22 | def __add__(self, other): | 22 | def __add__(self, other): | ||
23 | """Add Tone + Tone or Tone + Interval.""" | 23 | """Add Tone + Tone or Tone + Interval.""" | ||
24 | if isinstance(other, Tone): | 24 | if isinstance(other, Tone): | ||
25 | return Chord(self, other) | 25 | return Chord(self, other) | ||
26 | elif isinstance(other, Interval): | 26 | elif isinstance(other, Interval): | ||
27 | idx = (Tone.TONES.index(self) + other.num_of_semitones) % len(Tone.TONES) | 27 | idx = (Tone.TONES.index(self) + other.num_of_semitones) % len(Tone.TONES) | ||
28 | return Tone(Tone.TONES[idx]) | 28 | return Tone(Tone.TONES[idx]) | ||
29 | 29 | ||||
30 | def __sub__(self, other): | 30 | def __sub__(self, other): | ||
31 | """Subtract Tone - Tone or Tone - Interval.""" | 31 | """Subtract Tone - Tone or Tone - Interval.""" | ||
32 | if isinstance(other, Tone): | 32 | if isinstance(other, Tone): | ||
33 | return Interval((Tone.TONES.index(self) - Tone.TONES.index(other)) % len(Tone.TONES)) | 33 | return Interval((Tone.TONES.index(self) - Tone.TONES.index(other)) % len(Tone.TONES)) | ||
34 | elif isinstance(other, Interval): | 34 | elif isinstance(other, Interval): | ||
35 | idx = (Tone.TONES.index(self) - other.num_of_semitones) % len(Tone.TONES) | 35 | idx = (Tone.TONES.index(self) - other.num_of_semitones) % len(Tone.TONES) | ||
36 | return Tone(Tone.TONES[idx]) | 36 | return Tone(Tone.TONES[idx]) | ||
37 | 37 | ||||
38 | 38 | ||||
39 | class Interval: | 39 | class Interval: | ||
40 | """Implement an interval.""" | 40 | """Implement an interval.""" | ||
41 | 41 | ||||
42 | INTERVALS = { | 42 | INTERVALS = { | ||
43 | 0: 'unison', | 43 | 0: 'unison', | ||
44 | 1: 'minor 2nd', | 44 | 1: 'minor 2nd', | ||
45 | 2: 'major 2nd', | 45 | 2: 'major 2nd', | ||
46 | 3: 'minor 3rd', | 46 | 3: 'minor 3rd', | ||
47 | 4: 'major 3rd', | 47 | 4: 'major 3rd', | ||
48 | 5: 'perfect 4th', | 48 | 5: 'perfect 4th', | ||
49 | 6: 'diminished 5th', | 49 | 6: 'diminished 5th', | ||
50 | 7: 'perfect 5th', | 50 | 7: 'perfect 5th', | ||
51 | 8: 'minor 6th', | 51 | 8: 'minor 6th', | ||
52 | 9: 'major 6th', | 52 | 9: 'major 6th', | ||
53 | 10: 'minor 7th', | 53 | 10: 'minor 7th', | ||
54 | 11: 'major 7th' | 54 | 11: 'major 7th' | ||
55 | } | 55 | } | ||
56 | 56 | ||||
57 | def __init__(self, num_of_semitones): | 57 | def __init__(self, num_of_semitones): | ||
58 | self.num_of_semitones = num_of_semitones % 12 | 58 | self.num_of_semitones = num_of_semitones % 12 | ||
59 | 59 | ||||
60 | def __str__(self): | 60 | def __str__(self): | ||
61 | return self.INTERVALS[self.num_of_semitones] | 61 | return self.INTERVALS[self.num_of_semitones] | ||
62 | 62 | ||||
63 | def __add__(self, other): | 63 | def __add__(self, other): | ||
64 | """Add Interval + Interval.""" | 64 | """Add Interval + Interval.""" | ||
65 | if isinstance(other, Tone): | 65 | if isinstance(other, Tone): | ||
66 | raise TypeError("Invalid operation") | 66 | raise TypeError("Invalid operation") | ||
67 | elif isinstance(other, Interval): | 67 | elif isinstance(other, Interval): | ||
68 | return Interval(self.num_of_semitones + other.num_of_semitones) | 68 | return Interval(self.num_of_semitones + other.num_of_semitones) | ||
69 | 69 | ||||
70 | def __sub__(self, other): | 70 | def __sub__(self, other): | ||
71 | """Invalidate subtraction Interval - Tone.""" | 71 | """Invalidate subtraction Interval - Tone.""" | ||
72 | if isinstance(other, Tone): | 72 | if isinstance(other, Tone): | ||
73 | raise TypeError("Invalid operation") | 73 | raise TypeError("Invalid operation") | ||
74 | 74 | ||||
75 | def __neg__(self): | 75 | def __neg__(self): | ||
76 | """Support the creation of such intervals: -Interval(5).""" | 76 | """Support the creation of such intervals: -Interval(5).""" | ||
77 | return Interval(len(Tone.TONES) - self.num_of_semitones) | 77 | return Interval(len(Tone.TONES) - self.num_of_semitones) | ||
78 | 78 | ||||
79 | @classmethod | 79 | @classmethod | ||
80 | def init_from_tones(cls, tone1, tone2): | 80 | def init_from_tones(cls, tone1, tone2): | ||
81 | """Initialize interval from 2 tones.""" | 81 | """Initialize interval from 2 tones.""" | ||
82 | return cls(abs(Tone.TONES.index(tone1) - Tone.TONES.index(tone2))) | 82 | return cls(abs(Tone.TONES.index(tone1) - Tone.TONES.index(tone2))) | ||
83 | 83 | ||||
84 | 84 | ||||
85 | class Chord: | 85 | class Chord: | ||
86 | """Implement a chord.""" | 86 | """Implement a chord.""" | ||
87 | 87 | ||||
88 | INDEX_MINOR = 3 # index in interval.INTERVALS for minor 3rd | 88 | INDEX_MINOR = 3 # index in interval.INTERVALS for minor 3rd | ||
89 | INDEX_MAJOR = 4 # index in interval.INTERVALS for major 3rd | 89 | INDEX_MAJOR = 4 # index in interval.INTERVALS for major 3rd | ||
90 | 90 | ||||
91 | def __init__(self, main_tone, *args): | 91 | def __init__(self, main_tone, *args): | ||
92 | self.main_tone = main_tone | 92 | self.main_tone = main_tone | ||
93 | self.tones = list(args) | 93 | self.tones = list(args) | ||
94 | self.tones.append(self.main_tone) | 94 | self.tones.append(self.main_tone) | ||
95 | self._sort_tones() | 95 | self._sort_tones() | ||
96 | if len(self.tones) < 2: | 96 | if len(self.tones) < 2: | ||
97 | raise TypeError('Cannot have a chord made of only 1 unique tone') | 97 | raise TypeError('Cannot have a chord made of only 1 unique tone') | ||
98 | 98 | ||||
99 | def __str__(self): | 99 | def __str__(self): | ||
100 | return f'{'-'.join(map(str, self.tones))}' | 100 | return f'{'-'.join(map(str, self.tones))}' | ||
101 | 101 | ||||
102 | def __add__(self, other): | 102 | def __add__(self, other): | ||
103 | """Add Chord + Tone or Chord + Chord.""" | 103 | """Add Chord + Tone or Chord + Chord.""" | ||
104 | if isinstance(other, Tone): | 104 | if isinstance(other, Tone): | ||
105 | return Chord(self.main_tone, *(self.tones + [other])) | 105 | return Chord(self.main_tone, *(self.tones + [other])) | ||
106 | if isinstance(other, Chord): | 106 | if isinstance(other, Chord): | ||
107 | return Chord(self.main_tone, *(self.tones + other.tones)) | 107 | return Chord(self.main_tone, *(self.tones + other.tones)) | ||
108 | 108 | ||||
109 | def __sub__(self, other): | 109 | def __sub__(self, other): | ||
110 | """Subtract Chord - Tone.""" | 110 | """Subtract Chord - Tone.""" | ||
111 | if isinstance(other, Tone): | 111 | if isinstance(other, Tone): | ||
112 | if other in self.tones: | 112 | if other in self.tones: | ||
n | n | 113 | new_tones = self.tones[:] | ||
113 | self.tones.remove(other) | 114 | new_tones.remove(other) | ||
114 | if len(self.tones) < 2: | ||||
115 | raise TypeError('Cannot have a chord made of only 1 unique tone') | ||||
116 | if other == self.main_tone: | ||||
117 | self.main_tone = self.tones[0] | 115 | new_main_tone = new_tones[0] | ||
118 | return self | 116 | return Chord(new_main_tone, *new_tones) | ||
119 | else: | 117 | else: | ||
120 | raise TypeError(f'Cannot remove tone {other} from chord {self}') | 118 | raise TypeError(f'Cannot remove tone {other} from chord {self}') | ||
121 | 119 | ||||
122 | def _sort_tones(self): | 120 | def _sort_tones(self): | ||
123 | """Sort all tones in chord relative to the main tone.""" | 121 | """Sort all tones in chord relative to the main tone.""" | ||
124 | main_tone_idx = Tone.TONES.index(self.main_tone) | 122 | main_tone_idx = Tone.TONES.index(self.main_tone) | ||
125 | tones_unique = set(self.tones) # because of the hash func the tones are saved as strings | 123 | tones_unique = set(self.tones) # because of the hash func the tones are saved as strings | ||
126 | all_tones_sorted = Tone.TONES[main_tone_idx:] + Tone.TONES[:main_tone_idx] | 124 | all_tones_sorted = Tone.TONES[main_tone_idx:] + Tone.TONES[:main_tone_idx] | ||
127 | self.tones = list(map(Tone, filter(lambda tone: tone in tones_unique, all_tones_sorted))) | 125 | self.tones = list(map(Tone, filter(lambda tone: tone in tones_unique, all_tones_sorted))) | ||
128 | 126 | ||||
129 | def _check_for_special_tone(self, idx): | 127 | def _check_for_special_tone(self, idx): | ||
130 | """Check if there is any tone that makes a special tone with root based on idx given.""" | 128 | """Check if there is any tone that makes a special tone with root based on idx given.""" | ||
131 | return any(str(Interval.init_from_tones(self.main_tone, tone)) == Interval.INTERVALS[idx] for tone in self.tones) | 129 | return any(str(Interval.init_from_tones(self.main_tone, tone)) == Interval.INTERVALS[idx] for tone in self.tones) | ||
132 | 130 | ||||
133 | def is_minor(self): | 131 | def is_minor(self): | ||
134 | """Check if chord is minor.""" | 132 | """Check if chord is minor.""" | ||
135 | return self._check_for_special_tone(self.INDEX_MINOR) | 133 | return self._check_for_special_tone(self.INDEX_MINOR) | ||
136 | 134 | ||||
137 | def is_major(self): | 135 | def is_major(self): | ||
138 | """Check if chord is major.""" | 136 | """Check if chord is major.""" | ||
139 | return self._check_for_special_tone(self.INDEX_MAJOR) | 137 | return self._check_for_special_tone(self.INDEX_MAJOR) | ||
140 | 138 | ||||
141 | def is_power_chord(self): | 139 | def is_power_chord(self): | ||
142 | """Check if tone is power chord.""" | 140 | """Check if tone is power chord.""" | ||
143 | return not self.is_minor() and not self.is_major() | 141 | return not self.is_minor() and not self.is_major() | ||
144 | 142 | ||||
145 | def transposed(self, interval): | 143 | def transposed(self, interval): | ||
146 | """Transpose by interval.""" | 144 | """Transpose by interval.""" | ||
147 | return Chord(self.main_tone + interval, *list(map(lambda tone: tone + interval, self.tones))) | 145 | return Chord(self.main_tone + interval, *list(map(lambda tone: tone + interval, self.tones))) | ||
t | t | 146 | |||
147 | |||||
148 | c_power_chord = Chord(Tone("C"), Tone("G")) | ||||
149 | result_chord = c_power_chord - Tone("E") | ||||
150 | # C-G |
Legends | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
|
f | 1 | class Tone: | f | 1 | class Tone: |
n | 2 | """Implements a tone""" | n | 2 | """Implement a tone.""" |
3 | 3 | ||||
4 | TONES = ('C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B') | 4 | TONES = ('C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B') | ||
5 | 5 | ||||
6 | def __key(self): | 6 | def __key(self): | ||
n | 7 | """Create private key so tone is hashable""" | n | 7 | """Create a private key so tone is hashable.""" |
8 | return str(self.tone) | 8 | return str(self.tone) | ||
9 | 9 | ||||
10 | def __init__(self, tone): | 10 | def __init__(self, tone): | ||
11 | self.tone = tone | 11 | self.tone = tone | ||
12 | 12 | ||||
13 | def __str__(self): | 13 | def __str__(self): | ||
14 | return self.tone | 14 | return self.tone | ||
15 | 15 | ||||
16 | def __eq__(self, other): | 16 | def __eq__(self, other): | ||
17 | return str(self) == str(other) | 17 | return str(self) == str(other) | ||
18 | 18 | ||||
19 | def __hash__(self): | 19 | def __hash__(self): | ||
20 | return hash(self.__key()) | 20 | return hash(self.__key()) | ||
21 | 21 | ||||
22 | def __add__(self, other): | 22 | def __add__(self, other): | ||
n | 23 | """Addition supports Tone + Tone and Tone + Interval""" | n | 23 | """Add Tone + Tone or Tone + Interval.""" |
24 | if isinstance(other, Tone): | 24 | if isinstance(other, Tone): | ||
25 | return Chord(self, other) | 25 | return Chord(self, other) | ||
26 | elif isinstance(other, Interval): | 26 | elif isinstance(other, Interval): | ||
27 | idx = (Tone.TONES.index(self) + other.num_of_semitones) % len(Tone.TONES) | 27 | idx = (Tone.TONES.index(self) + other.num_of_semitones) % len(Tone.TONES) | ||
28 | return Tone(Tone.TONES[idx]) | 28 | return Tone(Tone.TONES[idx]) | ||
29 | 29 | ||||
30 | def __sub__(self, other): | 30 | def __sub__(self, other): | ||
n | 31 | """Subtraction supports Tone - Tone and Tone - Interval""" | n | 31 | """Subtract Tone - Tone or Tone - Interval.""" |
32 | if isinstance(other, Tone): | 32 | if isinstance(other, Tone): | ||
33 | return Interval((Tone.TONES.index(self) - Tone.TONES.index(other)) % len(Tone.TONES)) | 33 | return Interval((Tone.TONES.index(self) - Tone.TONES.index(other)) % len(Tone.TONES)) | ||
34 | elif isinstance(other, Interval): | 34 | elif isinstance(other, Interval): | ||
35 | idx = (Tone.TONES.index(self) - other.num_of_semitones) % len(Tone.TONES) | 35 | idx = (Tone.TONES.index(self) - other.num_of_semitones) % len(Tone.TONES) | ||
36 | return Tone(Tone.TONES[idx]) | 36 | return Tone(Tone.TONES[idx]) | ||
37 | 37 | ||||
38 | 38 | ||||
39 | class Interval: | 39 | class Interval: | ||
n | 40 | """Implements an interval""" | n | 40 | """Implement an interval.""" |
41 | 41 | ||||
42 | INTERVALS = { | 42 | INTERVALS = { | ||
43 | 0: 'unison', | 43 | 0: 'unison', | ||
44 | 1: 'minor 2nd', | 44 | 1: 'minor 2nd', | ||
45 | 2: 'major 2nd', | 45 | 2: 'major 2nd', | ||
46 | 3: 'minor 3rd', | 46 | 3: 'minor 3rd', | ||
47 | 4: 'major 3rd', | 47 | 4: 'major 3rd', | ||
48 | 5: 'perfect 4th', | 48 | 5: 'perfect 4th', | ||
49 | 6: 'diminished 5th', | 49 | 6: 'diminished 5th', | ||
50 | 7: 'perfect 5th', | 50 | 7: 'perfect 5th', | ||
51 | 8: 'minor 6th', | 51 | 8: 'minor 6th', | ||
52 | 9: 'major 6th', | 52 | 9: 'major 6th', | ||
53 | 10: 'minor 7th', | 53 | 10: 'minor 7th', | ||
54 | 11: 'major 7th' | 54 | 11: 'major 7th' | ||
55 | } | 55 | } | ||
56 | 56 | ||||
57 | def __init__(self, num_of_semitones): | 57 | def __init__(self, num_of_semitones): | ||
58 | self.num_of_semitones = num_of_semitones % 12 | 58 | self.num_of_semitones = num_of_semitones % 12 | ||
59 | 59 | ||||
60 | def __str__(self): | 60 | def __str__(self): | ||
61 | return self.INTERVALS[self.num_of_semitones] | 61 | return self.INTERVALS[self.num_of_semitones] | ||
62 | 62 | ||||
63 | def __add__(self, other): | 63 | def __add__(self, other): | ||
n | 64 | """Addition supports Interval + Interval""" | n | 64 | """Add Interval + Interval.""" |
65 | if isinstance(other, Tone): | 65 | if isinstance(other, Tone): | ||
66 | raise TypeError("Invalid operation") | 66 | raise TypeError("Invalid operation") | ||
67 | elif isinstance(other, Interval): | 67 | elif isinstance(other, Interval): | ||
68 | return Interval(self.num_of_semitones + other.num_of_semitones) | 68 | return Interval(self.num_of_semitones + other.num_of_semitones) | ||
69 | 69 | ||||
70 | def __sub__(self, other): | 70 | def __sub__(self, other): | ||
n | 71 | """Interval - Tone is invalid operation""" | n | 71 | """Invalidate subtraction Interval - Tone.""" |
72 | if isinstance(other, Tone): | 72 | if isinstance(other, Tone): | ||
73 | raise TypeError("Invalid operation") | 73 | raise TypeError("Invalid operation") | ||
74 | 74 | ||||
75 | def __neg__(self): | 75 | def __neg__(self): | ||
n | 76 | """Supports such intervals: -Interval(5)""" | n | 76 | """Support the creation of such intervals: -Interval(5).""" |
77 | return Interval(len(Tone.TONES) - self.num_of_semitones) | 77 | return Interval(len(Tone.TONES) - self.num_of_semitones) | ||
78 | 78 | ||||
79 | @classmethod | 79 | @classmethod | ||
80 | def init_from_tones(cls, tone1, tone2): | 80 | def init_from_tones(cls, tone1, tone2): | ||
n | 81 | """Initialize interval from 2 tones""" | n | 81 | """Initialize interval from 2 tones.""" |
82 | return cls(abs(Tone.TONES.index(tone1) - Tone.TONES.index(tone2))) | 82 | return cls(abs(Tone.TONES.index(tone1) - Tone.TONES.index(tone2))) | ||
83 | 83 | ||||
84 | 84 | ||||
85 | class Chord: | 85 | class Chord: | ||
n | 86 | """Implements a chord""" | n | 86 | """Implement a chord.""" |
87 | 87 | ||||
88 | INDEX_MINOR = 3 # index in interval.INTERVALS for minor 3rd | 88 | INDEX_MINOR = 3 # index in interval.INTERVALS for minor 3rd | ||
89 | INDEX_MAJOR = 4 # index in interval.INTERVALS for major 3rd | 89 | INDEX_MAJOR = 4 # index in interval.INTERVALS for major 3rd | ||
90 | 90 | ||||
91 | def __init__(self, main_tone, *args): | 91 | def __init__(self, main_tone, *args): | ||
92 | self.main_tone = main_tone | 92 | self.main_tone = main_tone | ||
93 | self.tones = list(args) | 93 | self.tones = list(args) | ||
94 | self.tones.append(self.main_tone) | 94 | self.tones.append(self.main_tone) | ||
95 | self._sort_tones() | 95 | self._sort_tones() | ||
96 | if len(self.tones) < 2: | 96 | if len(self.tones) < 2: | ||
97 | raise TypeError('Cannot have a chord made of only 1 unique tone') | 97 | raise TypeError('Cannot have a chord made of only 1 unique tone') | ||
98 | 98 | ||||
99 | def __str__(self): | 99 | def __str__(self): | ||
100 | return f'{'-'.join(map(str, self.tones))}' | 100 | return f'{'-'.join(map(str, self.tones))}' | ||
101 | 101 | ||||
102 | def __add__(self, other): | 102 | def __add__(self, other): | ||
n | 103 | """Addition supports Chord + Tone and Chord + Chord""" | n | 103 | """Add Chord + Tone or Chord + Chord.""" |
104 | if isinstance(other, Tone): | 104 | if isinstance(other, Tone): | ||
105 | return Chord(self.main_tone, *(self.tones + [other])) | 105 | return Chord(self.main_tone, *(self.tones + [other])) | ||
106 | if isinstance(other, Chord): | 106 | if isinstance(other, Chord): | ||
107 | return Chord(self.main_tone, *(self.tones + other.tones)) | 107 | return Chord(self.main_tone, *(self.tones + other.tones)) | ||
108 | 108 | ||||
109 | def __sub__(self, other): | 109 | def __sub__(self, other): | ||
n | 110 | """Subtraction supports Chord - Tone""" | n | 110 | """Subtract Chord - Tone.""" |
111 | if isinstance(other, Tone): | 111 | if isinstance(other, Tone): | ||
112 | if other in self.tones: | 112 | if other in self.tones: | ||
113 | self.tones.remove(other) | 113 | self.tones.remove(other) | ||
114 | if len(self.tones) < 2: | 114 | if len(self.tones) < 2: | ||
115 | raise TypeError('Cannot have a chord made of only 1 unique tone') | 115 | raise TypeError('Cannot have a chord made of only 1 unique tone') | ||
116 | if other == self.main_tone: | 116 | if other == self.main_tone: | ||
117 | self.main_tone = self.tones[0] | 117 | self.main_tone = self.tones[0] | ||
118 | return self | 118 | return self | ||
119 | else: | 119 | else: | ||
120 | raise TypeError(f'Cannot remove tone {other} from chord {self}') | 120 | raise TypeError(f'Cannot remove tone {other} from chord {self}') | ||
121 | 121 | ||||
122 | def _sort_tones(self): | 122 | def _sort_tones(self): | ||
n | 123 | """Sorts all tones in chord relative to the main tone""" | n | 123 | """Sort all tones in chord relative to the main tone.""" |
124 | main_tone_idx = Tone.TONES.index(self.main_tone) | 124 | main_tone_idx = Tone.TONES.index(self.main_tone) | ||
125 | tones_unique = set(self.tones) # because of the hash func the tones are saved as strings | 125 | tones_unique = set(self.tones) # because of the hash func the tones are saved as strings | ||
126 | all_tones_sorted = Tone.TONES[main_tone_idx:] + Tone.TONES[:main_tone_idx] | 126 | all_tones_sorted = Tone.TONES[main_tone_idx:] + Tone.TONES[:main_tone_idx] | ||
127 | self.tones = list(map(Tone, filter(lambda tone: tone in tones_unique, all_tones_sorted))) | 127 | self.tones = list(map(Tone, filter(lambda tone: tone in tones_unique, all_tones_sorted))) | ||
128 | 128 | ||||
129 | def _check_for_special_tone(self, idx): | 129 | def _check_for_special_tone(self, idx): | ||
n | 130 | """Checks if there is any tone that makes a special tone with root based on idx given""" | n | 130 | """Check if there is any tone that makes a special tone with root based on idx given.""" |
131 | return any(str(Interval.init_from_tones(self.main_tone, tone)) == Interval.INTERVALS[idx] for tone in self.tones) | 131 | return any(str(Interval.init_from_tones(self.main_tone, tone)) == Interval.INTERVALS[idx] for tone in self.tones) | ||
132 | 132 | ||||
133 | def is_minor(self): | 133 | def is_minor(self): | ||
n | 134 | """Checks if chord is minor""" | n | 134 | """Check if chord is minor.""" |
135 | return self._check_for_special_tone(self.INDEX_MINOR) | 135 | return self._check_for_special_tone(self.INDEX_MINOR) | ||
136 | 136 | ||||
137 | def is_major(self): | 137 | def is_major(self): | ||
n | 138 | """Checks if chord is major""" | n | 138 | """Check if chord is major.""" |
139 | return self._check_for_special_tone(self.INDEX_MAJOR) | 139 | return self._check_for_special_tone(self.INDEX_MAJOR) | ||
140 | 140 | ||||
141 | def is_power_chord(self): | 141 | def is_power_chord(self): | ||
n | 142 | """Checks if tone is power chord""" | n | 142 | """Check if tone is power chord.""" |
143 | return not self.is_minor() and not self.is_major() | 143 | return not self.is_minor() and not self.is_major() | ||
144 | 144 | ||||
145 | def transposed(self, interval): | 145 | def transposed(self, interval): | ||
n | 146 | """Transposes by interval""" | n | 146 | """Transpose by interval.""" |
147 | return Chord(self.main_tone + interval, *list(map(lambda tone: tone + interval, self.tones))) | 147 | return Chord(self.main_tone + interval, *list(map(lambda tone: tone + interval, self.tones))) | ||
t | 148 | t |
Legends | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
|
f | 1 | class Tone: | f | 1 | class Tone: |
n | n | 2 | """Implements a tone""" | ||
2 | 3 | ||||
n | 3 | tones = ('C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B') | n | 4 | TONES = ('C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B') |
4 | 5 | ||||
5 | def __key(self): | 6 | def __key(self): | ||
6 | """Create private key so tone is hashable""" | 7 | """Create private key so tone is hashable""" | ||
7 | return str(self.tone) | 8 | return str(self.tone) | ||
8 | 9 | ||||
9 | def __init__(self, tone): | 10 | def __init__(self, tone): | ||
10 | self.tone = tone | 11 | self.tone = tone | ||
11 | 12 | ||||
12 | def __str__(self): | 13 | def __str__(self): | ||
13 | return self.tone | 14 | return self.tone | ||
14 | 15 | ||||
15 | def __eq__(self, other): | 16 | def __eq__(self, other): | ||
16 | return str(self) == str(other) | 17 | return str(self) == str(other) | ||
17 | 18 | ||||
18 | def __hash__(self): | 19 | def __hash__(self): | ||
19 | return hash(self.__key()) | 20 | return hash(self.__key()) | ||
20 | 21 | ||||
21 | def __add__(self, other): | 22 | def __add__(self, other): | ||
22 | """Addition supports Tone + Tone and Tone + Interval""" | 23 | """Addition supports Tone + Tone and Tone + Interval""" | ||
23 | if isinstance(other, Tone): | 24 | if isinstance(other, Tone): | ||
24 | return Chord(self, other) | 25 | return Chord(self, other) | ||
25 | elif isinstance(other, Interval): | 26 | elif isinstance(other, Interval): | ||
n | 26 | idx = (Tone.tones.index(self) + other.num_of_semitones) % len(Tone.tones) | n | 27 | idx = (Tone.TONES.index(self) + other.num_of_semitones) % len(Tone.TONES) |
27 | return Tone(Tone.tones[idx]) | 28 | return Tone(Tone.TONES[idx]) | ||
28 | 29 | ||||
29 | def __sub__(self, other): | 30 | def __sub__(self, other): | ||
30 | """Subtraction supports Tone - Tone and Tone - Interval""" | 31 | """Subtraction supports Tone - Tone and Tone - Interval""" | ||
31 | if isinstance(other, Tone): | 32 | if isinstance(other, Tone): | ||
n | 32 | return Interval((Tone.tones.index(self) - Tone.tones.index(other)) % len(Tone.tones)) | n | 33 | return Interval((Tone.TONES.index(self) - Tone.TONES.index(other)) % len(Tone.TONES)) |
33 | elif isinstance(other, Interval): | 34 | elif isinstance(other, Interval): | ||
n | 34 | idx = (Tone.tones.index(self) - other.num_of_semitones) % len(Tone.tones) | n | 35 | idx = (Tone.TONES.index(self) - other.num_of_semitones) % len(Tone.TONES) |
35 | return Tone(Tone.tones[idx]) | 36 | return Tone(Tone.TONES[idx]) | ||
36 | 37 | ||||
37 | 38 | ||||
38 | class Interval: | 39 | class Interval: | ||
n | n | 40 | """Implements an interval""" | ||
39 | 41 | ||||
n | 40 | intervals = { | n | 42 | INTERVALS = { |
41 | 0: 'unison', | 43 | 0: 'unison', | ||
42 | 1: 'minor 2nd', | 44 | 1: 'minor 2nd', | ||
43 | 2: 'major 2nd', | 45 | 2: 'major 2nd', | ||
44 | 3: 'minor 3rd', | 46 | 3: 'minor 3rd', | ||
n | 45 | 4: 'major 3th', | n | 47 | 4: 'major 3rd', |
46 | 5: 'perfect 4th', | 48 | 5: 'perfect 4th', | ||
47 | 6: 'diminished 5th', | 49 | 6: 'diminished 5th', | ||
48 | 7: 'perfect 5th', | 50 | 7: 'perfect 5th', | ||
49 | 8: 'minor 6th', | 51 | 8: 'minor 6th', | ||
50 | 9: 'major 6th', | 52 | 9: 'major 6th', | ||
51 | 10: 'minor 7th', | 53 | 10: 'minor 7th', | ||
52 | 11: 'major 7th' | 54 | 11: 'major 7th' | ||
53 | } | 55 | } | ||
54 | 56 | ||||
n | 55 | def __init__(self, *args): | n | 57 | def __init__(self, num_of_semitones): |
56 | if len(args) == 1: | ||||
57 | self.num_of_semitones = args[0] % 12 | 58 | self.num_of_semitones = num_of_semitones % 12 | ||
58 | elif len(args) == 2: # create interval by using two tones | ||||
59 | self.num_of_semitones = abs(Tone.tones.index(args[0]) - Tone.tones.index(args[1])) | ||||
60 | 59 | ||||
61 | def __str__(self): | 60 | def __str__(self): | ||
n | 62 | return self.intervals[self.num_of_semitones] | n | 61 | return self.INTERVALS[self.num_of_semitones] |
63 | 62 | ||||
64 | def __add__(self, other): | 63 | def __add__(self, other): | ||
65 | """Addition supports Interval + Interval""" | 64 | """Addition supports Interval + Interval""" | ||
66 | if isinstance(other, Tone): | 65 | if isinstance(other, Tone): | ||
67 | raise TypeError("Invalid operation") | 66 | raise TypeError("Invalid operation") | ||
68 | elif isinstance(other, Interval): | 67 | elif isinstance(other, Interval): | ||
69 | return Interval(self.num_of_semitones + other.num_of_semitones) | 68 | return Interval(self.num_of_semitones + other.num_of_semitones) | ||
70 | 69 | ||||
71 | def __sub__(self, other): | 70 | def __sub__(self, other): | ||
72 | """Interval - Tone is invalid operation""" | 71 | """Interval - Tone is invalid operation""" | ||
73 | if isinstance(other, Tone): | 72 | if isinstance(other, Tone): | ||
74 | raise TypeError("Invalid operation") | 73 | raise TypeError("Invalid operation") | ||
75 | 74 | ||||
76 | def __neg__(self): | 75 | def __neg__(self): | ||
n | 77 | self.num_of_semitones = len(Tone.tones) - self.num_of_semitones | n | 76 | """Supports such intervals: -Interval(5)""" |
78 | return self | 77 | return Interval(len(Tone.TONES) - self.num_of_semitones) | ||
78 | |||||
79 | @classmethod | ||||
80 | def init_from_tones(cls, tone1, tone2): | ||||
81 | """Initialize interval from 2 tones""" | ||||
82 | return cls(abs(Tone.TONES.index(tone1) - Tone.TONES.index(tone2))) | ||||
79 | 83 | ||||
80 | 84 | ||||
81 | class Chord: | 85 | class Chord: | ||
n | n | 86 | """Implements a chord""" | ||
87 | |||||
88 | INDEX_MINOR = 3 # index in interval.INTERVALS for minor 3rd | ||||
89 | INDEX_MAJOR = 4 # index in interval.INTERVALS for major 3rd | ||||
82 | 90 | ||||
83 | def __init__(self, main_tone, *args): | 91 | def __init__(self, main_tone, *args): | ||
84 | self.main_tone = main_tone | 92 | self.main_tone = main_tone | ||
85 | self.tones = list(args) | 93 | self.tones = list(args) | ||
86 | self.tones.append(self.main_tone) | 94 | self.tones.append(self.main_tone) | ||
87 | self._sort_tones() | 95 | self._sort_tones() | ||
88 | if len(self.tones) < 2: | 96 | if len(self.tones) < 2: | ||
89 | raise TypeError('Cannot have a chord made of only 1 unique tone') | 97 | raise TypeError('Cannot have a chord made of only 1 unique tone') | ||
90 | 98 | ||||
91 | def __str__(self): | 99 | def __str__(self): | ||
92 | return f'{'-'.join(map(str, self.tones))}' | 100 | return f'{'-'.join(map(str, self.tones))}' | ||
93 | 101 | ||||
94 | def __add__(self, other): | 102 | def __add__(self, other): | ||
95 | """Addition supports Chord + Tone and Chord + Chord""" | 103 | """Addition supports Chord + Tone and Chord + Chord""" | ||
96 | if isinstance(other, Tone): | 104 | if isinstance(other, Tone): | ||
n | 97 | self.tones.append(other) | n | 105 | return Chord(self.main_tone, *(self.tones + [other])) |
98 | self._sort_tones() | ||||
99 | return self | ||||
100 | if isinstance(other, Chord): | 106 | if isinstance(other, Chord): | ||
101 | return Chord(self.main_tone, *(self.tones + other.tones)) | 107 | return Chord(self.main_tone, *(self.tones + other.tones)) | ||
102 | 108 | ||||
103 | def __sub__(self, other): | 109 | def __sub__(self, other): | ||
104 | """Subtraction supports Chord - Tone""" | 110 | """Subtraction supports Chord - Tone""" | ||
105 | if isinstance(other, Tone): | 111 | if isinstance(other, Tone): | ||
106 | if other in self.tones: | 112 | if other in self.tones: | ||
107 | self.tones.remove(other) | 113 | self.tones.remove(other) | ||
108 | if len(self.tones) < 2: | 114 | if len(self.tones) < 2: | ||
109 | raise TypeError('Cannot have a chord made of only 1 unique tone') | 115 | raise TypeError('Cannot have a chord made of only 1 unique tone') | ||
110 | if other == self.main_tone: | 116 | if other == self.main_tone: | ||
111 | self.main_tone = self.tones[0] | 117 | self.main_tone = self.tones[0] | ||
112 | return self | 118 | return self | ||
113 | else: | 119 | else: | ||
114 | raise TypeError(f'Cannot remove tone {other} from chord {self}') | 120 | raise TypeError(f'Cannot remove tone {other} from chord {self}') | ||
115 | 121 | ||||
116 | def _sort_tones(self): | 122 | def _sort_tones(self): | ||
117 | """Sorts all tones in chord relative to the main tone""" | 123 | """Sorts all tones in chord relative to the main tone""" | ||
n | 118 | main_tone_idx = Tone.tones.index(self.main_tone) | n | 124 | main_tone_idx = Tone.TONES.index(self.main_tone) |
119 | tones_unique = set(self.tones) # because of the hash func the tones are saved as strings | 125 | tones_unique = set(self.tones) # because of the hash func the tones are saved as strings | ||
n | 120 | all_tones_sorted = Tone.tones[main_tone_idx:] + Tone.tones[:main_tone_idx] | n | 126 | all_tones_sorted = Tone.TONES[main_tone_idx:] + Tone.TONES[:main_tone_idx] |
121 | self.tones = list(map(Tone, filter(lambda tone: tone in tones_unique, all_tones_sorted))) | 127 | self.tones = list(map(Tone, filter(lambda tone: tone in tones_unique, all_tones_sorted))) | ||
122 | 128 | ||||
n | 123 | def _check_tone(self, idx): | n | 129 | def _check_for_special_tone(self, idx): |
124 | """Checks if there is any tone that makes a special tone with root based on idx given""" | 130 | """Checks if there is any tone that makes a special tone with root based on idx given""" | ||
n | 125 | if any(str(Interval(self.main_tone, tone)) == Interval.intervals[idx] for tone in self.tones): | n | 131 | return any(str(Interval.init_from_tones(self.main_tone, tone)) == Interval.INTERVALS[idx] for tone in self.tones) |
126 | return True | ||||
127 | return False | ||||
128 | 132 | ||||
129 | def is_minor(self): | 133 | def is_minor(self): | ||
130 | """Checks if chord is minor""" | 134 | """Checks if chord is minor""" | ||
n | 131 | return self._check_tone(3) | n | 135 | return self._check_for_special_tone(self.INDEX_MINOR) |
132 | 136 | ||||
133 | def is_major(self): | 137 | def is_major(self): | ||
134 | """Checks if chord is major""" | 138 | """Checks if chord is major""" | ||
n | 135 | return self._check_tone(4) | n | 139 | return self._check_for_special_tone(self.INDEX_MAJOR) |
136 | 140 | ||||
137 | def is_power_chord(self): | 141 | def is_power_chord(self): | ||
138 | """Checks if tone is power chord""" | 142 | """Checks if tone is power chord""" | ||
139 | return not self.is_minor() and not self.is_major() | 143 | return not self.is_minor() and not self.is_major() | ||
140 | 144 | ||||
141 | def transposed(self, interval): | 145 | def transposed(self, interval): | ||
142 | """Transposes by interval""" | 146 | """Transposes by interval""" | ||
143 | return Chord(self.main_tone + interval, *list(map(lambda tone: tone + interval, self.tones))) | 147 | return Chord(self.main_tone + interval, *list(map(lambda tone: tone + interval, self.tones))) | ||
t | t | 148 |
Legends | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
|