1DEFINED_TONES = (
2 "C", "C#", "D", "D#", "E", "F",
3 "F#", "G", "G#", "A", "A#", "B"
4 )
5DEFINED_INTERVALS = (
6 "unison", "minor 2nd", "major 2nd",
7 "minor 3rd", "major 3rd", "perfect 4th",
8 "diminished 5th", "perfect 5th", "minor 6th",
9 "major 6th", "minor 7th", "major 7th"
10 )
11TOTAL_TONES_COUNT = 12
12
13
14class Tone:
15
16 def __init__(self, note):
17 self.note = note
18
19 def __str__(self):
20 return self.note
21
22 def __add__(self, other):
23 if isinstance(other, Tone):
24 return Chord(self, other)
25 elif isinstance(other, Interval):
26 note_idx = (DEFINED_TONES.index(self.note)
27 + other.number_of_semitones)
28 return Tone(DEFINED_TONES[note_idx % TOTAL_TONES_COUNT])
29
30 def __sub__(self, other):
31 if isinstance(other, Tone):
32 return Interval(DEFINED_TONES.index(self.note)
33 - DEFINED_TONES.index(other.note))
34 elif isinstance(other, Interval):
35 note_idx = (DEFINED_TONES.index(self.note)
36 - other.number_of_semitones)
37 return Tone(DEFINED_TONES[note_idx % TOTAL_TONES_COUNT])
38
39 def __eq__(self, value):
40 return self.note == value.note
41
42
43class Interval:
44
45 def __init__(self, number_of_semitones):
46 self.number_of_semitones = number_of_semitones % TOTAL_TONES_COUNT
47 self.semitone = DEFINED_INTERVALS[self.number_of_semitones]
48
49 def __str__(self):
50 return self.semitone
51
52 def __add__(self, other):
53 if isinstance(other, Interval):
54 return Interval(self.number_of_semitones
55 + other.number_of_semitones)
56 elif isinstance(other, Tone):
57 raise TypeError("Invalid operation")
58
59 def __sub__(self, other):
60 if isinstance(other, Tone):
61 raise TypeError("Invalid operation")
62
63 def __neg__(self):
64 return Interval(-self.number_of_semitones)
65
66
67class Chord:
68
69 def __init__(self, root, *tones):
70 self.root = root
71 self.secondary_tones = []
72 for tone in tones:
73 if tone not in self.secondary_tones:
74 self.secondary_tones.append(tone)
75 if root in self.secondary_tones:
76 self.secondary_tones.remove(root)
77 if not self.secondary_tones:
78 raise TypeError("Cannot have a chord made of only 1 unique tone")
79 self.secondary_tones.sort(key=lambda x: DEFINED_TONES.index(x.note))
80
81 def __str__(self):
82 tones_concat = "-".join(tone.note for tone in self.secondary_tones)
83 return f"{str(self.root)}-{tones_concat}"
84
85 def __add__(self, other):
86 if isinstance(other, Tone):
87 return Chord(self.root,
88 *self.secondary_tones,
89 other)
90 elif isinstance(other, Chord):
91 return Chord(self.root,
92 *self.secondary_tones,
93 other.root,
94 *other.secondary_tones)
95
96 def __sub__(self, other):
97 if not isinstance(other, Tone):
98 return
99 if (other not in self.secondary_tones and
100 not (other == self.root)):
101 raise TypeError(
102 f"Cannot remove tone {str(other)} from chord {str(self)}"
103 )
104 if other == self.root:
105 return Chord(self.secondary_tones[0],
106 *self.secondary_tones[1:])
107 else:
108 modified_secondary_tones = self.secondary_tones[:]
109 modified_secondary_tones.remove(other)
110 return Chord(self.root,
111 *modified_secondary_tones)
112
113 def transposed(self, interval):
114 modified_secondary_tones = [
115 (tone + interval) for tone in self.secondary_tones
116 ]
117 return Chord(self.root + interval, *modified_secondary_tones)
118
119 def is_minor(self):
120 root_idx = DEFINED_TONES.index(self.root.note)
121 minor_3rd_diff = DEFINED_INTERVALS.index("minor 3rd")
122 for tone in self.secondary_tones:
123 tone_idx = DEFINED_TONES.index(tone.note)
124 if abs(root_idx - tone_idx) == minor_3rd_diff:
125 return True
126 return False
127
128 def is_major(self):
129 root_idx = DEFINED_TONES.index(self.root.note)
130 major_3rd_diff = DEFINED_INTERVALS.index("major 3rd")
131 for tone in self.secondary_tones:
132 tone_idx = DEFINED_TONES.index(tone.note)
133 if abs(root_idx - tone_idx) == major_3rd_diff:
134 return True
135 return False
136
137 def is_power_chord(self):
138 return not (self.is_major() or self.is_minor())
.F..FFF..F...........FFF.............
======================================================================
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_existing_tone (test.TestOperations.test_add_tone_to_chord_existing_tone)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 270, in test_add_tone_to_chord_existing_tone
self.assertEqual(str(result_chord), "F-G#-C")
AssertionError: 'F-C-G#' != 'F-G#-C'
- F-C-G#
+ F-G#-C
======================================================================
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
? ++
----------------------------------------------------------------------
Ran 37 tests in 0.002s
FAILED (failures=8)
Георги Кунчев
04.11.2024 13:40Усещам, че се стремиш стриктно да спазваш правилото за 79 символа на ред. Това е чудесно, но имай предвид, че в днешно време повечето хора се стремят да използват 100 или 120 като лимит, защото 79 е легаси и е прекалено малко.
Оценявам факта, че всички пренасяния спазват стриктно PEP8 правилата (или поне не видях отклонения).
Ако решиш да си до 79 символа - супер, но ако ти е прекалено изкуствено да пренасяш само заради някакви си 4-5 символа отгоре, специално в нашия курс няма да се оплакваме.
Като цяло имаш много чист стил и кодът се чете сам. Браво!
|
f | 1 | DEFINED_TONES = ( | f | 1 | DEFINED_TONES = ( |
2 | "C", "C#", "D", "D#", "E", "F", | 2 | "C", "C#", "D", "D#", "E", "F", | ||
3 | "F#", "G", "G#", "A", "A#", "B" | 3 | "F#", "G", "G#", "A", "A#", "B" | ||
4 | ) | 4 | ) | ||
5 | DEFINED_INTERVALS = ( | 5 | DEFINED_INTERVALS = ( | ||
6 | "unison", "minor 2nd", "major 2nd", | 6 | "unison", "minor 2nd", "major 2nd", | ||
7 | "minor 3rd", "major 3rd", "perfect 4th", | 7 | "minor 3rd", "major 3rd", "perfect 4th", | ||
8 | "diminished 5th", "perfect 5th", "minor 6th", | 8 | "diminished 5th", "perfect 5th", "minor 6th", | ||
9 | "major 6th", "minor 7th", "major 7th" | 9 | "major 6th", "minor 7th", "major 7th" | ||
10 | ) | 10 | ) | ||
11 | TOTAL_TONES_COUNT = 12 | 11 | TOTAL_TONES_COUNT = 12 | ||
12 | 12 | ||||
13 | 13 | ||||
14 | class Tone: | 14 | class Tone: | ||
15 | 15 | ||||
16 | def __init__(self, note): | 16 | def __init__(self, note): | ||
17 | self.note = note | 17 | self.note = note | ||
18 | 18 | ||||
19 | def __str__(self): | 19 | def __str__(self): | ||
20 | return self.note | 20 | return self.note | ||
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 | note_idx = (DEFINED_TONES.index(self.note) | 26 | note_idx = (DEFINED_TONES.index(self.note) | ||
27 | + other.number_of_semitones) | 27 | + other.number_of_semitones) | ||
28 | return Tone(DEFINED_TONES[note_idx % TOTAL_TONES_COUNT]) | 28 | return Tone(DEFINED_TONES[note_idx % TOTAL_TONES_COUNT]) | ||
29 | 29 | ||||
30 | def __sub__(self, other): | 30 | def __sub__(self, other): | ||
31 | if isinstance(other, Tone): | 31 | if isinstance(other, Tone): | ||
32 | return Interval(DEFINED_TONES.index(self.note) | 32 | return Interval(DEFINED_TONES.index(self.note) | ||
33 | - DEFINED_TONES.index(other.note)) | 33 | - DEFINED_TONES.index(other.note)) | ||
34 | elif isinstance(other, Interval): | 34 | elif isinstance(other, Interval): | ||
35 | note_idx = (DEFINED_TONES.index(self.note) | 35 | note_idx = (DEFINED_TONES.index(self.note) | ||
36 | - other.number_of_semitones) | 36 | - other.number_of_semitones) | ||
37 | return Tone(DEFINED_TONES[note_idx % TOTAL_TONES_COUNT]) | 37 | return Tone(DEFINED_TONES[note_idx % TOTAL_TONES_COUNT]) | ||
38 | 38 | ||||
39 | def __eq__(self, value): | 39 | def __eq__(self, value): | ||
40 | return self.note == value.note | 40 | return self.note == value.note | ||
41 | 41 | ||||
42 | 42 | ||||
43 | class Interval: | 43 | class Interval: | ||
44 | 44 | ||||
45 | def __init__(self, number_of_semitones): | 45 | def __init__(self, number_of_semitones): | ||
46 | self.number_of_semitones = number_of_semitones % TOTAL_TONES_COUNT | 46 | self.number_of_semitones = number_of_semitones % TOTAL_TONES_COUNT | ||
47 | self.semitone = DEFINED_INTERVALS[self.number_of_semitones] | 47 | self.semitone = DEFINED_INTERVALS[self.number_of_semitones] | ||
48 | 48 | ||||
49 | def __str__(self): | 49 | def __str__(self): | ||
50 | return self.semitone | 50 | return self.semitone | ||
51 | 51 | ||||
52 | def __add__(self, other): | 52 | def __add__(self, other): | ||
53 | if isinstance(other, Interval): | 53 | if isinstance(other, Interval): | ||
54 | return Interval(self.number_of_semitones | 54 | return Interval(self.number_of_semitones | ||
55 | + other.number_of_semitones) | 55 | + other.number_of_semitones) | ||
56 | elif isinstance(other, Tone): | 56 | elif isinstance(other, Tone): | ||
57 | raise TypeError("Invalid operation") | 57 | raise TypeError("Invalid operation") | ||
58 | 58 | ||||
59 | def __sub__(self, other): | 59 | def __sub__(self, other): | ||
60 | if isinstance(other, Tone): | 60 | if isinstance(other, Tone): | ||
61 | raise TypeError("Invalid operation") | 61 | raise TypeError("Invalid operation") | ||
62 | 62 | ||||
63 | def __neg__(self): | 63 | def __neg__(self): | ||
64 | return Interval(-self.number_of_semitones) | 64 | return Interval(-self.number_of_semitones) | ||
65 | 65 | ||||
66 | 66 | ||||
67 | class Chord: | 67 | class Chord: | ||
68 | 68 | ||||
n | 69 | min_tones_count = 2 | n | ||
70 | |||||
71 | def __init__(self, root, *tones): | 69 | def __init__(self, root, *tones): | ||
72 | self.root = root | 70 | self.root = root | ||
73 | self.secondary_tones = [] | 71 | self.secondary_tones = [] | ||
74 | for tone in tones: | 72 | for tone in tones: | ||
75 | if tone not in self.secondary_tones: | 73 | if tone not in self.secondary_tones: | ||
76 | self.secondary_tones.append(tone) | 74 | self.secondary_tones.append(tone) | ||
77 | if root in self.secondary_tones: | 75 | if root in self.secondary_tones: | ||
78 | self.secondary_tones.remove(root) | 76 | self.secondary_tones.remove(root) | ||
79 | if not self.secondary_tones: | 77 | if not self.secondary_tones: | ||
80 | raise TypeError("Cannot have a chord made of only 1 unique tone") | 78 | raise TypeError("Cannot have a chord made of only 1 unique tone") | ||
81 | self.secondary_tones.sort(key=lambda x: DEFINED_TONES.index(x.note)) | 79 | self.secondary_tones.sort(key=lambda x: DEFINED_TONES.index(x.note)) | ||
82 | 80 | ||||
83 | def __str__(self): | 81 | def __str__(self): | ||
84 | tones_concat = "-".join(tone.note for tone in self.secondary_tones) | 82 | tones_concat = "-".join(tone.note for tone in self.secondary_tones) | ||
85 | return f"{str(self.root)}-{tones_concat}" | 83 | return f"{str(self.root)}-{tones_concat}" | ||
86 | 84 | ||||
87 | def __add__(self, other): | 85 | def __add__(self, other): | ||
88 | if isinstance(other, Tone): | 86 | if isinstance(other, Tone): | ||
89 | return Chord(self.root, | 87 | return Chord(self.root, | ||
n | 90 | *self.secondary_tones, | n | 88 | *self.secondary_tones, |
91 | other) | 89 | other) | ||
92 | elif isinstance(other, Chord): | 90 | elif isinstance(other, Chord): | ||
93 | return Chord(self.root, | 91 | return Chord(self.root, | ||
n | 94 | *self.secondary_tones, | n | 92 | *self.secondary_tones, |
95 | other.root, | 93 | other.root, | ||
96 | *other.secondary_tones) | 94 | *other.secondary_tones) | ||
97 | 95 | ||||
98 | def __sub__(self, other): | 96 | def __sub__(self, other): | ||
99 | if not isinstance(other, Tone): | 97 | if not isinstance(other, Tone): | ||
100 | return | 98 | return | ||
101 | if (other not in self.secondary_tones and | 99 | if (other not in self.secondary_tones and | ||
102 | not (other == self.root)): | 100 | not (other == self.root)): | ||
103 | raise TypeError( | 101 | raise TypeError( | ||
104 | f"Cannot remove tone {str(other)} from chord {str(self)}" | 102 | f"Cannot remove tone {str(other)} from chord {str(self)}" | ||
105 | ) | 103 | ) | ||
n | 106 | if (len(self.secondary_tones) < Chord.min_tones_count): | n | ||
107 | raise TypeError("Cannot have a chord made of only 1 unique tone") | ||||
108 | if other == self.root: | 104 | if other == self.root: | ||
109 | return Chord(self.secondary_tones[0], | 105 | return Chord(self.secondary_tones[0], | ||
n | 110 | *self.secondary_tones[1:]) | n | 106 | *self.secondary_tones[1:]) |
111 | else: | 107 | else: | ||
112 | modified_secondary_tones = self.secondary_tones[:] | 108 | modified_secondary_tones = self.secondary_tones[:] | ||
113 | modified_secondary_tones.remove(other) | 109 | modified_secondary_tones.remove(other) | ||
114 | return Chord(self.root, | 110 | return Chord(self.root, | ||
t | 115 | *modified_secondary_tones) | t | 111 | *modified_secondary_tones) |
116 | 112 | ||||
117 | def transposed(self, interval): | 113 | def transposed(self, interval): | ||
118 | modified_secondary_tones = [ | 114 | modified_secondary_tones = [ | ||
119 | (tone + interval) for tone in self.secondary_tones | 115 | (tone + interval) for tone in self.secondary_tones | ||
120 | ] | 116 | ] | ||
121 | return Chord(self.root + interval, *modified_secondary_tones) | 117 | return Chord(self.root + interval, *modified_secondary_tones) | ||
122 | 118 | ||||
123 | def is_minor(self): | 119 | def is_minor(self): | ||
124 | root_idx = DEFINED_TONES.index(self.root.note) | 120 | root_idx = DEFINED_TONES.index(self.root.note) | ||
125 | minor_3rd_diff = DEFINED_INTERVALS.index("minor 3rd") | 121 | minor_3rd_diff = DEFINED_INTERVALS.index("minor 3rd") | ||
126 | for tone in self.secondary_tones: | 122 | for tone in self.secondary_tones: | ||
127 | tone_idx = DEFINED_TONES.index(tone.note) | 123 | tone_idx = DEFINED_TONES.index(tone.note) | ||
128 | if abs(root_idx - tone_idx) == minor_3rd_diff: | 124 | if abs(root_idx - tone_idx) == minor_3rd_diff: | ||
129 | return True | 125 | return True | ||
130 | return False | 126 | return False | ||
131 | 127 | ||||
132 | def is_major(self): | 128 | def is_major(self): | ||
133 | root_idx = DEFINED_TONES.index(self.root.note) | 129 | root_idx = DEFINED_TONES.index(self.root.note) | ||
134 | major_3rd_diff = DEFINED_INTERVALS.index("major 3rd") | 130 | major_3rd_diff = DEFINED_INTERVALS.index("major 3rd") | ||
135 | for tone in self.secondary_tones: | 131 | for tone in self.secondary_tones: | ||
136 | tone_idx = DEFINED_TONES.index(tone.note) | 132 | tone_idx = DEFINED_TONES.index(tone.note) | ||
137 | if abs(root_idx - tone_idx) == major_3rd_diff: | 133 | if abs(root_idx - tone_idx) == major_3rd_diff: | ||
138 | return True | 134 | return True | ||
139 | return False | 135 | return False | ||
140 | 136 | ||||
141 | def is_power_chord(self): | 137 | def is_power_chord(self): | ||
142 | return not (self.is_major() or self.is_minor()) | 138 | return not (self.is_major() or self.is_minor()) |
Legends | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
|