1CHROMATIC_SCALE = (
  2    'C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'
  3)
  4
  5TONES_COUNT = 12
  6MINOR_THIRD_LEN = 3
  7MAJOR_THIRD_LEN = 4
  8
  9
 10class Tone:
 11
 12    def __init__(self, tone_name):
 13        self.tone_name = tone_name
 14
 15    def __str__(self):
 16        return self.tone_name
 17
 18    def __eq__(self, other):
 19        return self.tone_name == other.tone_name
 20
 21    def __ne__(self, other):
 22        return self.tone_name != other.tone_name
 23
 24    def __add__(self, other):
 25        if isinstance(other, Tone):
 26            return Chord(self,other)
 27        elif isinstance(other, Interval):
 28            new_tone_idx = (CHROMATIC_SCALE.index(self.tone_name) + other.semitones_count) % TONES_COUNT
 29            return Tone(CHROMATIC_SCALE[new_tone_idx])
 30
 31    def __sub__(self, other):
 32        if isinstance(other, Tone):
 33            self_idx = CHROMATIC_SCALE.index(self.tone_name)
 34            other_idx = CHROMATIC_SCALE.index(other.tone_name)
 35            interval_length = (self_idx - other_idx) % TONES_COUNT
 36            return Interval(interval_length)
 37        elif isinstance(other, Interval):
 38            new_tone_idx = (CHROMATIC_SCALE.index(self.tone_name) - other.semitones_count) % TONES_COUNT
 39            return Tone(CHROMATIC_SCALE[new_tone_idx])
 40
 41
 42class Interval:
 43
 44    _INTERVAL_NAMES = (
 45        'unison', 'minor 2nd', 'major 2nd', 'minor 3rd', 'major 3rd',
 46        'perfect 4th', 'diminished 5th', 'perfect 5th', 'minor 6th',
 47        'major 6th', 'minor 7th', 'major 7th'
 48    )
 49
 50    def __init__(self, semitones_count):
 51        self.semitones_count = semitones_count
 52
 53    def __str__(self):
 54        return self._INTERVAL_NAMES[self.semitones_count % TONES_COUNT]
 55
 56    def __add__(self, other):
 57        if isinstance(other, Tone):
 58            raise TypeError('Invalid operation')
 59        elif isinstance(other, Interval):
 60            new_count = (self.semitones_count + other.semitones_count)
 61            return Interval(new_count)
 62
 63    def __sub__(self, other):
 64        if isinstance(other, Tone):
 65            raise TypeError('Invalid operation')
 66
 67    def __neg__(self):
 68        return Interval(-self.semitones_count)
 69
 70
 71class Chord:
 72
 73    def __init__(self, *tones):
 74        if not self._are_valid_tones(*tones):
 75            raise TypeError('Cannot have a chord made of only 1 unique tone')
 76        self.sorted_tones = self._sort_tones(*tones)
 77
 78    @staticmethod
 79    def _are_valid_tones(*tones):
 80        tones_set = {str(tone) for tone in tones}
 81        return len(tones_set) > 1
 82
 83    @staticmethod
 84    def _sort_tones(*tones):
 85        root = tones[0]
 86        sorted_tones = [root]
 87        end_idx = CHROMATIC_SCALE.index(str(root))
 88        iter_idx = (end_idx + 1) % TONES_COUNT
 89
 90        while iter_idx != end_idx:
 91            current_tone = Tone(CHROMATIC_SCALE[iter_idx])
 92            if current_tone in tones and current_tone not in sorted_tones:
 93                sorted_tones.append(current_tone)
 94            iter_idx += 1
 95            iter_idx %= TONES_COUNT
 96        return sorted_tones
 97
 98    def __str__(self):
 99        return '-'.join(str(tone) for tone in self.sorted_tones)
100
101    def is_minor(self):
102        root_idx = CHROMATIC_SCALE.index(str(self.sorted_tones[0]))
103        end_tone_idx = (root_idx + MINOR_THIRD_LEN) % TONES_COUNT
104        return Tone(CHROMATIC_SCALE[end_tone_idx]) in self.sorted_tones
105
106    def is_major(self):
107        root_idx =  CHROMATIC_SCALE.index(str(self.sorted_tones[0]))
108        end_tone_idx = (root_idx + MAJOR_THIRD_LEN) % TONES_COUNT
109        return Tone(CHROMATIC_SCALE[end_tone_idx]) in self.sorted_tones
110
111    def is_power_chord(self):
112        return not self.is_minor() and not self.is_major()
113
114    def __add__(self, other):
115        if isinstance(other, Tone):
116            new_tones = self.sorted_tones + [other]
117            return Chord(*new_tones)
118        elif isinstance(other, Chord):
119            new_tones = self.sorted_tones[:]
120            new_tones.extend(other.sorted_tones)
121            return Chord(*new_tones)
122
123    def __sub__(self, other):
124        if isinstance(other, Tone):
125            if other not in self.sorted_tones:
126                raise TypeError(f'Cannot remove tone {str(other)} from chord {str(self)}')
127            new_tones = self.sorted_tones[:]
128            new_tones.remove(other)
129            return Chord(*new_tones)
130
131    def transposed(self, interval):
132        transpose = lambda tone : tone + interval
133        transposed_tones  = list(map(transpose, self.sorted_tones))
134        return Chord(*transposed_tones)
.....................................
----------------------------------------------------------------------
Ran 37 tests in 0.001s
OK
| t | 1 | CHROMATIC_SCALE = ( | t | 1 | CHROMATIC_SCALE = ( | 
| 2 | 'C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B' | 2 | 'C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B' | ||
| 3 | ) | 3 | ) | ||
| 4 | 4 | ||||
| 5 | TONES_COUNT = 12 | 5 | TONES_COUNT = 12 | ||
| 6 | MINOR_THIRD_LEN = 3 | 6 | MINOR_THIRD_LEN = 3 | ||
| 7 | MAJOR_THIRD_LEN = 4 | 7 | MAJOR_THIRD_LEN = 4 | ||
| 8 | 8 | ||||
| 9 | 9 | ||||
| 10 | class Tone: | 10 | class Tone: | ||
| 11 | 11 | ||||
| 12 | def __init__(self, tone_name): | 12 | def __init__(self, tone_name): | ||
| 13 | self.tone_name = tone_name | 13 | self.tone_name = tone_name | ||
| 14 | 14 | ||||
| 15 | def __str__(self): | 15 | def __str__(self): | ||
| 16 | return self.tone_name | 16 | return self.tone_name | ||
| 17 | 17 | ||||
| 18 | def __eq__(self, other): | 18 | def __eq__(self, other): | ||
| 19 | return self.tone_name == other.tone_name | 19 | return self.tone_name == other.tone_name | ||
| 20 | 20 | ||||
| 21 | def __ne__(self, other): | 21 | def __ne__(self, other): | ||
| 22 | return self.tone_name != other.tone_name | 22 | return self.tone_name != other.tone_name | ||
| 23 | 23 | ||||
| 24 | def __add__(self, other): | 24 | def __add__(self, other): | ||
| 25 | if isinstance(other, Tone): | 25 | if isinstance(other, Tone): | ||
| 26 | return Chord(self,other) | 26 | return Chord(self,other) | ||
| 27 | elif isinstance(other, Interval): | 27 | elif isinstance(other, Interval): | ||
| 28 | new_tone_idx = (CHROMATIC_SCALE.index(self.tone_name) + other.semitones_count) % TONES_COUNT | 28 | new_tone_idx = (CHROMATIC_SCALE.index(self.tone_name) + other.semitones_count) % TONES_COUNT | ||
| 29 | return Tone(CHROMATIC_SCALE[new_tone_idx]) | 29 | return Tone(CHROMATIC_SCALE[new_tone_idx]) | ||
| 30 | 30 | ||||
| 31 | def __sub__(self, other): | 31 | def __sub__(self, other): | ||
| 32 | if isinstance(other, Tone): | 32 | if isinstance(other, Tone): | ||
| 33 | self_idx = CHROMATIC_SCALE.index(self.tone_name) | 33 | self_idx = CHROMATIC_SCALE.index(self.tone_name) | ||
| 34 | other_idx = CHROMATIC_SCALE.index(other.tone_name) | 34 | other_idx = CHROMATIC_SCALE.index(other.tone_name) | ||
| 35 | interval_length = (self_idx - other_idx) % TONES_COUNT | 35 | interval_length = (self_idx - other_idx) % TONES_COUNT | ||
| 36 | return Interval(interval_length) | 36 | return Interval(interval_length) | ||
| 37 | elif isinstance(other, Interval): | 37 | elif isinstance(other, Interval): | ||
| 38 | new_tone_idx = (CHROMATIC_SCALE.index(self.tone_name) - other.semitones_count) % TONES_COUNT | 38 | new_tone_idx = (CHROMATIC_SCALE.index(self.tone_name) - other.semitones_count) % TONES_COUNT | ||
| 39 | return Tone(CHROMATIC_SCALE[new_tone_idx]) | 39 | return Tone(CHROMATIC_SCALE[new_tone_idx]) | ||
| 40 | 40 | ||||
| 41 | 41 | ||||
| 42 | class Interval: | 42 | class Interval: | ||
| 43 | 43 | ||||
| 44 | _INTERVAL_NAMES = ( | 44 | _INTERVAL_NAMES = ( | ||
| 45 | 'unison', 'minor 2nd', 'major 2nd', 'minor 3rd', 'major 3rd', | 45 | 'unison', 'minor 2nd', 'major 2nd', 'minor 3rd', 'major 3rd', | ||
| 46 | 'perfect 4th', 'diminished 5th', 'perfect 5th', 'minor 6th', | 46 | 'perfect 4th', 'diminished 5th', 'perfect 5th', 'minor 6th', | ||
| 47 | 'major 6th', 'minor 7th', 'major 7th' | 47 | 'major 6th', 'minor 7th', 'major 7th' | ||
| 48 | ) | 48 | ) | ||
| 49 | 49 | ||||
| 50 | def __init__(self, semitones_count): | 50 | def __init__(self, semitones_count): | ||
| 51 | self.semitones_count = semitones_count | 51 | self.semitones_count = semitones_count | ||
| 52 | 52 | ||||
| 53 | def __str__(self): | 53 | def __str__(self): | ||
| 54 | return self._INTERVAL_NAMES[self.semitones_count % TONES_COUNT] | 54 | return self._INTERVAL_NAMES[self.semitones_count % TONES_COUNT] | ||
| 55 | 55 | ||||
| 56 | def __add__(self, other): | 56 | def __add__(self, other): | ||
| 57 | if isinstance(other, Tone): | 57 | if isinstance(other, Tone): | ||
| 58 | raise TypeError('Invalid operation') | 58 | raise TypeError('Invalid operation') | ||
| 59 | elif isinstance(other, Interval): | 59 | elif isinstance(other, Interval): | ||
| 60 | new_count = (self.semitones_count + other.semitones_count) | 60 | new_count = (self.semitones_count + other.semitones_count) | ||
| 61 | return Interval(new_count) | 61 | return Interval(new_count) | ||
| 62 | 62 | ||||
| 63 | def __sub__(self, other): | 63 | def __sub__(self, other): | ||
| 64 | if isinstance(other, Tone): | 64 | if isinstance(other, Tone): | ||
| 65 | raise TypeError('Invalid operation') | 65 | raise TypeError('Invalid operation') | ||
| 66 | 66 | ||||
| 67 | def __neg__(self): | 67 | def __neg__(self): | ||
| 68 | return Interval(-self.semitones_count) | 68 | return Interval(-self.semitones_count) | ||
| 69 | 69 | ||||
| 70 | 70 | ||||
| 71 | class Chord: | 71 | class Chord: | ||
| 72 | 72 | ||||
| 73 | def __init__(self, *tones): | 73 | def __init__(self, *tones): | ||
| 74 | if not self._are_valid_tones(*tones): | 74 | if not self._are_valid_tones(*tones): | ||
| 75 | raise TypeError('Cannot have a chord made of only 1 unique tone') | 75 | raise TypeError('Cannot have a chord made of only 1 unique tone') | ||
| 76 | self.sorted_tones = self._sort_tones(*tones) | 76 | self.sorted_tones = self._sort_tones(*tones) | ||
| 77 | 77 | ||||
| 78 | @staticmethod | 78 | @staticmethod | ||
| 79 | def _are_valid_tones(*tones): | 79 | def _are_valid_tones(*tones): | ||
| 80 | tones_set = {str(tone) for tone in tones} | 80 | tones_set = {str(tone) for tone in tones} | ||
| 81 | return len(tones_set) > 1 | 81 | return len(tones_set) > 1 | ||
| 82 | 82 | ||||
| 83 | @staticmethod | 83 | @staticmethod | ||
| 84 | def _sort_tones(*tones): | 84 | def _sort_tones(*tones): | ||
| 85 | root = tones[0] | 85 | root = tones[0] | ||
| 86 | sorted_tones = [root] | 86 | sorted_tones = [root] | ||
| 87 | end_idx = CHROMATIC_SCALE.index(str(root)) | 87 | end_idx = CHROMATIC_SCALE.index(str(root)) | ||
| 88 | iter_idx = (end_idx + 1) % TONES_COUNT | 88 | iter_idx = (end_idx + 1) % TONES_COUNT | ||
| 89 | 89 | ||||
| 90 | while iter_idx != end_idx: | 90 | while iter_idx != end_idx: | ||
| 91 | current_tone = Tone(CHROMATIC_SCALE[iter_idx]) | 91 | current_tone = Tone(CHROMATIC_SCALE[iter_idx]) | ||
| 92 | if current_tone in tones and current_tone not in sorted_tones: | 92 | if current_tone in tones and current_tone not in sorted_tones: | ||
| 93 | sorted_tones.append(current_tone) | 93 | sorted_tones.append(current_tone) | ||
| 94 | iter_idx += 1 | 94 | iter_idx += 1 | ||
| 95 | iter_idx %= TONES_COUNT | 95 | iter_idx %= TONES_COUNT | ||
| 96 | return sorted_tones | 96 | return sorted_tones | ||
| 97 | 97 | ||||
| 98 | def __str__(self): | 98 | def __str__(self): | ||
| 99 | return '-'.join(str(tone) for tone in self.sorted_tones) | 99 | return '-'.join(str(tone) for tone in self.sorted_tones) | ||
| 100 | 100 | ||||
| 101 | def is_minor(self): | 101 | def is_minor(self): | ||
| 102 | root_idx = CHROMATIC_SCALE.index(str(self.sorted_tones[0])) | 102 | root_idx = CHROMATIC_SCALE.index(str(self.sorted_tones[0])) | ||
| 103 | end_tone_idx = (root_idx + MINOR_THIRD_LEN) % TONES_COUNT | 103 | end_tone_idx = (root_idx + MINOR_THIRD_LEN) % TONES_COUNT | ||
| 104 | return Tone(CHROMATIC_SCALE[end_tone_idx]) in self.sorted_tones | 104 | return Tone(CHROMATIC_SCALE[end_tone_idx]) in self.sorted_tones | ||
| 105 | 105 | ||||
| 106 | def is_major(self): | 106 | def is_major(self): | ||
| 107 | root_idx = CHROMATIC_SCALE.index(str(self.sorted_tones[0])) | 107 | root_idx = CHROMATIC_SCALE.index(str(self.sorted_tones[0])) | ||
| 108 | end_tone_idx = (root_idx + MAJOR_THIRD_LEN) % TONES_COUNT | 108 | end_tone_idx = (root_idx + MAJOR_THIRD_LEN) % TONES_COUNT | ||
| 109 | return Tone(CHROMATIC_SCALE[end_tone_idx]) in self.sorted_tones | 109 | return Tone(CHROMATIC_SCALE[end_tone_idx]) in self.sorted_tones | ||
| 110 | 110 | ||||
| 111 | def is_power_chord(self): | 111 | def is_power_chord(self): | ||
| 112 | return not self.is_minor() and not self.is_major() | 112 | return not self.is_minor() and not self.is_major() | ||
| 113 | 113 | ||||
| 114 | def __add__(self, other): | 114 | def __add__(self, other): | ||
| 115 | if isinstance(other, Tone): | 115 | if isinstance(other, Tone): | ||
| 116 | new_tones = self.sorted_tones + [other] | 116 | new_tones = self.sorted_tones + [other] | ||
| 117 | return Chord(*new_tones) | 117 | return Chord(*new_tones) | ||
| 118 | elif isinstance(other, Chord): | 118 | elif isinstance(other, Chord): | ||
| 119 | new_tones = self.sorted_tones[:] | 119 | new_tones = self.sorted_tones[:] | ||
| 120 | new_tones.extend(other.sorted_tones) | 120 | new_tones.extend(other.sorted_tones) | ||
| 121 | return Chord(*new_tones) | 121 | return Chord(*new_tones) | ||
| 122 | 122 | ||||
| 123 | def __sub__(self, other): | 123 | def __sub__(self, other): | ||
| 124 | if isinstance(other, Tone): | 124 | if isinstance(other, Tone): | ||
| 125 | if other not in self.sorted_tones: | 125 | if other not in self.sorted_tones: | ||
| 126 | raise TypeError(f'Cannot remove tone {str(other)} from chord {str(self)}') | 126 | raise TypeError(f'Cannot remove tone {str(other)} from chord {str(self)}') | ||
| 127 | new_tones = self.sorted_tones[:] | 127 | new_tones = self.sorted_tones[:] | ||
| 128 | new_tones.remove(other) | 128 | new_tones.remove(other) | ||
| 129 | return Chord(*new_tones) | 129 | return Chord(*new_tones) | ||
| 130 | 130 | ||||
| 131 | def transposed(self, interval): | 131 | def transposed(self, interval): | ||
| 132 | transpose = lambda tone : tone + interval | 132 | transpose = lambda tone : tone + interval | ||
| 133 | transposed_tones = list(map(transpose, self.sorted_tones)) | 133 | transposed_tones = list(map(transpose, self.sorted_tones)) | ||
| 134 | return Chord(*transposed_tones) | 134 | return Chord(*transposed_tones) | 
| Legends | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|
 
  | 
              
  |  |||||||||
| f | 1 | CHROMATIC_SCALE = ( | f | 1 | CHROMATIC_SCALE = ( | 
| 2 | 'C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B' | 2 | 'C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B' | ||
| 3 | ) | 3 | ) | ||
| 4 | 4 | ||||
| 5 | TONES_COUNT = 12 | 5 | TONES_COUNT = 12 | ||
| 6 | MINOR_THIRD_LEN = 3 | 6 | MINOR_THIRD_LEN = 3 | ||
| 7 | MAJOR_THIRD_LEN = 4 | 7 | MAJOR_THIRD_LEN = 4 | ||
| 8 | 8 | ||||
| 9 | 9 | ||||
| 10 | class Tone: | 10 | class Tone: | ||
| 11 | 11 | ||||
| 12 | def __init__(self, tone_name): | 12 | def __init__(self, tone_name): | ||
| 13 | self.tone_name = tone_name | 13 | self.tone_name = tone_name | ||
| 14 | 14 | ||||
| 15 | def __str__(self): | 15 | def __str__(self): | ||
| 16 | return self.tone_name | 16 | return self.tone_name | ||
| 17 | 17 | ||||
| 18 | def __eq__(self, other): | 18 | def __eq__(self, other): | ||
| 19 | return self.tone_name == other.tone_name | 19 | return self.tone_name == other.tone_name | ||
| 20 | 20 | ||||
| 21 | def __ne__(self, other): | 21 | def __ne__(self, other): | ||
| 22 | return self.tone_name != other.tone_name | 22 | return self.tone_name != other.tone_name | ||
| 23 | 23 | ||||
| 24 | def __add__(self, other): | 24 | def __add__(self, other): | ||
| 25 | if isinstance(other, Tone): | 25 | if isinstance(other, Tone): | ||
| 26 | return Chord(self,other) | 26 | return Chord(self,other) | ||
| 27 | elif isinstance(other, Interval): | 27 | elif isinstance(other, Interval): | ||
| 28 | new_tone_idx = (CHROMATIC_SCALE.index(self.tone_name) + other.semitones_count) % TONES_COUNT | 28 | new_tone_idx = (CHROMATIC_SCALE.index(self.tone_name) + other.semitones_count) % TONES_COUNT | ||
| 29 | return Tone(CHROMATIC_SCALE[new_tone_idx]) | 29 | return Tone(CHROMATIC_SCALE[new_tone_idx]) | ||
| 30 | 30 | ||||
| 31 | def __sub__(self, other): | 31 | def __sub__(self, other): | ||
| 32 | if isinstance(other, Tone): | 32 | if isinstance(other, Tone): | ||
| 33 | self_idx = CHROMATIC_SCALE.index(self.tone_name) | 33 | self_idx = CHROMATIC_SCALE.index(self.tone_name) | ||
| 34 | other_idx = CHROMATIC_SCALE.index(other.tone_name) | 34 | other_idx = CHROMATIC_SCALE.index(other.tone_name) | ||
| n | 35 | interval_length = (self_idx - other_idx + TONES_COUNT) % TONES_COUNT | n | 35 | interval_length = (self_idx - other_idx) % TONES_COUNT | 
| 36 | return Interval(interval_length) | 36 | return Interval(interval_length) | ||
| 37 | elif isinstance(other, Interval): | 37 | elif isinstance(other, Interval): | ||
| n | 38 | new_tone_idx = (CHROMATIC_SCALE.index(self.tone_name) - other.semitones_count % TONES_COUNT + TONES_COUNT) % TONES_COUNT | n | 38 | new_tone_idx = (CHROMATIC_SCALE.index(self.tone_name) - other.semitones_count) % TONES_COUNT | 
| 39 | return Tone(CHROMATIC_SCALE[new_tone_idx]) | 39 | return Tone(CHROMATIC_SCALE[new_tone_idx]) | ||
| 40 | 40 | ||||
| 41 | 41 | ||||
| 42 | class Interval: | 42 | class Interval: | ||
| 43 | 43 | ||||
| 44 | _INTERVAL_NAMES = ( | 44 | _INTERVAL_NAMES = ( | ||
| 45 | 'unison', 'minor 2nd', 'major 2nd', 'minor 3rd', 'major 3rd', | 45 | 'unison', 'minor 2nd', 'major 2nd', 'minor 3rd', 'major 3rd', | ||
| 46 | 'perfect 4th', 'diminished 5th', 'perfect 5th', 'minor 6th', | 46 | 'perfect 4th', 'diminished 5th', 'perfect 5th', 'minor 6th', | ||
| 47 | 'major 6th', 'minor 7th', 'major 7th' | 47 | 'major 6th', 'minor 7th', 'major 7th' | ||
| 48 | ) | 48 | ) | ||
| 49 | 49 | ||||
| 50 | def __init__(self, semitones_count): | 50 | def __init__(self, semitones_count): | ||
| 51 | self.semitones_count = semitones_count | 51 | self.semitones_count = semitones_count | ||
| 52 | 52 | ||||
| 53 | def __str__(self): | 53 | def __str__(self): | ||
| 54 | return self._INTERVAL_NAMES[self.semitones_count % TONES_COUNT] | 54 | return self._INTERVAL_NAMES[self.semitones_count % TONES_COUNT] | ||
| 55 | 55 | ||||
| 56 | def __add__(self, other): | 56 | def __add__(self, other): | ||
| 57 | if isinstance(other, Tone): | 57 | if isinstance(other, Tone): | ||
| 58 | raise TypeError('Invalid operation') | 58 | raise TypeError('Invalid operation') | ||
| 59 | elif isinstance(other, Interval): | 59 | elif isinstance(other, Interval): | ||
| 60 | new_count = (self.semitones_count + other.semitones_count) | 60 | new_count = (self.semitones_count + other.semitones_count) | ||
| 61 | return Interval(new_count) | 61 | return Interval(new_count) | ||
| 62 | 62 | ||||
| 63 | def __sub__(self, other): | 63 | def __sub__(self, other): | ||
| 64 | if isinstance(other, Tone): | 64 | if isinstance(other, Tone): | ||
| 65 | raise TypeError('Invalid operation') | 65 | raise TypeError('Invalid operation') | ||
| 66 | 66 | ||||
| 67 | def __neg__(self): | 67 | def __neg__(self): | ||
| 68 | return Interval(-self.semitones_count) | 68 | return Interval(-self.semitones_count) | ||
| 69 | 69 | ||||
| 70 | 70 | ||||
| 71 | class Chord: | 71 | class Chord: | ||
| 72 | 72 | ||||
| 73 | def __init__(self, *tones): | 73 | def __init__(self, *tones): | ||
| 74 | if not self._are_valid_tones(*tones): | 74 | if not self._are_valid_tones(*tones): | ||
| 75 | raise TypeError('Cannot have a chord made of only 1 unique tone') | 75 | raise TypeError('Cannot have a chord made of only 1 unique tone') | ||
| 76 | self.sorted_tones = self._sort_tones(*tones) | 76 | self.sorted_tones = self._sort_tones(*tones) | ||
| 77 | 77 | ||||
| 78 | @staticmethod | 78 | @staticmethod | ||
| 79 | def _are_valid_tones(*tones): | 79 | def _are_valid_tones(*tones): | ||
| 80 | tones_set = {str(tone) for tone in tones} | 80 | tones_set = {str(tone) for tone in tones} | ||
| 81 | return len(tones_set) > 1 | 81 | return len(tones_set) > 1 | ||
| 82 | 82 | ||||
| 83 | @staticmethod | 83 | @staticmethod | ||
| 84 | def _sort_tones(*tones): | 84 | def _sort_tones(*tones): | ||
| 85 | root = tones[0] | 85 | root = tones[0] | ||
| 86 | sorted_tones = [root] | 86 | sorted_tones = [root] | ||
| 87 | end_idx = CHROMATIC_SCALE.index(str(root)) | 87 | end_idx = CHROMATIC_SCALE.index(str(root)) | ||
| 88 | iter_idx = (end_idx + 1) % TONES_COUNT | 88 | iter_idx = (end_idx + 1) % TONES_COUNT | ||
| 89 | 89 | ||||
| 90 | while iter_idx != end_idx: | 90 | while iter_idx != end_idx: | ||
| 91 | current_tone = Tone(CHROMATIC_SCALE[iter_idx]) | 91 | current_tone = Tone(CHROMATIC_SCALE[iter_idx]) | ||
| 92 | if current_tone in tones and current_tone not in sorted_tones: | 92 | if current_tone in tones and current_tone not in sorted_tones: | ||
| 93 | sorted_tones.append(current_tone) | 93 | sorted_tones.append(current_tone) | ||
| 94 | iter_idx += 1 | 94 | iter_idx += 1 | ||
| 95 | iter_idx %= TONES_COUNT | 95 | iter_idx %= TONES_COUNT | ||
| 96 | return sorted_tones | 96 | return sorted_tones | ||
| 97 | 97 | ||||
| 98 | def __str__(self): | 98 | def __str__(self): | ||
| 99 | return '-'.join(str(tone) for tone in self.sorted_tones) | 99 | return '-'.join(str(tone) for tone in self.sorted_tones) | ||
| 100 | 100 | ||||
| 101 | def is_minor(self): | 101 | def is_minor(self): | ||
| 102 | root_idx = CHROMATIC_SCALE.index(str(self.sorted_tones[0])) | 102 | root_idx = CHROMATIC_SCALE.index(str(self.sorted_tones[0])) | ||
| 103 | end_tone_idx = (root_idx + MINOR_THIRD_LEN) % TONES_COUNT | 103 | end_tone_idx = (root_idx + MINOR_THIRD_LEN) % TONES_COUNT | ||
| 104 | return Tone(CHROMATIC_SCALE[end_tone_idx]) in self.sorted_tones | 104 | return Tone(CHROMATIC_SCALE[end_tone_idx]) in self.sorted_tones | ||
| 105 | 105 | ||||
| 106 | def is_major(self): | 106 | def is_major(self): | ||
| 107 | root_idx = CHROMATIC_SCALE.index(str(self.sorted_tones[0])) | 107 | root_idx = CHROMATIC_SCALE.index(str(self.sorted_tones[0])) | ||
| 108 | end_tone_idx = (root_idx + MAJOR_THIRD_LEN) % TONES_COUNT | 108 | end_tone_idx = (root_idx + MAJOR_THIRD_LEN) % TONES_COUNT | ||
| 109 | return Tone(CHROMATIC_SCALE[end_tone_idx]) in self.sorted_tones | 109 | return Tone(CHROMATIC_SCALE[end_tone_idx]) in self.sorted_tones | ||
| 110 | 110 | ||||
| 111 | def is_power_chord(self): | 111 | def is_power_chord(self): | ||
| 112 | return not self.is_minor() and not self.is_major() | 112 | return not self.is_minor() and not self.is_major() | ||
| 113 | 113 | ||||
| 114 | def __add__(self, other): | 114 | def __add__(self, other): | ||
| 115 | if isinstance(other, Tone): | 115 | if isinstance(other, Tone): | ||
| 116 | new_tones = self.sorted_tones + [other] | 116 | new_tones = self.sorted_tones + [other] | ||
| 117 | return Chord(*new_tones) | 117 | return Chord(*new_tones) | ||
| 118 | elif isinstance(other, Chord): | 118 | elif isinstance(other, Chord): | ||
| 119 | new_tones = self.sorted_tones[:] | 119 | new_tones = self.sorted_tones[:] | ||
| 120 | new_tones.extend(other.sorted_tones) | 120 | new_tones.extend(other.sorted_tones) | ||
| 121 | return Chord(*new_tones) | 121 | return Chord(*new_tones) | ||
| 122 | 122 | ||||
| 123 | def __sub__(self, other): | 123 | def __sub__(self, other): | ||
| 124 | if isinstance(other, Tone): | 124 | if isinstance(other, Tone): | ||
| 125 | if other not in self.sorted_tones: | 125 | if other not in self.sorted_tones: | ||
| 126 | raise TypeError(f'Cannot remove tone {str(other)} from chord {str(self)}') | 126 | raise TypeError(f'Cannot remove tone {str(other)} from chord {str(self)}') | ||
| 127 | new_tones = self.sorted_tones[:] | 127 | new_tones = self.sorted_tones[:] | ||
| 128 | new_tones.remove(other) | 128 | new_tones.remove(other) | ||
| 129 | return Chord(*new_tones) | 129 | return Chord(*new_tones) | ||
| 130 | 130 | ||||
| 131 | def transposed(self, interval): | 131 | def transposed(self, interval): | ||
| t | 132 | transpose_with = interval.semitones_count | t | 132 | transpose = lambda tone : tone + interval | 
| 133 | transpose = lambda tone : Tone(CHROMATIC_SCALE[(CHROMATIC_SCALE.index(str(tone)) + transpose_with + TONES_COUNT) % TONES_COUNT]) | ||||
| 134 | transposed_tones = list(map(transpose, self.sorted_tones)) | 133 | transposed_tones = list(map(transpose, self.sorted_tones)) | ||
| 135 | return Chord(*transposed_tones) | 134 | return Chord(*transposed_tones) | 
| Legends | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|
 
  | 
              
  |  |||||||||
| f | 1 | CHROMATIC_SCALE = ( | f | 1 | CHROMATIC_SCALE = ( | 
| 2 | 'C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B' | 2 | 'C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B' | ||
| 3 | ) | 3 | ) | ||
| 4 | 4 | ||||
| 5 | TONES_COUNT = 12 | 5 | TONES_COUNT = 12 | ||
| 6 | MINOR_THIRD_LEN = 3 | 6 | MINOR_THIRD_LEN = 3 | ||
| 7 | MAJOR_THIRD_LEN = 4 | 7 | MAJOR_THIRD_LEN = 4 | ||
| 8 | 8 | ||||
| 9 | 9 | ||||
| 10 | class Tone: | 10 | class Tone: | ||
| 11 | 11 | ||||
| 12 | def __init__(self, tone_name): | 12 | def __init__(self, tone_name): | ||
| 13 | self.tone_name = tone_name | 13 | self.tone_name = tone_name | ||
| 14 | 14 | ||||
| 15 | def __str__(self): | 15 | def __str__(self): | ||
| 16 | return self.tone_name | 16 | return self.tone_name | ||
| 17 | 17 | ||||
| 18 | def __eq__(self, other): | 18 | def __eq__(self, other): | ||
| 19 | return self.tone_name == other.tone_name | 19 | return self.tone_name == other.tone_name | ||
| 20 | 20 | ||||
| 21 | def __ne__(self, other): | 21 | def __ne__(self, other): | ||
| 22 | return self.tone_name != other.tone_name | 22 | return self.tone_name != other.tone_name | ||
| 23 | 23 | ||||
| 24 | def __add__(self, other): | 24 | def __add__(self, other): | ||
| 25 | if isinstance(other, Tone): | 25 | if isinstance(other, Tone): | ||
| 26 | return Chord(self,other) | 26 | return Chord(self,other) | ||
| 27 | elif isinstance(other, Interval): | 27 | elif isinstance(other, Interval): | ||
| 28 | new_tone_idx = (CHROMATIC_SCALE.index(self.tone_name) + other.semitones_count) % TONES_COUNT | 28 | new_tone_idx = (CHROMATIC_SCALE.index(self.tone_name) + other.semitones_count) % TONES_COUNT | ||
| 29 | return Tone(CHROMATIC_SCALE[new_tone_idx]) | 29 | return Tone(CHROMATIC_SCALE[new_tone_idx]) | ||
| 30 | 30 | ||||
| 31 | def __sub__(self, other): | 31 | def __sub__(self, other): | ||
| 32 | if isinstance(other, Tone): | 32 | if isinstance(other, Tone): | ||
| 33 | self_idx = CHROMATIC_SCALE.index(self.tone_name) | 33 | self_idx = CHROMATIC_SCALE.index(self.tone_name) | ||
| 34 | other_idx = CHROMATIC_SCALE.index(other.tone_name) | 34 | other_idx = CHROMATIC_SCALE.index(other.tone_name) | ||
| 35 | interval_length = (self_idx - other_idx + TONES_COUNT) % TONES_COUNT | 35 | interval_length = (self_idx - other_idx + TONES_COUNT) % TONES_COUNT | ||
| 36 | return Interval(interval_length) | 36 | return Interval(interval_length) | ||
| 37 | elif isinstance(other, Interval): | 37 | elif isinstance(other, Interval): | ||
| 38 | new_tone_idx = (CHROMATIC_SCALE.index(self.tone_name) - other.semitones_count % TONES_COUNT + TONES_COUNT) % TONES_COUNT | 38 | new_tone_idx = (CHROMATIC_SCALE.index(self.tone_name) - other.semitones_count % TONES_COUNT + TONES_COUNT) % TONES_COUNT | ||
| 39 | return Tone(CHROMATIC_SCALE[new_tone_idx]) | 39 | return Tone(CHROMATIC_SCALE[new_tone_idx]) | ||
| 40 | 40 | ||||
| 41 | 41 | ||||
| 42 | class Interval: | 42 | class Interval: | ||
| 43 | 43 | ||||
| 44 | _INTERVAL_NAMES = ( | 44 | _INTERVAL_NAMES = ( | ||
| 45 | 'unison', 'minor 2nd', 'major 2nd', 'minor 3rd', 'major 3rd', | 45 | 'unison', 'minor 2nd', 'major 2nd', 'minor 3rd', 'major 3rd', | ||
| 46 | 'perfect 4th', 'diminished 5th', 'perfect 5th', 'minor 6th', | 46 | 'perfect 4th', 'diminished 5th', 'perfect 5th', 'minor 6th', | ||
| 47 | 'major 6th', 'minor 7th', 'major 7th' | 47 | 'major 6th', 'minor 7th', 'major 7th' | ||
| 48 | ) | 48 | ) | ||
| 49 | 49 | ||||
| 50 | def __init__(self, semitones_count): | 50 | def __init__(self, semitones_count): | ||
| 51 | self.semitones_count = semitones_count | 51 | self.semitones_count = semitones_count | ||
| 52 | 52 | ||||
| 53 | def __str__(self): | 53 | def __str__(self): | ||
| 54 | return self._INTERVAL_NAMES[self.semitones_count % TONES_COUNT] | 54 | return self._INTERVAL_NAMES[self.semitones_count % TONES_COUNT] | ||
| 55 | 55 | ||||
| 56 | def __add__(self, other): | 56 | def __add__(self, other): | ||
| 57 | if isinstance(other, Tone): | 57 | if isinstance(other, Tone): | ||
| 58 | raise TypeError('Invalid operation') | 58 | raise TypeError('Invalid operation') | ||
| 59 | elif isinstance(other, Interval): | 59 | elif isinstance(other, Interval): | ||
| 60 | new_count = (self.semitones_count + other.semitones_count) | 60 | new_count = (self.semitones_count + other.semitones_count) | ||
| 61 | return Interval(new_count) | 61 | return Interval(new_count) | ||
| 62 | 62 | ||||
| 63 | def __sub__(self, other): | 63 | def __sub__(self, other): | ||
| 64 | if isinstance(other, Tone): | 64 | if isinstance(other, Tone): | ||
| 65 | raise TypeError('Invalid operation') | 65 | raise TypeError('Invalid operation') | ||
| 66 | 66 | ||||
| 67 | def __neg__(self): | 67 | def __neg__(self): | ||
| 68 | return Interval(-self.semitones_count) | 68 | return Interval(-self.semitones_count) | ||
| 69 | 69 | ||||
| 70 | 70 | ||||
| 71 | class Chord: | 71 | class Chord: | ||
| 72 | 72 | ||||
| 73 | def __init__(self, *tones): | 73 | def __init__(self, *tones): | ||
| 74 | if not self._are_valid_tones(*tones): | 74 | if not self._are_valid_tones(*tones): | ||
| 75 | raise TypeError('Cannot have a chord made of only 1 unique tone') | 75 | raise TypeError('Cannot have a chord made of only 1 unique tone') | ||
| 76 | self.sorted_tones = self._sort_tones(*tones) | 76 | self.sorted_tones = self._sort_tones(*tones) | ||
| 77 | 77 | ||||
| 78 | @staticmethod | 78 | @staticmethod | ||
| 79 | def _are_valid_tones(*tones): | 79 | def _are_valid_tones(*tones): | ||
| n | 80 | tones_set = set() | n | 80 | tones_set = {str(tone) for tone in tones} | 
| 81 | |||||
| 82 | for tone in tones: | ||||
| 83 | tones_set.add(str(tone)) | ||||
| 84 | |||||
| 85 | return len(tones_set) > 1 | 81 | return len(tones_set) > 1 | ||
| 86 | 82 | ||||
| 87 | @staticmethod | 83 | @staticmethod | ||
| 88 | def _sort_tones(*tones): | 84 | def _sort_tones(*tones): | ||
| 89 | root = tones[0] | 85 | root = tones[0] | ||
| 90 | sorted_tones = [root] | 86 | sorted_tones = [root] | ||
| 91 | end_idx = CHROMATIC_SCALE.index(str(root)) | 87 | end_idx = CHROMATIC_SCALE.index(str(root)) | ||
| 92 | iter_idx = (end_idx + 1) % TONES_COUNT | 88 | iter_idx = (end_idx + 1) % TONES_COUNT | ||
| 93 | 89 | ||||
| 94 | while iter_idx != end_idx: | 90 | while iter_idx != end_idx: | ||
| n | 95 | current_tone = Tone(CHROMATIC_SCALE[iter_idx]) | n | 91 | current_tone = Tone(CHROMATIC_SCALE[iter_idx]) | 
| 96 | if current_tone in tones and current_tone not in sorted_tones: | 92 | if current_tone in tones and current_tone not in sorted_tones: | ||
| 97 | sorted_tones.append(current_tone) | 93 | sorted_tones.append(current_tone) | ||
| n | 98 | iter_idx+=1 | n | 94 | iter_idx += 1 | 
| 99 | iter_idx %= TONES_COUNT | 95 | iter_idx %= TONES_COUNT | ||
| 100 | return sorted_tones | 96 | return sorted_tones | ||
| 101 | 97 | ||||
| 102 | def __str__(self): | 98 | def __str__(self): | ||
| 103 | return '-'.join(str(tone) for tone in self.sorted_tones) | 99 | return '-'.join(str(tone) for tone in self.sorted_tones) | ||
| 104 | 100 | ||||
| 105 | def is_minor(self): | 101 | def is_minor(self): | ||
| 106 | root_idx = CHROMATIC_SCALE.index(str(self.sorted_tones[0])) | 102 | root_idx = CHROMATIC_SCALE.index(str(self.sorted_tones[0])) | ||
| 107 | end_tone_idx = (root_idx + MINOR_THIRD_LEN) % TONES_COUNT | 103 | end_tone_idx = (root_idx + MINOR_THIRD_LEN) % TONES_COUNT | ||
| 108 | return Tone(CHROMATIC_SCALE[end_tone_idx]) in self.sorted_tones | 104 | return Tone(CHROMATIC_SCALE[end_tone_idx]) in self.sorted_tones | ||
| 109 | 105 | ||||
| 110 | def is_major(self): | 106 | def is_major(self): | ||
| 111 | root_idx = CHROMATIC_SCALE.index(str(self.sorted_tones[0])) | 107 | root_idx = CHROMATIC_SCALE.index(str(self.sorted_tones[0])) | ||
| 112 | end_tone_idx = (root_idx + MAJOR_THIRD_LEN) % TONES_COUNT | 108 | end_tone_idx = (root_idx + MAJOR_THIRD_LEN) % TONES_COUNT | ||
| 113 | return Tone(CHROMATIC_SCALE[end_tone_idx]) in self.sorted_tones | 109 | return Tone(CHROMATIC_SCALE[end_tone_idx]) in self.sorted_tones | ||
| 114 | 110 | ||||
| 115 | def is_power_chord(self): | 111 | def is_power_chord(self): | ||
| 116 | return not self.is_minor() and not self.is_major() | 112 | return not self.is_minor() and not self.is_major() | ||
| 117 | 113 | ||||
| 118 | def __add__(self, other): | 114 | def __add__(self, other): | ||
| 119 | if isinstance(other, Tone): | 115 | if isinstance(other, Tone): | ||
| 120 | new_tones = self.sorted_tones + [other] | 116 | new_tones = self.sorted_tones + [other] | ||
| 121 | return Chord(*new_tones) | 117 | return Chord(*new_tones) | ||
| 122 | elif isinstance(other, Chord): | 118 | elif isinstance(other, Chord): | ||
| n | 123 | new_tones = self.sorted_tones.copy() | n | 119 | new_tones = self.sorted_tones[:] | 
| 124 | new_tones.extend(other.sorted_tones) | 120 | new_tones.extend(other.sorted_tones) | ||
| 125 | return Chord(*new_tones) | 121 | return Chord(*new_tones) | ||
| 126 | 122 | ||||
| 127 | def __sub__(self, other): | 123 | def __sub__(self, other): | ||
| 128 | if isinstance(other, Tone): | 124 | if isinstance(other, Tone): | ||
| 129 | if other not in self.sorted_tones: | 125 | if other not in self.sorted_tones: | ||
| 130 | raise TypeError(f'Cannot remove tone {str(other)} from chord {str(self)}') | 126 | raise TypeError(f'Cannot remove tone {str(other)} from chord {str(self)}') | ||
| t | 131 | new_tones = self.sorted_tones.copy() | t | 127 | new_tones = self.sorted_tones[:] | 
| 132 | new_tones.remove(other) | 128 | new_tones.remove(other) | ||
| 133 | return Chord(*new_tones) | 129 | return Chord(*new_tones) | ||
| 134 | 130 | ||||
| 135 | def transposed(self, interval): | 131 | def transposed(self, interval): | ||
| 136 | transpose_with = interval.semitones_count | 132 | transpose_with = interval.semitones_count | ||
| 137 | transpose = lambda tone : Tone(CHROMATIC_SCALE[(CHROMATIC_SCALE.index(str(tone)) + transpose_with + TONES_COUNT) % TONES_COUNT]) | 133 | transpose = lambda tone : Tone(CHROMATIC_SCALE[(CHROMATIC_SCALE.index(str(tone)) + transpose_with + TONES_COUNT) % TONES_COUNT]) | ||
| 138 | transposed_tones = list(map(transpose, self.sorted_tones)) | 134 | transposed_tones = list(map(transpose, self.sorted_tones)) | ||
| 139 | return Chord(*transposed_tones) | 135 | return Chord(*transposed_tones) | 
| Legends | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|
 
  | 
              
  |  |||||||||