1CHORD_ERROR_MESSAGE = "Cannot have a chord made of only 1 unique tone"
  2INVALID_OPERATION_MESSAGE = "Invalid operation"
  3TONES = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]
  4INTERVALS = ["unison", "minor 2nd", "major 2nd", "minor 3rd", "major 3rd", "perfect 4th", "diminished 5th", "perfect 5th", "minor 6th", "major 6th", "minor 7th", "major 7th"]
  5
  6class Tone:
  7    def __init__(self, tone):
  8        self.tone = tone
  9
 10    def __str__(self):
 11        return str(self.tone)
 12    
 13    def __iter__(self):
 14        return iter(self.tone)
 15
 16    def __add__(self, other):
 17        if isinstance(other, Tone):
 18            return Chord(self, other)
 19        if isinstance(other, Interval): 
 20            return Tone(TONES[(TONES.index(str(self)) + other.num_of_semitones) % len(TONES)])
 21
 22    def __sub__(self, other):
 23        if isinstance(other, Tone):
 24            return Interval(abs(TONES.index(str(self)) - TONES.index(str(other))))
 25        if isinstance(other, Interval): 
 26            return Tone(TONES[(TONES.index(str(self)) - other.num_of_semitones) % len(TONES)])
 27        
 28
 29class Interval:
 30    def __init__(self, num_of_tones):
 31        self.is_positive = num_of_tones >= 0
 32        num_of_tones = abs(num_of_tones)
 33        self.num_of_semitones = num_of_tones % len(INTERVALS)
 34    
 35    def __str__(self):
 36        return str(INTERVALS[self.num_of_semitones])
 37    
 38    def __int__(self):
 39        return self.num_of_semitones
 40    
 41    def __add__(self, other):
 42        if isinstance(other, Tone):
 43            raise TypeError(INVALID_OPERATION_MESSAGE)
 44        if isinstance(other, Interval):
 45            return Interval(int(self) + int(other))
 46
 47    def __sub__(self, other):
 48        if isinstance(other, Tone):
 49            raise TypeError(INVALID_OPERATION_MESSAGE)
 50        
 51    def __neg__(self):
 52        return Interval(-self.num_of_semitones)
 53
 54class Chord:
 55    def __init__(self, *tones):
 56        self.tones = []
 57        for tone in tones:
 58            if str(tone) not in self.tones:
 59                self.tones.append(str(tone))
 60        self.root = self.tones[0]
 61        if len(self.tones) == 1:
 62            raise TypeError(CHORD_ERROR_MESSAGE)
 63        tone_index = TONES.index(str(self.root))
 64        self.scale = TONES[tone_index:] + TONES[:tone_index]
 65        self.tones = sorted(self.tones, key=lambda tone: self.scale.index(str(tone))) 
 66        
 67    def __str__(self):
 68        return '-'.join(map(str, self.tones))
 69
 70    def is_major(self):
 71        for tone in self.tones:
 72            if abs(TONES.index(str(self.root)) - TONES.index(str(tone))) == INTERVALS.index("major 3rd"):
 73                return True
 74        return False
 75    
 76    def is_minor(self):
 77        for tone in self.tones:
 78            if abs(TONES.index(str(self.root)) - TONES.index(str(tone))) == INTERVALS.index("minor 3rd"):
 79                return True
 80        return False
 81    
 82    def is_power_chord(self):
 83        if self.is_minor() or self.is_major():
 84            return False
 85        return True
 86    
 87    def __add__(self, other):
 88        if isinstance(other, Tone):
 89            new_tones = self.tones + [other]
 90        if isinstance(other, Chord):
 91            new_tones = self.tones + other.tones
 92        return Chord(*new_tones)
 93
 94    def __sub__(self, other):
 95        if isinstance(other, Tone):
 96            if str(other) not in self.tones:
 97                raise TypeError(f"Cannot remove tone {other} from chord {str(self)}")
 98            if len(self.tones) < 3:
 99                raise TypeError("Cannot have a chord made of only 1 unique tone")
100            to_return = Chord(*self.tones)
101            if str(other) == str(to_return.root):
102                to_return.root = to_return.tones[1]
103                del to_return.tones[0]
104            else:
105                other_index = to_return.tones.index(str(other))
106                del to_return.tones[other_index]
107            return to_return
108
109    def transposed(self, interval):
110        if isinstance(interval, Interval):
111            new_tones = []
112            direction = 1 if interval.is_positive else -1
113            for tone in self.tones:
114                new_index = self.scale.index(str(tone)) + direction*int(interval)
115                new_tones.append(str(self.scale[new_index]))
116            return Chord(*new_tones)
....FFF...EF.......................F.
======================================================================
ERROR: test_transposed_overflow (test.TestBasicChordFunctionality.test_transposed_overflow)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/tmp/test.py", line 143, in test_transposed_overflow
    result_chord = d_major_chord.transposed(Interval(8))
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/tmp/solution.py", line 115, in transposed
    new_tones.append(str(self.scale[new_index]))
                         ~~~~~~~~~~^^^^^^^^^^^
IndexError: list index out of range
======================================================================
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_interval_negative (test.TestBasicIntervalFunctionality.test_interval_negative)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/tmp/test.py", line 51, in test_interval_negative
    self.assertEqual(str(minor_2nd), "minor 2nd")
AssertionError: 'major 7th' != 'minor 2nd'
- major 7th
+ minor 2nd
======================================================================
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.003s
FAILED (failures=5, errors=1)
| f | 1 | CHORD_ERROR_MESSAGE = "Cannot have a chord made of only 1 unique tone" | f | 1 | CHORD_ERROR_MESSAGE = "Cannot have a chord made of only 1 unique tone" | 
| 2 | INVALID_OPERATION_MESSAGE = "Invalid operation" | 2 | INVALID_OPERATION_MESSAGE = "Invalid operation" | ||
| 3 | TONES = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"] | 3 | TONES = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"] | ||
| 4 | INTERVALS = ["unison", "minor 2nd", "major 2nd", "minor 3rd", "major 3rd", "perfect 4th", "diminished 5th", "perfect 5th", "minor 6th", "major 6th", "minor 7th", "major 7th"] | 4 | INTERVALS = ["unison", "minor 2nd", "major 2nd", "minor 3rd", "major 3rd", "perfect 4th", "diminished 5th", "perfect 5th", "minor 6th", "major 6th", "minor 7th", "major 7th"] | ||
| 5 | 5 | ||||
| 6 | class Tone: | 6 | class Tone: | ||
| 7 | def __init__(self, tone): | 7 | def __init__(self, tone): | ||
| 8 | self.tone = tone | 8 | self.tone = tone | ||
| 9 | 9 | ||||
| 10 | def __str__(self): | 10 | def __str__(self): | ||
| 11 | return str(self.tone) | 11 | return str(self.tone) | ||
| 12 | 12 | ||||
| 13 | def __iter__(self): | 13 | def __iter__(self): | ||
| 14 | return iter(self.tone) | 14 | return iter(self.tone) | ||
| 15 | 15 | ||||
| 16 | def __add__(self, other): | 16 | def __add__(self, other): | ||
| 17 | if isinstance(other, Tone): | 17 | if isinstance(other, Tone): | ||
| 18 | return Chord(self, other) | 18 | return Chord(self, other) | ||
| 19 | if isinstance(other, Interval): | 19 | if isinstance(other, Interval): | ||
| n | 20 | return Tone(TONES[(TONES.index(str(self)) + other.num_of_semitones)%len(TONES)]) | n | 20 | return Tone(TONES[(TONES.index(str(self)) + other.num_of_semitones) % len(TONES)]) | 
| 21 | 21 | ||||
| 22 | def __sub__(self, other): | 22 | def __sub__(self, other): | ||
| 23 | if isinstance(other, Tone): | 23 | if isinstance(other, Tone): | ||
| n | 24 | return Interval(abs(TONES.index(str(self))-TONES.index(str(other)))) | n | 24 | return Interval(abs(TONES.index(str(self)) - TONES.index(str(other)))) | 
| 25 | if isinstance(other, Interval): | 25 | if isinstance(other, Interval): | ||
| n | 26 | return Tone(TONES[(TONES.index(str(self)) - other.num_of_semitones)%len(TONES)]) | n | 26 | return Tone(TONES[(TONES.index(str(self)) - other.num_of_semitones) % len(TONES)]) | 
| 27 | 27 | ||||
| 28 | 28 | ||||
| 29 | class Interval: | 29 | class Interval: | ||
| 30 | def __init__(self, num_of_tones): | 30 | def __init__(self, num_of_tones): | ||
| n | 31 | if num_of_tones >= 0: | n | 31 | self.is_positive = num_of_tones >= 0 | 
| 32 | self.is_positive = True | ||||
| 33 | else: | ||||
| 34 | self.is_positive = False | ||||
| 35 | num_of_tones = abs(num_of_tones) | 32 | num_of_tones = abs(num_of_tones) | ||
| 36 | self.num_of_semitones = num_of_tones % len(INTERVALS) | 33 | self.num_of_semitones = num_of_tones % len(INTERVALS) | ||
| 37 | 34 | ||||
| 38 | def __str__(self): | 35 | def __str__(self): | ||
| 39 | return str(INTERVALS[self.num_of_semitones]) | 36 | return str(INTERVALS[self.num_of_semitones]) | ||
| 40 | 37 | ||||
| 41 | def __int__(self): | 38 | def __int__(self): | ||
| 42 | return self.num_of_semitones | 39 | return self.num_of_semitones | ||
| 43 | 40 | ||||
| 44 | def __add__(self, other): | 41 | def __add__(self, other): | ||
| 45 | if isinstance(other, Tone): | 42 | if isinstance(other, Tone): | ||
| 46 | raise TypeError(INVALID_OPERATION_MESSAGE) | 43 | raise TypeError(INVALID_OPERATION_MESSAGE) | ||
| 47 | if isinstance(other, Interval): | 44 | if isinstance(other, Interval): | ||
| 48 | return Interval(int(self) + int(other)) | 45 | return Interval(int(self) + int(other)) | ||
| 49 | 46 | ||||
| 50 | def __sub__(self, other): | 47 | def __sub__(self, other): | ||
| 51 | if isinstance(other, Tone): | 48 | if isinstance(other, Tone): | ||
| 52 | raise TypeError(INVALID_OPERATION_MESSAGE) | 49 | raise TypeError(INVALID_OPERATION_MESSAGE) | ||
| 53 | 50 | ||||
| 54 | def __neg__(self): | 51 | def __neg__(self): | ||
| n | 55 | return Interval(-1*(self.num_of_semitones)) | n | 52 | return Interval(-self.num_of_semitones) | 
| 56 | 53 | ||||
| 57 | class Chord: | 54 | class Chord: | ||
| 58 | def __init__(self, *tones): | 55 | def __init__(self, *tones): | ||
| 59 | self.tones = [] | 56 | self.tones = [] | ||
| 60 | for tone in tones: | 57 | for tone in tones: | ||
| 61 | if str(tone) not in self.tones: | 58 | if str(tone) not in self.tones: | ||
| 62 | self.tones.append(str(tone)) | 59 | self.tones.append(str(tone)) | ||
| 63 | self.root = self.tones[0] | 60 | self.root = self.tones[0] | ||
| 64 | if len(self.tones) == 1: | 61 | if len(self.tones) == 1: | ||
| 65 | raise TypeError(CHORD_ERROR_MESSAGE) | 62 | raise TypeError(CHORD_ERROR_MESSAGE) | ||
| 66 | tone_index = TONES.index(str(self.root)) | 63 | tone_index = TONES.index(str(self.root)) | ||
| 67 | self.scale = TONES[tone_index:] + TONES[:tone_index] | 64 | self.scale = TONES[tone_index:] + TONES[:tone_index] | ||
| 68 | self.tones = sorted(self.tones, key=lambda tone: self.scale.index(str(tone))) | 65 | self.tones = sorted(self.tones, key=lambda tone: self.scale.index(str(tone))) | ||
| 69 | 66 | ||||
| n | 70 | def sort_tones(self): | n | ||
| 71 | self.tones = sorted(self.tones, key=lambda tone: self.scale.index(str(tone))) | ||||
| 72 | |||||
| 73 | def __str__(self): | 67 | def __str__(self): | ||
| 74 | return '-'.join(map(str, self.tones)) | 68 | return '-'.join(map(str, self.tones)) | ||
| 75 | 69 | ||||
| 76 | def is_major(self): | 70 | def is_major(self): | ||
| 77 | for tone in self.tones: | 71 | for tone in self.tones: | ||
| 78 | if abs(TONES.index(str(self.root)) - TONES.index(str(tone))) == INTERVALS.index("major 3rd"): | 72 | if abs(TONES.index(str(self.root)) - TONES.index(str(tone))) == INTERVALS.index("major 3rd"): | ||
| 79 | return True | 73 | return True | ||
| 80 | return False | 74 | return False | ||
| 81 | 75 | ||||
| 82 | def is_minor(self): | 76 | def is_minor(self): | ||
| 83 | for tone in self.tones: | 77 | for tone in self.tones: | ||
| 84 | if abs(TONES.index(str(self.root)) - TONES.index(str(tone))) == INTERVALS.index("minor 3rd"): | 78 | if abs(TONES.index(str(self.root)) - TONES.index(str(tone))) == INTERVALS.index("minor 3rd"): | ||
| 85 | return True | 79 | return True | ||
| 86 | return False | 80 | return False | ||
| 87 | 81 | ||||
| 88 | def is_power_chord(self): | 82 | def is_power_chord(self): | ||
| 89 | if self.is_minor() or self.is_major(): | 83 | if self.is_minor() or self.is_major(): | ||
| 90 | return False | 84 | return False | ||
| 91 | return True | 85 | return True | ||
| 92 | 86 | ||||
| 93 | def __add__(self, other): | 87 | def __add__(self, other): | ||
| 94 | if isinstance(other, Tone): | 88 | if isinstance(other, Tone): | ||
| 95 | new_tones = self.tones + [other] | 89 | new_tones = self.tones + [other] | ||
| n | 96 | return Chord(*new_tones) | n | ||
| 97 | if isinstance(other, Chord): | 90 | if isinstance(other, Chord): | ||
| 98 | new_tones = self.tones + other.tones | 91 | new_tones = self.tones + other.tones | ||
| n | 99 | return Chord(*new_tones) | n | 92 | return Chord(*new_tones) | 
| 100 | 93 | ||||
| 101 | def __sub__(self, other): | 94 | def __sub__(self, other): | ||
| 102 | if isinstance(other, Tone): | 95 | if isinstance(other, Tone): | ||
| 103 | if str(other) not in self.tones: | 96 | if str(other) not in self.tones: | ||
| 104 | raise TypeError(f"Cannot remove tone {other} from chord {str(self)}") | 97 | raise TypeError(f"Cannot remove tone {other} from chord {str(self)}") | ||
| 105 | if len(self.tones) < 3: | 98 | if len(self.tones) < 3: | ||
| 106 | raise TypeError("Cannot have a chord made of only 1 unique tone") | 99 | raise TypeError("Cannot have a chord made of only 1 unique tone") | ||
| 107 | to_return = Chord(*self.tones) | 100 | to_return = Chord(*self.tones) | ||
| 108 | if str(other) == str(to_return.root): | 101 | if str(other) == str(to_return.root): | ||
| 109 | to_return.root = to_return.tones[1] | 102 | to_return.root = to_return.tones[1] | ||
| 110 | del to_return.tones[0] | 103 | del to_return.tones[0] | ||
| 111 | else: | 104 | else: | ||
| 112 | other_index = to_return.tones.index(str(other)) | 105 | other_index = to_return.tones.index(str(other)) | ||
| 113 | del to_return.tones[other_index] | 106 | del to_return.tones[other_index] | ||
| 114 | return to_return | 107 | return to_return | ||
| 115 | 108 | ||||
| 116 | def transposed(self, interval): | 109 | def transposed(self, interval): | ||
| 117 | if isinstance(interval, Interval): | 110 | if isinstance(interval, Interval): | ||
| 118 | new_tones = [] | 111 | new_tones = [] | ||
| t | 119 | if interval.is_positive: | t | 112 | direction = 1 if interval.is_positive else -1 | 
| 120 | for tone in self.tones: | 113 | for tone in self.tones: | ||
| 121 | new_index = self.scale.index(str(tone)) + int(interval) | 114 | new_index = self.scale.index(str(tone)) + direction*int(interval) | ||
| 122 | new_tones.append(str(self.scale[new_index])) | 115 | new_tones.append(str(self.scale[new_index])) | ||
| 123 | else: | ||||
| 124 | for tone in self.tones: | ||||
| 125 | new_index = self.scale.index(str(tone)) - int(interval) | ||||
| 126 | new_tones.append(str(self.scale[new_index])) | ||||
| 127 | return Chord(*new_tones) | 116 | return Chord(*new_tones) | 
| Legends | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|
 
  | 
              
  |  |||||||||