1TONES = ('C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B')
2INTERVALS = ('unison', 'minor 2nd', 'major 2nd', 'minor 3rd', 'major 3rd', 'perfect 4th', 'diminished 5th', 'perfect 5th', 'minor 6th', 'major 6th', 'minor 7th', 'major 7th')
3SEMITONES = 12
4
5class Tone:
6 def __init__(self, tone):
7 self.tone = tone
8
9 def __str__(self):
10 return str(self.tone)
11
12 def __add__(self, other):
13 if isinstance(other, Tone):
14 return Chord(self, other)
15 elif isinstance(other, Interval):
16 semitones = other.semitones % SEMITONES
17 self_index = TONES.index(str(self))
18 self_index += semitones
19 return Tone(TONES[self_index % SEMITONES])
20 return NotImplemented
21
22 def __sub__(self, other):
23 self_index = TONES.index(str(self))
24 if isinstance(other, Tone):
25 other_index = TONES.index(str(other))
26 return Interval(abs(self_index - other_index))
27 elif isinstance(other, Interval):
28 semitones = other.semitones % SEMITONES
29 self_index -= semitones
30 return Tone(TONES[self_index % SEMITONES])
31 return NotImplemented
32
33 def __eq__(self, other):
34 if isinstance(other, Tone):
35 return self.tone == other.tone
36 return False
37
38 #ne znam kakvo tochno oznachava no mi izleze greshka che Tone trqbva da e hashable
39 def __hash__(self):
40 return hash(self.tone)
41
42
43class Interval:
44 def __init__(self, semitones):
45 self.semitones = semitones
46
47 def __str__(self):
48 return INTERVALS[self.semitones % SEMITONES]
49
50 def __add__(self, other):
51 if isinstance(other, Tone):
52 raise TypeError('Invalid operation')
53 elif isinstance(other, Interval):
54 return Interval((self.semitones + other.semitones) % SEMITONES)
55
56 def __sub__(self, other):
57 if isinstance(other, Tone):
58 raise TypeError('Invalid operation')
59
60 def __neg__(self):
61 return Interval(-self.semitones)
62
63
64class Chord:
65 def __init__(self, *args):
66 tones = list(set(args))
67 if len(tones) == 1:
68 raise TypeError('Cannot have a chord made of only 1 unique tone')
69 else:
70 self.root = args[0]
71 self.tones = tones
72
73 def __str__(self):
74 output = str(self.root.tone)
75 root_index = TONES.index(str(self.root.tone))
76 tone_names = {str(tone) for tone in self.tones}
77 for tone in TONES[root_index + 1:]:
78 if tone in tone_names:
79 output += '-' + tone
80 for tone in TONES[:root_index]:
81 if tone in tone_names:
82 output += '-' + tone
83 return output
84
85 def __add__(self, other):
86 if isinstance(other, Tone):
87 return Chord(self.root, *self.tones, other)
88 elif isinstance(other, Chord):
89 return Chord(*self.tones, *other.tones)
90 return NotImplemented
91
92 def __sub__(self, other):
93 if isinstance(other, Tone):
94 if other in self.tones:
95 other_index = self.tones.index(other)
96 return Chord(*self.tones[:other_index], *self.tones[other_index + 1:])
97 else:
98 raise TypeError(f'Cannot remove tone {other} from chord {self}')
99 return NotImplemented
100
101 def is_minor(self):
102 for tone in self.tones:
103 interval = self.root - tone
104 if str(interval) == 'minor 3rd':
105 return True
106 return False
107
108 def is_major(self):
109 for tone in self.tones:
110 interval = self.root - tone
111 if str(interval) == 'major 3rd':
112 return True
113 return False
114
115 def is_power_chord(self):
116 return not (self.is_minor() or self.is_major())
117
118 def transposed(self, interval):
119 new_tones = []
120 new_tones.append(self.root + interval)
121 for tone in self.tones:
122 new_tones.append(tone + interval)
123 return Chord(*new_tones)
....FFF.......................F....F.
======================================================================
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_subtract_tone_from_chord (test.TestOperations.test_subtract_tone_from_chord)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 277, in test_subtract_tone_from_chord
self.assertEqual(str(result_chord), "F-G#")
AssertionError: 'G#-F' != 'F-G#'
- G#-F
+ F-G#
======================================================================
FAIL: test_tone_subtraction_inverse (test.TestOperations.test_tone_subtraction_inverse)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 178, in test_tone_subtraction_inverse
self.assertEqual(str(perfect_4th), "perfect 4th")
AssertionError: 'perfect 5th' != 'perfect 4th'
- perfect 5th
? ^
+ perfect 4th
? ^
----------------------------------------------------------------------
Ran 37 tests in 0.002s
FAILED (failures=5)
n | 1 | TONES = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'] | n | 1 | TONES = ('C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B') |
2 | INTERVALS = ['unison', 'minor 2nd', 'major 2nd', 'minor 3rd', 'major 3rd', 'perfect 4th', 'diminished 5th', 'perfect 5th', 'minor 6th', 'major 6th', 'minor 7th', 'major 7th'] | 2 | INTERVALS = ('unison', 'minor 2nd', 'major 2nd', 'minor 3rd', 'major 3rd', 'perfect 4th', 'diminished 5th', 'perfect 5th', 'minor 6th', 'major 6th', 'minor 7th', 'major 7th') | ||
3 | 3 | SEMITONES = 12 | |||
4 | 4 | ||||
5 | class Tone: | 5 | class Tone: | ||
6 | def __init__(self, tone): | 6 | def __init__(self, tone): | ||
7 | self.tone = tone | 7 | self.tone = tone | ||
8 | 8 | ||||
9 | def __str__(self): | 9 | def __str__(self): | ||
10 | return str(self.tone) | 10 | return str(self.tone) | ||
11 | 11 | ||||
12 | def __add__(self, other): | 12 | def __add__(self, other): | ||
13 | if isinstance(other, Tone): | 13 | if isinstance(other, Tone): | ||
14 | return Chord(self, other) | 14 | return Chord(self, other) | ||
15 | elif isinstance(other, Interval): | 15 | elif isinstance(other, Interval): | ||
n | 16 | semitones = other.semitones % 12 | n | 16 | semitones = other.semitones % SEMITONES |
17 | self_index = TONES.index(str(self)) | 17 | self_index = TONES.index(str(self)) | ||
18 | self_index += semitones | 18 | self_index += semitones | ||
n | 19 | return Tone(TONES[self_index % 12]) | n | 19 | return Tone(TONES[self_index % SEMITONES]) |
20 | return NotImplemented | 20 | return NotImplemented | ||
21 | 21 | ||||
22 | def __sub__(self, other): | 22 | def __sub__(self, other): | ||
23 | self_index = TONES.index(str(self)) | 23 | self_index = TONES.index(str(self)) | ||
24 | if isinstance(other, Tone): | 24 | if isinstance(other, Tone): | ||
25 | other_index = TONES.index(str(other)) | 25 | other_index = TONES.index(str(other)) | ||
26 | return Interval(abs(self_index - other_index)) | 26 | return Interval(abs(self_index - other_index)) | ||
27 | elif isinstance(other, Interval): | 27 | elif isinstance(other, Interval): | ||
n | 28 | semitones = other.semitones % 12 | n | 28 | semitones = other.semitones % SEMITONES |
29 | self_index -= semitones | 29 | self_index -= semitones | ||
n | 30 | return Tone(TONES[self_index % 12]) | n | 30 | return Tone(TONES[self_index % SEMITONES]) |
31 | return NotImplemented | 31 | return NotImplemented | ||
32 | 32 | ||||
33 | def __eq__(self, other): | 33 | def __eq__(self, other): | ||
34 | if isinstance(other, Tone): | 34 | if isinstance(other, Tone): | ||
35 | return self.tone == other.tone | 35 | return self.tone == other.tone | ||
36 | return False | 36 | return False | ||
37 | 37 | ||||
38 | #ne znam kakvo tochno oznachava no mi izleze greshka che Tone trqbva da e hashable | 38 | #ne znam kakvo tochno oznachava no mi izleze greshka che Tone trqbva da e hashable | ||
39 | def __hash__(self): | 39 | def __hash__(self): | ||
40 | return hash(self.tone) | 40 | return hash(self.tone) | ||
41 | 41 | ||||
42 | 42 | ||||
43 | class Interval: | 43 | class Interval: | ||
44 | def __init__(self, semitones): | 44 | def __init__(self, semitones): | ||
45 | self.semitones = semitones | 45 | self.semitones = semitones | ||
46 | 46 | ||||
47 | def __str__(self): | 47 | def __str__(self): | ||
n | 48 | return INTERVALS[self.semitones % 12] | n | 48 | return INTERVALS[self.semitones % SEMITONES] |
49 | 49 | ||||
50 | def __add__(self, other): | 50 | def __add__(self, other): | ||
51 | if isinstance(other, Tone): | 51 | if isinstance(other, Tone): | ||
52 | raise TypeError('Invalid operation') | 52 | raise TypeError('Invalid operation') | ||
53 | elif isinstance(other, Interval): | 53 | elif isinstance(other, Interval): | ||
n | 54 | return Interval((self.semitones + other.semitones) % 12) | n | 54 | return Interval((self.semitones + other.semitones) % SEMITONES) |
55 | 55 | ||||
56 | def __sub__(self, other): | 56 | def __sub__(self, other): | ||
57 | if isinstance(other, Tone): | 57 | if isinstance(other, Tone): | ||
58 | raise TypeError('Invalid operation') | 58 | raise TypeError('Invalid operation') | ||
59 | 59 | ||||
60 | def __neg__(self): | 60 | def __neg__(self): | ||
61 | return Interval(-self.semitones) | 61 | return Interval(-self.semitones) | ||
62 | 62 | ||||
63 | 63 | ||||
64 | class Chord: | 64 | class Chord: | ||
65 | def __init__(self, *args): | 65 | def __init__(self, *args): | ||
66 | tones = list(set(args)) | 66 | tones = list(set(args)) | ||
67 | if len(tones) == 1: | 67 | if len(tones) == 1: | ||
68 | raise TypeError('Cannot have a chord made of only 1 unique tone') | 68 | raise TypeError('Cannot have a chord made of only 1 unique tone') | ||
69 | else: | 69 | else: | ||
70 | self.root = args[0] | 70 | self.root = args[0] | ||
71 | self.tones = tones | 71 | self.tones = tones | ||
72 | 72 | ||||
73 | def __str__(self): | 73 | def __str__(self): | ||
74 | output = str(self.root.tone) | 74 | output = str(self.root.tone) | ||
75 | root_index = TONES.index(str(self.root.tone)) | 75 | root_index = TONES.index(str(self.root.tone)) | ||
76 | tone_names = {str(tone) for tone in self.tones} | 76 | tone_names = {str(tone) for tone in self.tones} | ||
77 | for tone in TONES[root_index + 1:]: | 77 | for tone in TONES[root_index + 1:]: | ||
78 | if tone in tone_names: | 78 | if tone in tone_names: | ||
79 | output += '-' + tone | 79 | output += '-' + tone | ||
80 | for tone in TONES[:root_index]: | 80 | for tone in TONES[:root_index]: | ||
81 | if tone in tone_names: | 81 | if tone in tone_names: | ||
82 | output += '-' + tone | 82 | output += '-' + tone | ||
83 | return output | 83 | return output | ||
84 | 84 | ||||
85 | def __add__(self, other): | 85 | def __add__(self, other): | ||
86 | if isinstance(other, Tone): | 86 | if isinstance(other, Tone): | ||
87 | return Chord(self.root, *self.tones, other) | 87 | return Chord(self.root, *self.tones, other) | ||
88 | elif isinstance(other, Chord): | 88 | elif isinstance(other, Chord): | ||
89 | return Chord(*self.tones, *other.tones) | 89 | return Chord(*self.tones, *other.tones) | ||
90 | return NotImplemented | 90 | return NotImplemented | ||
91 | 91 | ||||
92 | def __sub__(self, other): | 92 | def __sub__(self, other): | ||
93 | if isinstance(other, Tone): | 93 | if isinstance(other, Tone): | ||
94 | if other in self.tones: | 94 | if other in self.tones: | ||
n | 95 | if len(self.tones) < 3: | n | ||
96 | raise TypeError('Cannot have a chord made of only 1 unique tone') | ||||
97 | else: | ||||
98 | other_index = self.tones.index(other) | 95 | other_index = self.tones.index(other) | ||
99 | return Chord(*self.tones[:other_index], *self.tones[other_index + 1:]) | 96 | return Chord(*self.tones[:other_index], *self.tones[other_index + 1:]) | ||
100 | else: | 97 | else: | ||
101 | raise TypeError(f'Cannot remove tone {other} from chord {self}') | 98 | raise TypeError(f'Cannot remove tone {other} from chord {self}') | ||
102 | return NotImplemented | 99 | return NotImplemented | ||
103 | 100 | ||||
104 | def is_minor(self): | 101 | def is_minor(self): | ||
105 | for tone in self.tones: | 102 | for tone in self.tones: | ||
106 | interval = self.root - tone | 103 | interval = self.root - tone | ||
107 | if str(interval) == 'minor 3rd': | 104 | if str(interval) == 'minor 3rd': | ||
108 | return True | 105 | return True | ||
109 | return False | 106 | return False | ||
110 | 107 | ||||
111 | def is_major(self): | 108 | def is_major(self): | ||
112 | for tone in self.tones: | 109 | for tone in self.tones: | ||
113 | interval = self.root - tone | 110 | interval = self.root - tone | ||
114 | if str(interval) == 'major 3rd': | 111 | if str(interval) == 'major 3rd': | ||
115 | return True | 112 | return True | ||
116 | return False | 113 | return False | ||
117 | 114 | ||||
118 | def is_power_chord(self): | 115 | def is_power_chord(self): | ||
119 | return not (self.is_minor() or self.is_major()) | 116 | return not (self.is_minor() or self.is_major()) | ||
120 | 117 | ||||
121 | def transposed(self, interval): | 118 | def transposed(self, interval): | ||
t | 122 | new_tones = list() | t | 119 | new_tones = [] |
123 | new_tones.append(self.root + interval) | 120 | new_tones.append(self.root + interval) | ||
124 | for tone in self.tones: | 121 | for tone in self.tones: | ||
125 | new_tones.append(tone + interval) | 122 | new_tones.append(tone + interval) | ||
126 | return Chord(*new_tones) | 123 | return Chord(*new_tones) |
Legends | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
|