1class Tone:
2 """Represent a musical tone."""
3
4 TONE_SCALE = ("C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B")
5
6 def __init__(self, name):
7 self.name = name
8
9 def __str__(self):
10 return self.name
11
12 def __int__(self):
13 return Tone.TONE_SCALE.index(self.name)
14
15 def __eq__(self, other):
16 return self.name == other.name
17
18 def __hash__(self):
19 """Return a hash value for the Tone object using its name."""
20 return hash(self.name)
21
22 def __add__(self, other):
23 if isinstance(other, Tone):
24 return Chord(self, other)
25 elif isinstance(other, Interval):
26 new_tone_index = Tone.TONE_SCALE.index(self.name) + other.semitone
27 return Tone(Tone.TONE_SCALE[new_tone_index % len(Tone.TONE_SCALE)])
28 raise TypeError("Invalid operation")
29
30 def __radd__(self, other):
31 if isinstance(other, Interval):
32 raise TypeError("Invalid operation")
33
34 def __sub__(self, other):
35 if isinstance(other, Tone):
36 return Interval((int(self) - int(other)) % len(Tone.TONE_SCALE))
37 elif isinstance(other, Interval):
38 new_tone_index = Tone.TONE_SCALE.index(self.name) - other.semitone
39 return Tone(Tone.TONE_SCALE[new_tone_index % len(Tone.TONE_SCALE)])
40 raise TypeError("Invalid operation")
41
42 def __rsub__(self, other):
43 if isinstance(other, Interval):
44 raise TypeError("Invalid operation")
45
46
47class Interval:
48 """Represent a music interval."""
49
50 SEMITONE_NAMES = (
51 "unison", "minor 2nd", "major 2nd", "minor 3rd", "major 3rd",
52 "perfect 4th", "diminished 5th", "perfect 5th", "minor 6th",
53 "major 6th", "minor 7th", "major 7th"
54 )
55
56 def __init__(self, semitone):
57 if not isinstance(semitone, int):
58 raise TypeError("Invalid semitone")
59
60 self.semitone = semitone % len(Interval.SEMITONE_NAMES)
61
62 def __str__(self):
63 return Interval.SEMITONE_NAMES[self.semitone]
64
65 def __neg__(self):
66 return Interval(-self.semitone)
67
68 def __add__(self, other):
69 if isinstance(other, Interval):
70 return Interval(self.semitone + other.semitone)
71 raise TypeError("Invalid operation")
72
73 def __sub__(self, other):
74 if isinstance(other, Interval):
75 return Interval(self.semitone - other.semitone)
76 raise TypeError("Invalid operation")
77
78
79class Chord:
80 """Represent a music chord."""
81
82 def __init__(self, *tones):
83
84 unique_tones = list(set(tones))
85 if len(unique_tones) <= 1:
86 raise TypeError("Cannot have a chord made of only 1 unique tone")
87
88 if not all(isinstance(tone, Tone) for tone in unique_tones):
89 raise TypeError("Invalid Tone found")
90
91 self.root = tones[0]
92 self.tones = list(filter(lambda tone: tone != self.root, unique_tones))
93
94 def sort_tones(self):
95 """Sort the tones relatively."""
96
97 root_index = Tone.TONE_SCALE.index(str(self.root))
98 relative_scale = Tone.TONE_SCALE[root_index:] + Tone.TONE_SCALE[:root_index]
99
100 return sorted(self.tones, key=lambda tone: relative_scale.index(str(tone)))
101
102 def __str__(self):
103 sorted_tones = self.sort_tones()
104 return f'{self.root}-{"-".join(map(str, sorted_tones))}'
105
106 def find_new_value(self, tone, interval):
107 """Set new values after transpose."""
108
109 if not isinstance(interval, Interval):
110 raise TypeError("Invalid Interval")
111
112 new_tone_index = Tone.TONE_SCALE.index(str(tone)) + interval.semitone
113 return Tone(Tone.TONE_SCALE[new_tone_index % len(Tone.TONE_SCALE)])
114
115 def transposed(self, interval):
116 if not isinstance(interval, Interval):
117 raise TypeError("Invalid operation")
118
119 transposed_root = self.find_new_value(self.root, interval)
120 transposed_tones = [self.find_new_value(tone, interval) for tone in self.tones]
121
122 return Chord(*([transposed_root] + transposed_tones))
123
124 def is_minor(self):
125 return any(str(tone - self.root) == "minor 3rd" for tone in self.tones)
126
127 def is_major(self):
128 return any(str(tone - self.root) == "major 3rd" for tone in self.tones)
129
130 def is_power_chord(self):
131 return not self.is_major() and not self.is_minor()
132
133 def __add__(self, other):
134 if isinstance(other, Tone):
135 return Chord(*([self.root] + self.tones + [other]))
136 elif isinstance(other, Chord):
137 return Chord(*([self.root] + self.tones + [other.root] + other.tones))
138 raise TypeError("Invalid operation")
139
140 def __sub__(self, other):
141 if isinstance(other, Tone):
142 new_tones = [self.root] + self.tones
143 if other not in new_tones:
144 raise TypeError (f"Cannot remove tone {other} from chord {self}")
145
146 new_tones.remove(other)
147 return Chord(*new_tones)
148 raise TypeError("Invalid operation")
.....................................
----------------------------------------------------------------------
Ran 37 tests in 0.001s
OK
Виктор Бечев
03.11.2024 22:31Генерално не сме против дефанзивността при взимането на input, просто знай, че нямаме навика да тестваме с невалиден вход.
|
f | 1 | class Tone: | f | 1 | class Tone: |
2 | """Represent a musical tone.""" | 2 | """Represent a musical tone.""" | ||
3 | 3 | ||||
4 | TONE_SCALE = ("C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B") | 4 | TONE_SCALE = ("C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B") | ||
5 | 5 | ||||
6 | def __init__(self, name): | 6 | def __init__(self, name): | ||
7 | self.name = name | 7 | self.name = name | ||
8 | 8 | ||||
9 | def __str__(self): | 9 | def __str__(self): | ||
10 | return self.name | 10 | return self.name | ||
11 | 11 | ||||
12 | def __int__(self): | 12 | def __int__(self): | ||
13 | return Tone.TONE_SCALE.index(self.name) | 13 | return Tone.TONE_SCALE.index(self.name) | ||
14 | 14 | ||||
15 | def __eq__(self, other): | 15 | def __eq__(self, other): | ||
16 | return self.name == other.name | 16 | return self.name == other.name | ||
17 | 17 | ||||
18 | def __hash__(self): | 18 | def __hash__(self): | ||
19 | """Return a hash value for the Tone object using its name.""" | 19 | """Return a hash value for the Tone object using its name.""" | ||
20 | return hash(self.name) | 20 | return hash(self.name) | ||
21 | 21 | ||||
22 | def __add__(self, other): | 22 | def __add__(self, other): | ||
23 | if isinstance(other, Tone): | 23 | if isinstance(other, Tone): | ||
24 | return Chord(self, other) | 24 | return Chord(self, other) | ||
25 | elif isinstance(other, Interval): | 25 | elif isinstance(other, Interval): | ||
26 | new_tone_index = Tone.TONE_SCALE.index(self.name) + other.semitone | 26 | new_tone_index = Tone.TONE_SCALE.index(self.name) + other.semitone | ||
27 | return Tone(Tone.TONE_SCALE[new_tone_index % len(Tone.TONE_SCALE)]) | 27 | return Tone(Tone.TONE_SCALE[new_tone_index % len(Tone.TONE_SCALE)]) | ||
n | 28 | raise TypeError("Invalid Tone or Interval.") | n | 28 | raise TypeError("Invalid operation") |
29 | 29 | ||||
30 | def __radd__(self, other): | 30 | def __radd__(self, other): | ||
31 | if isinstance(other, Interval): | 31 | if isinstance(other, Interval): | ||
32 | raise TypeError("Invalid operation") | 32 | raise TypeError("Invalid operation") | ||
33 | 33 | ||||
34 | def __sub__(self, other): | 34 | def __sub__(self, other): | ||
35 | if isinstance(other, Tone): | 35 | if isinstance(other, Tone): | ||
36 | return Interval((int(self) - int(other)) % len(Tone.TONE_SCALE)) | 36 | return Interval((int(self) - int(other)) % len(Tone.TONE_SCALE)) | ||
37 | elif isinstance(other, Interval): | 37 | elif isinstance(other, Interval): | ||
38 | new_tone_index = Tone.TONE_SCALE.index(self.name) - other.semitone | 38 | new_tone_index = Tone.TONE_SCALE.index(self.name) - other.semitone | ||
39 | return Tone(Tone.TONE_SCALE[new_tone_index % len(Tone.TONE_SCALE)]) | 39 | return Tone(Tone.TONE_SCALE[new_tone_index % len(Tone.TONE_SCALE)]) | ||
n | 40 | raise TypeError("Invalid Tone or Interval.") | n | 40 | raise TypeError("Invalid operation") |
41 | 41 | ||||
42 | def __rsub__(self, other): | 42 | def __rsub__(self, other): | ||
43 | if isinstance(other, Interval): | 43 | if isinstance(other, Interval): | ||
44 | raise TypeError("Invalid operation") | 44 | raise TypeError("Invalid operation") | ||
45 | 45 | ||||
46 | 46 | ||||
47 | class Interval: | 47 | class Interval: | ||
48 | """Represent a music interval.""" | 48 | """Represent a music interval.""" | ||
49 | 49 | ||||
50 | SEMITONE_NAMES = ( | 50 | SEMITONE_NAMES = ( | ||
51 | "unison", "minor 2nd", "major 2nd", "minor 3rd", "major 3rd", | 51 | "unison", "minor 2nd", "major 2nd", "minor 3rd", "major 3rd", | ||
52 | "perfect 4th", "diminished 5th", "perfect 5th", "minor 6th", | 52 | "perfect 4th", "diminished 5th", "perfect 5th", "minor 6th", | ||
53 | "major 6th", "minor 7th", "major 7th" | 53 | "major 6th", "minor 7th", "major 7th" | ||
54 | ) | 54 | ) | ||
55 | 55 | ||||
56 | def __init__(self, semitone): | 56 | def __init__(self, semitone): | ||
57 | if not isinstance(semitone, int): | 57 | if not isinstance(semitone, int): | ||
58 | raise TypeError("Invalid semitone") | 58 | raise TypeError("Invalid semitone") | ||
59 | 59 | ||||
60 | self.semitone = semitone % len(Interval.SEMITONE_NAMES) | 60 | self.semitone = semitone % len(Interval.SEMITONE_NAMES) | ||
61 | 61 | ||||
62 | def __str__(self): | 62 | def __str__(self): | ||
63 | return Interval.SEMITONE_NAMES[self.semitone] | 63 | return Interval.SEMITONE_NAMES[self.semitone] | ||
64 | 64 | ||||
65 | def __neg__(self): | 65 | def __neg__(self): | ||
66 | return Interval(-self.semitone) | 66 | return Interval(-self.semitone) | ||
67 | 67 | ||||
68 | def __add__(self, other): | 68 | def __add__(self, other): | ||
69 | if isinstance(other, Interval): | 69 | if isinstance(other, Interval): | ||
70 | return Interval(self.semitone + other.semitone) | 70 | return Interval(self.semitone + other.semitone) | ||
71 | raise TypeError("Invalid operation") | 71 | raise TypeError("Invalid operation") | ||
72 | 72 | ||||
73 | def __sub__(self, other): | 73 | def __sub__(self, other): | ||
74 | if isinstance(other, Interval): | 74 | if isinstance(other, Interval): | ||
75 | return Interval(self.semitone - other.semitone) | 75 | return Interval(self.semitone - other.semitone) | ||
76 | raise TypeError("Invalid operation") | 76 | raise TypeError("Invalid operation") | ||
77 | 77 | ||||
78 | 78 | ||||
79 | class Chord: | 79 | class Chord: | ||
80 | """Represent a music chord.""" | 80 | """Represent a music chord.""" | ||
81 | 81 | ||||
82 | def __init__(self, *tones): | 82 | def __init__(self, *tones): | ||
83 | 83 | ||||
84 | unique_tones = list(set(tones)) | 84 | unique_tones = list(set(tones)) | ||
85 | if len(unique_tones) <= 1: | 85 | if len(unique_tones) <= 1: | ||
86 | raise TypeError("Cannot have a chord made of only 1 unique tone") | 86 | raise TypeError("Cannot have a chord made of only 1 unique tone") | ||
87 | 87 | ||||
88 | if not all(isinstance(tone, Tone) for tone in unique_tones): | 88 | if not all(isinstance(tone, Tone) for tone in unique_tones): | ||
89 | raise TypeError("Invalid Tone found") | 89 | raise TypeError("Invalid Tone found") | ||
90 | 90 | ||||
91 | self.root = tones[0] | 91 | self.root = tones[0] | ||
92 | self.tones = list(filter(lambda tone: tone != self.root, unique_tones)) | 92 | self.tones = list(filter(lambda tone: tone != self.root, unique_tones)) | ||
93 | 93 | ||||
94 | def sort_tones(self): | 94 | def sort_tones(self): | ||
95 | """Sort the tones relatively.""" | 95 | """Sort the tones relatively.""" | ||
96 | 96 | ||||
97 | root_index = Tone.TONE_SCALE.index(str(self.root)) | 97 | root_index = Tone.TONE_SCALE.index(str(self.root)) | ||
98 | relative_scale = Tone.TONE_SCALE[root_index:] + Tone.TONE_SCALE[:root_index] | 98 | relative_scale = Tone.TONE_SCALE[root_index:] + Tone.TONE_SCALE[:root_index] | ||
99 | 99 | ||||
100 | return sorted(self.tones, key=lambda tone: relative_scale.index(str(tone))) | 100 | return sorted(self.tones, key=lambda tone: relative_scale.index(str(tone))) | ||
101 | 101 | ||||
102 | def __str__(self): | 102 | def __str__(self): | ||
103 | sorted_tones = self.sort_tones() | 103 | sorted_tones = self.sort_tones() | ||
104 | return f'{self.root}-{"-".join(map(str, sorted_tones))}' | 104 | return f'{self.root}-{"-".join(map(str, sorted_tones))}' | ||
105 | 105 | ||||
106 | def find_new_value(self, tone, interval): | 106 | def find_new_value(self, tone, interval): | ||
107 | """Set new values after transpose.""" | 107 | """Set new values after transpose.""" | ||
108 | 108 | ||||
109 | if not isinstance(interval, Interval): | 109 | if not isinstance(interval, Interval): | ||
110 | raise TypeError("Invalid Interval") | 110 | raise TypeError("Invalid Interval") | ||
111 | 111 | ||||
112 | new_tone_index = Tone.TONE_SCALE.index(str(tone)) + interval.semitone | 112 | new_tone_index = Tone.TONE_SCALE.index(str(tone)) + interval.semitone | ||
113 | return Tone(Tone.TONE_SCALE[new_tone_index % len(Tone.TONE_SCALE)]) | 113 | return Tone(Tone.TONE_SCALE[new_tone_index % len(Tone.TONE_SCALE)]) | ||
114 | 114 | ||||
115 | def transposed(self, interval): | 115 | def transposed(self, interval): | ||
116 | if not isinstance(interval, Interval): | 116 | if not isinstance(interval, Interval): | ||
n | 117 | raise TypeError("Invalid Interval") | n | 117 | raise TypeError("Invalid operation") |
118 | 118 | ||||
119 | transposed_root = self.find_new_value(self.root, interval) | 119 | transposed_root = self.find_new_value(self.root, interval) | ||
120 | transposed_tones = [self.find_new_value(tone, interval) for tone in self.tones] | 120 | transposed_tones = [self.find_new_value(tone, interval) for tone in self.tones] | ||
121 | 121 | ||||
122 | return Chord(*([transposed_root] + transposed_tones)) | 122 | return Chord(*([transposed_root] + transposed_tones)) | ||
123 | 123 | ||||
124 | def is_minor(self): | 124 | def is_minor(self): | ||
125 | return any(str(tone - self.root) == "minor 3rd" for tone in self.tones) | 125 | return any(str(tone - self.root) == "minor 3rd" for tone in self.tones) | ||
126 | 126 | ||||
127 | def is_major(self): | 127 | def is_major(self): | ||
128 | return any(str(tone - self.root) == "major 3rd" for tone in self.tones) | 128 | return any(str(tone - self.root) == "major 3rd" for tone in self.tones) | ||
129 | 129 | ||||
130 | def is_power_chord(self): | 130 | def is_power_chord(self): | ||
131 | return not self.is_major() and not self.is_minor() | 131 | return not self.is_major() and not self.is_minor() | ||
132 | 132 | ||||
133 | def __add__(self, other): | 133 | def __add__(self, other): | ||
134 | if isinstance(other, Tone): | 134 | if isinstance(other, Tone): | ||
135 | return Chord(*([self.root] + self.tones + [other])) | 135 | return Chord(*([self.root] + self.tones + [other])) | ||
136 | elif isinstance(other, Chord): | 136 | elif isinstance(other, Chord): | ||
137 | return Chord(*([self.root] + self.tones + [other.root] + other.tones)) | 137 | return Chord(*([self.root] + self.tones + [other.root] + other.tones)) | ||
n | 138 | return TypeError("Invalid Tone or Chord") | n | 138 | raise TypeError("Invalid operation") |
139 | 139 | ||||
140 | def __sub__(self, other): | 140 | def __sub__(self, other): | ||
141 | if isinstance(other, Tone): | 141 | if isinstance(other, Tone): | ||
142 | new_tones = [self.root] + self.tones | 142 | new_tones = [self.root] + self.tones | ||
143 | if other not in new_tones: | 143 | if other not in new_tones: | ||
144 | raise TypeError (f"Cannot remove tone {other} from chord {self}") | 144 | raise TypeError (f"Cannot remove tone {other} from chord {self}") | ||
145 | 145 | ||||
146 | new_tones.remove(other) | 146 | new_tones.remove(other) | ||
147 | return Chord(*new_tones) | 147 | return Chord(*new_tones) | ||
t | 148 | raise TypeError("Invalid Tone") | t | 148 | raise TypeError("Invalid operation") |
Legends | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
|
f | 1 | class Tone: | f | 1 | class Tone: |
2 | """Represent a musical tone.""" | 2 | """Represent a musical tone.""" | ||
3 | 3 | ||||
4 | TONE_SCALE = ("C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B") | 4 | TONE_SCALE = ("C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B") | ||
5 | 5 | ||||
6 | def __init__(self, name): | 6 | def __init__(self, name): | ||
7 | self.name = name | 7 | self.name = name | ||
8 | 8 | ||||
9 | def __str__(self): | 9 | def __str__(self): | ||
10 | return self.name | 10 | return self.name | ||
11 | 11 | ||||
12 | def __int__(self): | 12 | def __int__(self): | ||
13 | return Tone.TONE_SCALE.index(self.name) | 13 | return Tone.TONE_SCALE.index(self.name) | ||
14 | 14 | ||||
15 | def __eq__(self, other): | 15 | def __eq__(self, other): | ||
16 | return self.name == other.name | 16 | return self.name == other.name | ||
17 | 17 | ||||
18 | def __hash__(self): | 18 | def __hash__(self): | ||
19 | """Return a hash value for the Tone object using its name.""" | 19 | """Return a hash value for the Tone object using its name.""" | ||
20 | return hash(self.name) | 20 | return hash(self.name) | ||
21 | 21 | ||||
22 | def __add__(self, other): | 22 | def __add__(self, other): | ||
23 | if isinstance(other, Tone): | 23 | if isinstance(other, Tone): | ||
24 | return Chord(self, other) | 24 | return Chord(self, other) | ||
25 | elif isinstance(other, Interval): | 25 | elif isinstance(other, Interval): | ||
26 | new_tone_index = Tone.TONE_SCALE.index(self.name) + other.semitone | 26 | new_tone_index = Tone.TONE_SCALE.index(self.name) + other.semitone | ||
27 | return Tone(Tone.TONE_SCALE[new_tone_index % len(Tone.TONE_SCALE)]) | 27 | return Tone(Tone.TONE_SCALE[new_tone_index % len(Tone.TONE_SCALE)]) | ||
28 | raise TypeError("Invalid Tone or Interval.") | 28 | raise TypeError("Invalid Tone or Interval.") | ||
29 | 29 | ||||
30 | def __radd__(self, other): | 30 | def __radd__(self, other): | ||
31 | if isinstance(other, Interval): | 31 | if isinstance(other, Interval): | ||
32 | raise TypeError("Invalid operation") | 32 | raise TypeError("Invalid operation") | ||
33 | 33 | ||||
34 | def __sub__(self, other): | 34 | def __sub__(self, other): | ||
35 | if isinstance(other, Tone): | 35 | if isinstance(other, Tone): | ||
36 | return Interval((int(self) - int(other)) % len(Tone.TONE_SCALE)) | 36 | return Interval((int(self) - int(other)) % len(Tone.TONE_SCALE)) | ||
37 | elif isinstance(other, Interval): | 37 | elif isinstance(other, Interval): | ||
38 | new_tone_index = Tone.TONE_SCALE.index(self.name) - other.semitone | 38 | new_tone_index = Tone.TONE_SCALE.index(self.name) - other.semitone | ||
39 | return Tone(Tone.TONE_SCALE[new_tone_index % len(Tone.TONE_SCALE)]) | 39 | return Tone(Tone.TONE_SCALE[new_tone_index % len(Tone.TONE_SCALE)]) | ||
40 | raise TypeError("Invalid Tone or Interval.") | 40 | raise TypeError("Invalid Tone or Interval.") | ||
41 | 41 | ||||
42 | def __rsub__(self, other): | 42 | def __rsub__(self, other): | ||
43 | if isinstance(other, Interval): | 43 | if isinstance(other, Interval): | ||
44 | raise TypeError("Invalid operation") | 44 | raise TypeError("Invalid operation") | ||
45 | 45 | ||||
46 | 46 | ||||
47 | class Interval: | 47 | class Interval: | ||
48 | """Represent a music interval.""" | 48 | """Represent a music interval.""" | ||
49 | 49 | ||||
50 | SEMITONE_NAMES = ( | 50 | SEMITONE_NAMES = ( | ||
51 | "unison", "minor 2nd", "major 2nd", "minor 3rd", "major 3rd", | 51 | "unison", "minor 2nd", "major 2nd", "minor 3rd", "major 3rd", | ||
52 | "perfect 4th", "diminished 5th", "perfect 5th", "minor 6th", | 52 | "perfect 4th", "diminished 5th", "perfect 5th", "minor 6th", | ||
53 | "major 6th", "minor 7th", "major 7th" | 53 | "major 6th", "minor 7th", "major 7th" | ||
54 | ) | 54 | ) | ||
55 | 55 | ||||
56 | def __init__(self, semitone): | 56 | def __init__(self, semitone): | ||
57 | if not isinstance(semitone, int): | 57 | if not isinstance(semitone, int): | ||
58 | raise TypeError("Invalid semitone") | 58 | raise TypeError("Invalid semitone") | ||
59 | 59 | ||||
60 | self.semitone = semitone % len(Interval.SEMITONE_NAMES) | 60 | self.semitone = semitone % len(Interval.SEMITONE_NAMES) | ||
61 | 61 | ||||
62 | def __str__(self): | 62 | def __str__(self): | ||
63 | return Interval.SEMITONE_NAMES[self.semitone] | 63 | return Interval.SEMITONE_NAMES[self.semitone] | ||
64 | 64 | ||||
65 | def __neg__(self): | 65 | def __neg__(self): | ||
66 | return Interval(-self.semitone) | 66 | return Interval(-self.semitone) | ||
67 | 67 | ||||
68 | def __add__(self, other): | 68 | def __add__(self, other): | ||
69 | if isinstance(other, Interval): | 69 | if isinstance(other, Interval): | ||
70 | return Interval(self.semitone + other.semitone) | 70 | return Interval(self.semitone + other.semitone) | ||
71 | raise TypeError("Invalid operation") | 71 | raise TypeError("Invalid operation") | ||
72 | 72 | ||||
73 | def __sub__(self, other): | 73 | def __sub__(self, other): | ||
74 | if isinstance(other, Interval): | 74 | if isinstance(other, Interval): | ||
75 | return Interval(self.semitone - other.semitone) | 75 | return Interval(self.semitone - other.semitone) | ||
76 | raise TypeError("Invalid operation") | 76 | raise TypeError("Invalid operation") | ||
77 | 77 | ||||
78 | 78 | ||||
79 | class Chord: | 79 | class Chord: | ||
80 | """Represent a music chord.""" | 80 | """Represent a music chord.""" | ||
81 | 81 | ||||
82 | def __init__(self, *tones): | 82 | def __init__(self, *tones): | ||
n | 83 | if len(tones) == 1 and isinstance(tones[0], (list, tuple)): | n | ||
84 | tones = tones[0] | ||||
85 | 83 | ||||
86 | unique_tones = list(set(tones)) | 84 | unique_tones = list(set(tones)) | ||
87 | if len(unique_tones) <= 1: | 85 | if len(unique_tones) <= 1: | ||
88 | raise TypeError("Cannot have a chord made of only 1 unique tone") | 86 | raise TypeError("Cannot have a chord made of only 1 unique tone") | ||
89 | 87 | ||||
90 | if not all(isinstance(tone, Tone) for tone in unique_tones): | 88 | if not all(isinstance(tone, Tone) for tone in unique_tones): | ||
91 | raise TypeError("Invalid Tone found") | 89 | raise TypeError("Invalid Tone found") | ||
92 | 90 | ||||
93 | self.root = tones[0] | 91 | self.root = tones[0] | ||
94 | self.tones = list(filter(lambda tone: tone != self.root, unique_tones)) | 92 | self.tones = list(filter(lambda tone: tone != self.root, unique_tones)) | ||
95 | 93 | ||||
96 | def sort_tones(self): | 94 | def sort_tones(self): | ||
97 | """Sort the tones relatively.""" | 95 | """Sort the tones relatively.""" | ||
98 | 96 | ||||
99 | root_index = Tone.TONE_SCALE.index(str(self.root)) | 97 | root_index = Tone.TONE_SCALE.index(str(self.root)) | ||
100 | relative_scale = Tone.TONE_SCALE[root_index:] + Tone.TONE_SCALE[:root_index] | 98 | relative_scale = Tone.TONE_SCALE[root_index:] + Tone.TONE_SCALE[:root_index] | ||
101 | 99 | ||||
102 | return sorted(self.tones, key=lambda tone: relative_scale.index(str(tone))) | 100 | return sorted(self.tones, key=lambda tone: relative_scale.index(str(tone))) | ||
103 | 101 | ||||
104 | def __str__(self): | 102 | def __str__(self): | ||
105 | sorted_tones = self.sort_tones() | 103 | sorted_tones = self.sort_tones() | ||
106 | return f'{self.root}-{"-".join(map(str, sorted_tones))}' | 104 | return f'{self.root}-{"-".join(map(str, sorted_tones))}' | ||
107 | 105 | ||||
108 | def find_new_value(self, tone, interval): | 106 | def find_new_value(self, tone, interval): | ||
109 | """Set new values after transpose.""" | 107 | """Set new values after transpose.""" | ||
110 | 108 | ||||
111 | if not isinstance(interval, Interval): | 109 | if not isinstance(interval, Interval): | ||
112 | raise TypeError("Invalid Interval") | 110 | raise TypeError("Invalid Interval") | ||
113 | 111 | ||||
114 | new_tone_index = Tone.TONE_SCALE.index(str(tone)) + interval.semitone | 112 | new_tone_index = Tone.TONE_SCALE.index(str(tone)) + interval.semitone | ||
115 | return Tone(Tone.TONE_SCALE[new_tone_index % len(Tone.TONE_SCALE)]) | 113 | return Tone(Tone.TONE_SCALE[new_tone_index % len(Tone.TONE_SCALE)]) | ||
116 | 114 | ||||
117 | def transposed(self, interval): | 115 | def transposed(self, interval): | ||
118 | if not isinstance(interval, Interval): | 116 | if not isinstance(interval, Interval): | ||
119 | raise TypeError("Invalid Interval") | 117 | raise TypeError("Invalid Interval") | ||
120 | 118 | ||||
121 | transposed_root = self.find_new_value(self.root, interval) | 119 | transposed_root = self.find_new_value(self.root, interval) | ||
122 | transposed_tones = [self.find_new_value(tone, interval) for tone in self.tones] | 120 | transposed_tones = [self.find_new_value(tone, interval) for tone in self.tones] | ||
123 | 121 | ||||
n | 124 | return Chord([transposed_root] + transposed_tones) | n | 122 | return Chord(*([transposed_root] + transposed_tones)) |
125 | 123 | ||||
126 | def is_minor(self): | 124 | def is_minor(self): | ||
127 | return any(str(tone - self.root) == "minor 3rd" for tone in self.tones) | 125 | return any(str(tone - self.root) == "minor 3rd" for tone in self.tones) | ||
128 | 126 | ||||
129 | def is_major(self): | 127 | def is_major(self): | ||
130 | return any(str(tone - self.root) == "major 3rd" for tone in self.tones) | 128 | return any(str(tone - self.root) == "major 3rd" for tone in self.tones) | ||
131 | 129 | ||||
132 | def is_power_chord(self): | 130 | def is_power_chord(self): | ||
133 | return not self.is_major() and not self.is_minor() | 131 | return not self.is_major() and not self.is_minor() | ||
134 | 132 | ||||
135 | def __add__(self, other): | 133 | def __add__(self, other): | ||
136 | if isinstance(other, Tone): | 134 | if isinstance(other, Tone): | ||
n | 137 | return Chord([self.root] + self.tones + [other]) | n | 135 | return Chord(*([self.root] + self.tones + [other])) |
138 | elif isinstance(other, Chord): | 136 | elif isinstance(other, Chord): | ||
n | 139 | return Chord([self.root] + self.tones + [other.root] + other.tones) | n | 137 | return Chord(*([self.root] + self.tones + [other.root] + other.tones)) |
140 | return TypeError("Invalid Tone or Chord") | 138 | return TypeError("Invalid Tone or Chord") | ||
141 | 139 | ||||
142 | def __sub__(self, other): | 140 | def __sub__(self, other): | ||
143 | if isinstance(other, Tone): | 141 | if isinstance(other, Tone): | ||
144 | new_tones = [self.root] + self.tones | 142 | new_tones = [self.root] + self.tones | ||
145 | if other not in new_tones: | 143 | if other not in new_tones: | ||
146 | raise TypeError (f"Cannot remove tone {other} from chord {self}") | 144 | raise TypeError (f"Cannot remove tone {other} from chord {self}") | ||
147 | 145 | ||||
148 | new_tones.remove(other) | 146 | new_tones.remove(other) | ||
t | 149 | return Chord(new_tones) | t | 147 | return Chord(*new_tones) |
150 | raise TypeError("Invalid Tone") | 148 | raise TypeError("Invalid Tone") |
Legends | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
|
f | 1 | class Tone: | f | 1 | class Tone: |
2 | """Represent a musical tone.""" | 2 | """Represent a musical tone.""" | ||
3 | 3 | ||||
4 | TONE_SCALE = ("C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B") | 4 | TONE_SCALE = ("C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B") | ||
5 | 5 | ||||
6 | def __init__(self, name): | 6 | def __init__(self, name): | ||
7 | self.name = name | 7 | self.name = name | ||
8 | 8 | ||||
9 | def __str__(self): | 9 | def __str__(self): | ||
10 | return self.name | 10 | return self.name | ||
11 | 11 | ||||
12 | def __int__(self): | 12 | def __int__(self): | ||
13 | return Tone.TONE_SCALE.index(self.name) | 13 | return Tone.TONE_SCALE.index(self.name) | ||
14 | 14 | ||||
15 | def __eq__(self, other): | 15 | def __eq__(self, other): | ||
n | 16 | if isinstance(other, Tone): | n | ||
17 | return self.name == other.name | 16 | return self.name == other.name | ||
18 | return False | ||||
19 | 17 | ||||
20 | def __hash__(self): | 18 | def __hash__(self): | ||
21 | """Return a hash value for the Tone object using its name.""" | 19 | """Return a hash value for the Tone object using its name.""" | ||
22 | return hash(self.name) | 20 | return hash(self.name) | ||
23 | 21 | ||||
24 | def __add__(self, other): | 22 | def __add__(self, other): | ||
25 | if isinstance(other, Tone): | 23 | if isinstance(other, Tone): | ||
26 | return Chord(self, other) | 24 | return Chord(self, other) | ||
27 | elif isinstance(other, Interval): | 25 | elif isinstance(other, Interval): | ||
28 | new_tone_index = Tone.TONE_SCALE.index(self.name) + other.semitone | 26 | new_tone_index = Tone.TONE_SCALE.index(self.name) + other.semitone | ||
29 | return Tone(Tone.TONE_SCALE[new_tone_index % len(Tone.TONE_SCALE)]) | 27 | return Tone(Tone.TONE_SCALE[new_tone_index % len(Tone.TONE_SCALE)]) | ||
30 | raise TypeError("Invalid Tone or Interval.") | 28 | raise TypeError("Invalid Tone or Interval.") | ||
31 | 29 | ||||
32 | def __radd__(self, other): | 30 | def __radd__(self, other): | ||
33 | if isinstance(other, Interval): | 31 | if isinstance(other, Interval): | ||
34 | raise TypeError("Invalid operation") | 32 | raise TypeError("Invalid operation") | ||
35 | 33 | ||||
36 | def __sub__(self, other): | 34 | def __sub__(self, other): | ||
37 | if isinstance(other, Tone): | 35 | if isinstance(other, Tone): | ||
38 | return Interval((int(self) - int(other)) % len(Tone.TONE_SCALE)) | 36 | return Interval((int(self) - int(other)) % len(Tone.TONE_SCALE)) | ||
39 | elif isinstance(other, Interval): | 37 | elif isinstance(other, Interval): | ||
40 | new_tone_index = Tone.TONE_SCALE.index(self.name) - other.semitone | 38 | new_tone_index = Tone.TONE_SCALE.index(self.name) - other.semitone | ||
41 | return Tone(Tone.TONE_SCALE[new_tone_index % len(Tone.TONE_SCALE)]) | 39 | return Tone(Tone.TONE_SCALE[new_tone_index % len(Tone.TONE_SCALE)]) | ||
42 | raise TypeError("Invalid Tone or Interval.") | 40 | raise TypeError("Invalid Tone or Interval.") | ||
43 | 41 | ||||
44 | def __rsub__(self, other): | 42 | def __rsub__(self, other): | ||
45 | if isinstance(other, Interval): | 43 | if isinstance(other, Interval): | ||
46 | raise TypeError("Invalid operation") | 44 | raise TypeError("Invalid operation") | ||
47 | 45 | ||||
48 | 46 | ||||
49 | class Interval: | 47 | class Interval: | ||
50 | """Represent a music interval.""" | 48 | """Represent a music interval.""" | ||
51 | 49 | ||||
n | 52 | SEMITONE_NAMES = { | n | 50 | SEMITONE_NAMES = ( |
53 | 0: "unison", | 51 | "unison", "minor 2nd", "major 2nd", "minor 3rd", "major 3rd", | ||
54 | 1: "minor 2nd", | 52 | "perfect 4th", "diminished 5th", "perfect 5th", "minor 6th", | ||
55 | 2: "major 2nd", | 53 | "major 6th", "minor 7th", "major 7th" | ||
56 | 3: "minor 3rd", | ||||
57 | 4: "major 3rd", | ||||
58 | 5: "perfect 4th", | ||||
59 | 6: "diminished 5th", | ||||
60 | 7: "perfect 5th", | ||||
61 | 8: "minor 6th", | ||||
62 | 9: "major 6th", | ||||
63 | 10: "minor 7th", | ||||
64 | 11: "major 7th", | ||||
65 | } | 54 | ) | ||
66 | 55 | ||||
67 | def __init__(self, semitone): | 56 | def __init__(self, semitone): | ||
68 | if not isinstance(semitone, int): | 57 | if not isinstance(semitone, int): | ||
69 | raise TypeError("Invalid semitone") | 58 | raise TypeError("Invalid semitone") | ||
70 | 59 | ||||
71 | self.semitone = semitone % len(Interval.SEMITONE_NAMES) | 60 | self.semitone = semitone % len(Interval.SEMITONE_NAMES) | ||
72 | 61 | ||||
73 | def __str__(self): | 62 | def __str__(self): | ||
n | 74 | return Interval.SEMITONE_NAMES.get(self.semitone, "Invalid semitone") | n | 63 | return Interval.SEMITONE_NAMES[self.semitone] |
75 | 64 | ||||
76 | def __neg__(self): | 65 | def __neg__(self): | ||
77 | return Interval(-self.semitone) | 66 | return Interval(-self.semitone) | ||
78 | 67 | ||||
79 | def __add__(self, other): | 68 | def __add__(self, other): | ||
80 | if isinstance(other, Interval): | 69 | if isinstance(other, Interval): | ||
81 | return Interval(self.semitone + other.semitone) | 70 | return Interval(self.semitone + other.semitone) | ||
82 | raise TypeError("Invalid operation") | 71 | raise TypeError("Invalid operation") | ||
83 | 72 | ||||
84 | def __sub__(self, other): | 73 | def __sub__(self, other): | ||
85 | if isinstance(other, Interval): | 74 | if isinstance(other, Interval): | ||
86 | return Interval(self.semitone - other.semitone) | 75 | return Interval(self.semitone - other.semitone) | ||
87 | raise TypeError("Invalid operation") | 76 | raise TypeError("Invalid operation") | ||
88 | 77 | ||||
89 | 78 | ||||
90 | class Chord: | 79 | class Chord: | ||
n | 91 | """Represent a music cord.""" | n | 80 | """Represent a music chord.""" |
92 | 81 | ||||
93 | def __init__(self, *tones): | 82 | def __init__(self, *tones): | ||
94 | if len(tones) == 1 and isinstance(tones[0], (list, tuple)): | 83 | if len(tones) == 1 and isinstance(tones[0], (list, tuple)): | ||
n | 95 | tones = tones[0] | n | 84 | tones = tones[0] |
96 | 85 | ||||
97 | unique_tones = list(set(tones)) | 86 | unique_tones = list(set(tones)) | ||
98 | if len(unique_tones) <= 1: | 87 | if len(unique_tones) <= 1: | ||
99 | raise TypeError("Cannot have a chord made of only 1 unique tone") | 88 | raise TypeError("Cannot have a chord made of only 1 unique tone") | ||
100 | 89 | ||||
101 | if not all(isinstance(tone, Tone) for tone in unique_tones): | 90 | if not all(isinstance(tone, Tone) for tone in unique_tones): | ||
102 | raise TypeError("Invalid Tone found") | 91 | raise TypeError("Invalid Tone found") | ||
103 | 92 | ||||
104 | self.root = tones[0] | 93 | self.root = tones[0] | ||
n | 105 | self.tones = list(filter(lambda tone: tone != self.root, unique_tones)) | n | 94 | self.tones = list(filter(lambda tone: tone != self.root, unique_tones)) |
106 | 95 | ||||
107 | def sort_tones(self): | 96 | def sort_tones(self): | ||
108 | """Sort the tones relatively.""" | 97 | """Sort the tones relatively.""" | ||
109 | 98 | ||||
110 | root_index = Tone.TONE_SCALE.index(str(self.root)) | 99 | root_index = Tone.TONE_SCALE.index(str(self.root)) | ||
111 | relative_scale = Tone.TONE_SCALE[root_index:] + Tone.TONE_SCALE[:root_index] | 100 | relative_scale = Tone.TONE_SCALE[root_index:] + Tone.TONE_SCALE[:root_index] | ||
112 | 101 | ||||
113 | return sorted(self.tones, key=lambda tone: relative_scale.index(str(tone))) | 102 | return sorted(self.tones, key=lambda tone: relative_scale.index(str(tone))) | ||
114 | 103 | ||||
115 | def __str__(self): | 104 | def __str__(self): | ||
116 | sorted_tones = self.sort_tones() | 105 | sorted_tones = self.sort_tones() | ||
117 | return f'{self.root}-{"-".join(map(str, sorted_tones))}' | 106 | return f'{self.root}-{"-".join(map(str, sorted_tones))}' | ||
118 | 107 | ||||
119 | def find_new_value(self, tone, interval): | 108 | def find_new_value(self, tone, interval): | ||
120 | """Set new values after transpose.""" | 109 | """Set new values after transpose.""" | ||
121 | 110 | ||||
122 | if not isinstance(interval, Interval): | 111 | if not isinstance(interval, Interval): | ||
123 | raise TypeError("Invalid Interval") | 112 | raise TypeError("Invalid Interval") | ||
124 | 113 | ||||
125 | new_tone_index = Tone.TONE_SCALE.index(str(tone)) + interval.semitone | 114 | new_tone_index = Tone.TONE_SCALE.index(str(tone)) + interval.semitone | ||
126 | return Tone(Tone.TONE_SCALE[new_tone_index % len(Tone.TONE_SCALE)]) | 115 | return Tone(Tone.TONE_SCALE[new_tone_index % len(Tone.TONE_SCALE)]) | ||
127 | 116 | ||||
128 | def transposed(self, interval): | 117 | def transposed(self, interval): | ||
129 | if not isinstance(interval, Interval): | 118 | if not isinstance(interval, Interval): | ||
130 | raise TypeError("Invalid Interval") | 119 | raise TypeError("Invalid Interval") | ||
131 | 120 | ||||
132 | transposed_root = self.find_new_value(self.root, interval) | 121 | transposed_root = self.find_new_value(self.root, interval) | ||
133 | transposed_tones = [self.find_new_value(tone, interval) for tone in self.tones] | 122 | transposed_tones = [self.find_new_value(tone, interval) for tone in self.tones] | ||
134 | 123 | ||||
135 | return Chord([transposed_root] + transposed_tones) | 124 | return Chord([transposed_root] + transposed_tones) | ||
136 | 125 | ||||
137 | def is_minor(self): | 126 | def is_minor(self): | ||
n | 138 | return any([True for tone in self.tones if str(tone - self.root) == "minor 3rd"]) | n | 127 | return any(str(tone - self.root) == "minor 3rd" for tone in self.tones) |
139 | 128 | ||||
140 | def is_major(self): | 129 | def is_major(self): | ||
n | 141 | return any([True for tone in self.tones if str(tone - self.root) == "major 3rd"]) | n | 130 | return any(str(tone - self.root) == "major 3rd" for tone in self.tones) |
142 | 131 | ||||
143 | def is_power_chord(self): | 132 | def is_power_chord(self): | ||
144 | return not self.is_major() and not self.is_minor() | 133 | return not self.is_major() and not self.is_minor() | ||
145 | 134 | ||||
146 | def __add__(self, other): | 135 | def __add__(self, other): | ||
147 | if isinstance(other, Tone): | 136 | if isinstance(other, Tone): | ||
148 | return Chord([self.root] + self.tones + [other]) | 137 | return Chord([self.root] + self.tones + [other]) | ||
149 | elif isinstance(other, Chord): | 138 | elif isinstance(other, Chord): | ||
150 | return Chord([self.root] + self.tones + [other.root] + other.tones) | 139 | return Chord([self.root] + self.tones + [other.root] + other.tones) | ||
151 | return TypeError("Invalid Tone or Chord") | 140 | return TypeError("Invalid Tone or Chord") | ||
152 | 141 | ||||
153 | def __sub__(self, other): | 142 | def __sub__(self, other): | ||
154 | if isinstance(other, Tone): | 143 | if isinstance(other, Tone): | ||
155 | new_tones = [self.root] + self.tones | 144 | new_tones = [self.root] + self.tones | ||
156 | if other not in new_tones: | 145 | if other not in new_tones: | ||
t | 157 | raise TypeError (f"Cannot remove tone {str(other)} from chord {str(self)}") | t | 146 | raise TypeError (f"Cannot remove tone {other} from chord {self}") |
158 | 147 | ||||
159 | new_tones.remove(other) | 148 | new_tones.remove(other) | ||
160 | return Chord(new_tones) | 149 | return Chord(new_tones) | ||
161 | raise TypeError("Invalid Tone") | 150 | raise TypeError("Invalid Tone") |
Legends | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
|