Домашни > Pitches love the D > Решения > Решението на Николай Стоянов

Резултати
9 точки от тестове
0 точки от учител

9 точки общо

34 успешни теста
3 неуспешни теста
Код (Решение 4.0)

  1class Tone:
  2    """Implement a tone."""
  3
  4    TONES = ('C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B')
  5
  6    def __key(self):
  7        """Create a private key so tone is hashable."""
  8        return str(self.tone)
  9
 10    def __init__(self, tone):
 11        self.tone = tone
 12
 13    def __str__(self):
 14        return self.tone
 15
 16    def __eq__(self, other):
 17        return str(self) == str(other)
 18
 19    def __hash__(self):
 20        return hash(self.__key())
 21
 22    def __add__(self, other):
 23        """Add Tone + Tone or Tone + Interval."""
 24        if isinstance(other, Tone):
 25            return Chord(self, other)
 26        elif isinstance(other, Interval):
 27            idx = (Tone.TONES.index(self) + other.num_of_semitones) % len(Tone.TONES)
 28            return Tone(Tone.TONES[idx])
 29
 30    def __sub__(self, other):
 31        """Subtract Tone - Tone or Tone - Interval."""
 32        if isinstance(other, Tone):
 33            return Interval((Tone.TONES.index(self) - Tone.TONES.index(other)) % len(Tone.TONES))
 34        elif isinstance(other, Interval):
 35            idx = (Tone.TONES.index(self) - other.num_of_semitones) % len(Tone.TONES)
 36            return Tone(Tone.TONES[idx])
 37
 38
 39class Interval:
 40    """Implement an interval."""
 41
 42    INTERVALS = {
 43        0: 'unison',
 44        1: 'minor 2nd',
 45        2: 'major 2nd',
 46        3: 'minor 3rd',
 47        4: 'major 3rd',
 48        5: 'perfect 4th',
 49        6: 'diminished 5th',
 50        7: 'perfect 5th',
 51        8: 'minor 6th',
 52        9: 'major 6th',
 53        10: 'minor 7th',
 54        11: 'major 7th'
 55    }
 56
 57    def __init__(self, num_of_semitones):
 58        self.num_of_semitones = num_of_semitones % 12
 59
 60    def __str__(self):
 61        return self.INTERVALS[self.num_of_semitones]
 62
 63    def __add__(self, other):
 64        """Add Interval + Interval."""
 65        if isinstance(other, Tone):
 66            raise TypeError("Invalid operation")
 67        elif isinstance(other, Interval):
 68            return Interval(self.num_of_semitones + other.num_of_semitones)
 69
 70    def __sub__(self, other):
 71        """Invalidate subtraction Interval - Tone."""
 72        if isinstance(other, Tone):
 73            raise TypeError("Invalid operation")
 74
 75    def __neg__(self):
 76        """Support the creation of such intervals: -Interval(5)."""
 77        return Interval(len(Tone.TONES) - self.num_of_semitones)
 78
 79    @classmethod
 80    def init_from_tones(cls, tone1, tone2):
 81        """Initialize interval from 2 tones."""
 82        return cls(abs(Tone.TONES.index(tone1) - Tone.TONES.index(tone2)))
 83
 84
 85class Chord:
 86    """Implement a chord."""
 87
 88    INDEX_MINOR = 3 # index in interval.INTERVALS for minor 3rd
 89    INDEX_MAJOR = 4 # index in interval.INTERVALS for major 3rd
 90
 91    def __init__(self, main_tone, *args):
 92        self.main_tone = main_tone
 93        self.tones = list(args)
 94        self.tones.append(self.main_tone)
 95        self._sort_tones()
 96        if len(self.tones) < 2:
 97            raise TypeError('Cannot have a chord made of only 1 unique tone')
 98
 99    def __str__(self):
100        return f'{'-'.join(map(str, self.tones))}'
101
102    def __add__(self, other):
103        """Add Chord + Tone or Chord + Chord."""
104        if isinstance(other, Tone):
105            return Chord(self.main_tone, *(self.tones + [other]))
106        if isinstance(other, Chord):
107            return Chord(self.main_tone, *(self.tones + other.tones))
108
109    def __sub__(self, other):
110        """Subtract Chord - Tone."""
111        if isinstance(other, Tone):
112            if other in self.tones:
113                new_tones = self.tones[:]
114                new_tones.remove(other)
115                new_main_tone = new_tones[0]
116                return Chord(new_main_tone, *new_tones)
117            else:
118                raise TypeError(f'Cannot remove tone {other} from chord {self}')
119
120    def _sort_tones(self):
121        """Sort all tones in chord relative to the main tone."""
122        main_tone_idx = Tone.TONES.index(self.main_tone)
123        tones_unique = set(self.tones) # because of the hash func the tones are saved as strings
124        all_tones_sorted = Tone.TONES[main_tone_idx:] + Tone.TONES[:main_tone_idx]
125        self.tones = list(map(Tone, filter(lambda tone: tone in tones_unique, all_tones_sorted)))
126
127    def _check_for_special_tone(self, idx):
128        """Check if there is any tone that makes a special tone with root based on idx given."""
129        return any(str(Interval.init_from_tones(self.main_tone, tone)) == Interval.INTERVALS[idx] for tone in self.tones)
130
131    def is_minor(self):
132        """Check if chord is minor."""
133        return self._check_for_special_tone(self.INDEX_MINOR)
134
135    def is_major(self):
136        """Check if chord is major."""
137        return self._check_for_special_tone(self.INDEX_MAJOR)
138
139    def is_power_chord(self):
140        """Check if tone is power chord."""
141        return not self.is_minor() and not self.is_major()
142
143    def transposed(self, interval):
144        """Transpose by interval."""
145        return Chord(self.main_tone + interval, *list(map(lambda tone: tone + interval, self.tones)))

....FFF..............................
======================================================================
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

----------------------------------------------------------------------
Ran 37 tests in 0.002s

FAILED (failures=3)

Дискусия
История

f1class Tone:f1class Tone:
2    """Implement a tone."""2    """Implement a tone."""
33
4    TONES = ('C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B')4    TONES = ('C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B')
55
6    def __key(self):6    def __key(self):
7        """Create a private key so tone is hashable."""7        """Create a private key so tone is hashable."""
8        return str(self.tone)8        return str(self.tone)
99
10    def __init__(self, tone):10    def __init__(self, tone):
11        self.tone = tone11        self.tone = tone
1212
13    def __str__(self):13    def __str__(self):
14        return self.tone14        return self.tone
1515
16    def __eq__(self, other):16    def __eq__(self, other):
17        return str(self) == str(other)17        return str(self) == str(other)
1818
19    def __hash__(self):19    def __hash__(self):
20        return hash(self.__key())20        return hash(self.__key())
2121
22    def __add__(self, other):22    def __add__(self, other):
23        """Add Tone + Tone or Tone + Interval."""23        """Add Tone + Tone or Tone + Interval."""
24        if isinstance(other, Tone):24        if isinstance(other, Tone):
25            return Chord(self, other)25            return Chord(self, other)
26        elif isinstance(other, Interval):26        elif isinstance(other, Interval):
27            idx = (Tone.TONES.index(self) + other.num_of_semitones) % len(Tone.TONES)27            idx = (Tone.TONES.index(self) + other.num_of_semitones) % len(Tone.TONES)
28            return Tone(Tone.TONES[idx])28            return Tone(Tone.TONES[idx])
2929
30    def __sub__(self, other):30    def __sub__(self, other):
31        """Subtract Tone - Tone or Tone - Interval."""31        """Subtract Tone - Tone or Tone - Interval."""
32        if isinstance(other, Tone):32        if isinstance(other, Tone):
33            return Interval((Tone.TONES.index(self) - Tone.TONES.index(other)) % len(Tone.TONES))33            return Interval((Tone.TONES.index(self) - Tone.TONES.index(other)) % len(Tone.TONES))
34        elif isinstance(other, Interval):34        elif isinstance(other, Interval):
35            idx = (Tone.TONES.index(self) - other.num_of_semitones) % len(Tone.TONES)35            idx = (Tone.TONES.index(self) - other.num_of_semitones) % len(Tone.TONES)
36            return Tone(Tone.TONES[idx])36            return Tone(Tone.TONES[idx])
3737
3838
39class Interval:39class Interval:
40    """Implement an interval."""40    """Implement an interval."""
4141
42    INTERVALS = {42    INTERVALS = {
43        0: 'unison',43        0: 'unison',
44        1: 'minor 2nd',44        1: 'minor 2nd',
45        2: 'major 2nd',45        2: 'major 2nd',
46        3: 'minor 3rd',46        3: 'minor 3rd',
47        4: 'major 3rd',47        4: 'major 3rd',
48        5: 'perfect 4th',48        5: 'perfect 4th',
49        6: 'diminished 5th',49        6: 'diminished 5th',
50        7: 'perfect 5th',50        7: 'perfect 5th',
51        8: 'minor 6th',51        8: 'minor 6th',
52        9: 'major 6th',52        9: 'major 6th',
53        10: 'minor 7th',53        10: 'minor 7th',
54        11: 'major 7th'54        11: 'major 7th'
55    }55    }
5656
57    def __init__(self, num_of_semitones):57    def __init__(self, num_of_semitones):
58        self.num_of_semitones = num_of_semitones % 1258        self.num_of_semitones = num_of_semitones % 12
5959
60    def __str__(self):60    def __str__(self):
61        return self.INTERVALS[self.num_of_semitones]61        return self.INTERVALS[self.num_of_semitones]
6262
63    def __add__(self, other):63    def __add__(self, other):
64        """Add Interval + Interval."""64        """Add Interval + Interval."""
65        if isinstance(other, Tone):65        if isinstance(other, Tone):
66            raise TypeError("Invalid operation")66            raise TypeError("Invalid operation")
67        elif isinstance(other, Interval):67        elif isinstance(other, Interval):
68            return Interval(self.num_of_semitones + other.num_of_semitones)68            return Interval(self.num_of_semitones + other.num_of_semitones)
6969
70    def __sub__(self, other):70    def __sub__(self, other):
71        """Invalidate subtraction Interval - Tone."""71        """Invalidate subtraction Interval - Tone."""
72        if isinstance(other, Tone):72        if isinstance(other, Tone):
73            raise TypeError("Invalid operation")73            raise TypeError("Invalid operation")
7474
75    def __neg__(self):75    def __neg__(self):
76        """Support the creation of such intervals: -Interval(5)."""76        """Support the creation of such intervals: -Interval(5)."""
77        return Interval(len(Tone.TONES) - self.num_of_semitones)77        return Interval(len(Tone.TONES) - self.num_of_semitones)
7878
79    @classmethod79    @classmethod
80    def init_from_tones(cls, tone1, tone2):80    def init_from_tones(cls, tone1, tone2):
81        """Initialize interval from 2 tones."""81        """Initialize interval from 2 tones."""
82        return cls(abs(Tone.TONES.index(tone1) - Tone.TONES.index(tone2)))82        return cls(abs(Tone.TONES.index(tone1) - Tone.TONES.index(tone2)))
8383
8484
85class Chord:85class Chord:
86    """Implement a chord."""86    """Implement a chord."""
8787
88    INDEX_MINOR = 3 # index in interval.INTERVALS for minor 3rd88    INDEX_MINOR = 3 # index in interval.INTERVALS for minor 3rd
89    INDEX_MAJOR = 4 # index in interval.INTERVALS for major 3rd89    INDEX_MAJOR = 4 # index in interval.INTERVALS for major 3rd
9090
91    def __init__(self, main_tone, *args):91    def __init__(self, main_tone, *args):
92        self.main_tone = main_tone92        self.main_tone = main_tone
93        self.tones = list(args)93        self.tones = list(args)
94        self.tones.append(self.main_tone)94        self.tones.append(self.main_tone)
95        self._sort_tones()95        self._sort_tones()
96        if len(self.tones) < 2:96        if len(self.tones) < 2:
97            raise TypeError('Cannot have a chord made of only 1 unique tone')97            raise TypeError('Cannot have a chord made of only 1 unique tone')
9898
99    def __str__(self):99    def __str__(self):
100        return f'{'-'.join(map(str, self.tones))}'100        return f'{'-'.join(map(str, self.tones))}'
101101
102    def __add__(self, other):102    def __add__(self, other):
103        """Add Chord + Tone or Chord + Chord."""103        """Add Chord + Tone or Chord + Chord."""
104        if isinstance(other, Tone):104        if isinstance(other, Tone):
105            return Chord(self.main_tone, *(self.tones + [other]))105            return Chord(self.main_tone, *(self.tones + [other]))
106        if isinstance(other, Chord):106        if isinstance(other, Chord):
107            return Chord(self.main_tone, *(self.tones + other.tones))107            return Chord(self.main_tone, *(self.tones + other.tones))
108108
109    def __sub__(self, other):109    def __sub__(self, other):
110        """Subtract Chord - Tone."""110        """Subtract Chord - Tone."""
111        if isinstance(other, Tone):111        if isinstance(other, Tone):
112            if other in self.tones:112            if other in self.tones:
113                new_tones = self.tones[:]113                new_tones = self.tones[:]
114                new_tones.remove(other)114                new_tones.remove(other)
115                new_main_tone = new_tones[0]115                new_main_tone = new_tones[0]
116                return Chord(new_main_tone, *new_tones)116                return Chord(new_main_tone, *new_tones)
117            else:117            else:
118                raise TypeError(f'Cannot remove tone {other} from chord {self}')118                raise TypeError(f'Cannot remove tone {other} from chord {self}')
119119
120    def _sort_tones(self):120    def _sort_tones(self):
121        """Sort all tones in chord relative to the main tone."""121        """Sort all tones in chord relative to the main tone."""
122        main_tone_idx = Tone.TONES.index(self.main_tone)122        main_tone_idx = Tone.TONES.index(self.main_tone)
123        tones_unique = set(self.tones) # because of the hash func the tones are saved as strings123        tones_unique = set(self.tones) # because of the hash func the tones are saved as strings
124        all_tones_sorted = Tone.TONES[main_tone_idx:] + Tone.TONES[:main_tone_idx]124        all_tones_sorted = Tone.TONES[main_tone_idx:] + Tone.TONES[:main_tone_idx]
125        self.tones = list(map(Tone, filter(lambda tone: tone in tones_unique, all_tones_sorted)))125        self.tones = list(map(Tone, filter(lambda tone: tone in tones_unique, all_tones_sorted)))
126126
127    def _check_for_special_tone(self, idx):127    def _check_for_special_tone(self, idx):
128        """Check if there is any tone that makes a special tone with root based on idx given."""128        """Check if there is any tone that makes a special tone with root based on idx given."""
129        return any(str(Interval.init_from_tones(self.main_tone, tone)) == Interval.INTERVALS[idx] for tone in self.tones)129        return any(str(Interval.init_from_tones(self.main_tone, tone)) == Interval.INTERVALS[idx] for tone in self.tones)
130130
131    def is_minor(self):131    def is_minor(self):
132        """Check if chord is minor."""132        """Check if chord is minor."""
133        return self._check_for_special_tone(self.INDEX_MINOR)133        return self._check_for_special_tone(self.INDEX_MINOR)
134134
135    def is_major(self):135    def is_major(self):
136        """Check if chord is major."""136        """Check if chord is major."""
137        return self._check_for_special_tone(self.INDEX_MAJOR)137        return self._check_for_special_tone(self.INDEX_MAJOR)
138138
139    def is_power_chord(self):139    def is_power_chord(self):
140        """Check if tone is power chord."""140        """Check if tone is power chord."""
141        return not self.is_minor() and not self.is_major()141        return not self.is_minor() and not self.is_major()
142142
143    def transposed(self, interval):143    def transposed(self, interval):
144        """Transpose by interval."""144        """Transpose by interval."""
145        return Chord(self.main_tone + interval, *list(map(lambda tone: tone + interval, self.tones)))145        return Chord(self.main_tone + interval, *list(map(lambda tone: tone + interval, self.tones)))
t146 t
147 
148c_power_chord = Chord(Tone("C"), Tone("G"))
149result_chord = c_power_chord - Tone("E")
150# C-G
Legends
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op

f1class Tone:f1class Tone:
2    """Implement a tone."""2    """Implement a tone."""
33
4    TONES = ('C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B')4    TONES = ('C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B')
55
6    def __key(self):6    def __key(self):
7        """Create a private key so tone is hashable."""7        """Create a private key so tone is hashable."""
8        return str(self.tone)8        return str(self.tone)
99
10    def __init__(self, tone):10    def __init__(self, tone):
11        self.tone = tone11        self.tone = tone
1212
13    def __str__(self):13    def __str__(self):
14        return self.tone14        return self.tone
1515
16    def __eq__(self, other):16    def __eq__(self, other):
17        return str(self) == str(other)17        return str(self) == str(other)
1818
19    def __hash__(self):19    def __hash__(self):
20        return hash(self.__key())20        return hash(self.__key())
2121
22    def __add__(self, other):22    def __add__(self, other):
23        """Add Tone + Tone or Tone + Interval."""23        """Add Tone + Tone or Tone + Interval."""
24        if isinstance(other, Tone):24        if isinstance(other, Tone):
25            return Chord(self, other)25            return Chord(self, other)
26        elif isinstance(other, Interval):26        elif isinstance(other, Interval):
27            idx = (Tone.TONES.index(self) + other.num_of_semitones) % len(Tone.TONES)27            idx = (Tone.TONES.index(self) + other.num_of_semitones) % len(Tone.TONES)
28            return Tone(Tone.TONES[idx])28            return Tone(Tone.TONES[idx])
2929
30    def __sub__(self, other):30    def __sub__(self, other):
31        """Subtract Tone - Tone or Tone - Interval."""31        """Subtract Tone - Tone or Tone - Interval."""
32        if isinstance(other, Tone):32        if isinstance(other, Tone):
33            return Interval((Tone.TONES.index(self) - Tone.TONES.index(other)) % len(Tone.TONES))33            return Interval((Tone.TONES.index(self) - Tone.TONES.index(other)) % len(Tone.TONES))
34        elif isinstance(other, Interval):34        elif isinstance(other, Interval):
35            idx = (Tone.TONES.index(self) - other.num_of_semitones) % len(Tone.TONES)35            idx = (Tone.TONES.index(self) - other.num_of_semitones) % len(Tone.TONES)
36            return Tone(Tone.TONES[idx])36            return Tone(Tone.TONES[idx])
3737
3838
39class Interval:39class Interval:
40    """Implement an interval."""40    """Implement an interval."""
4141
42    INTERVALS = {42    INTERVALS = {
43        0: 'unison',43        0: 'unison',
44        1: 'minor 2nd',44        1: 'minor 2nd',
45        2: 'major 2nd',45        2: 'major 2nd',
46        3: 'minor 3rd',46        3: 'minor 3rd',
47        4: 'major 3rd',47        4: 'major 3rd',
48        5: 'perfect 4th',48        5: 'perfect 4th',
49        6: 'diminished 5th',49        6: 'diminished 5th',
50        7: 'perfect 5th',50        7: 'perfect 5th',
51        8: 'minor 6th',51        8: 'minor 6th',
52        9: 'major 6th',52        9: 'major 6th',
53        10: 'minor 7th',53        10: 'minor 7th',
54        11: 'major 7th'54        11: 'major 7th'
55    }55    }
5656
57    def __init__(self, num_of_semitones):57    def __init__(self, num_of_semitones):
58        self.num_of_semitones = num_of_semitones % 1258        self.num_of_semitones = num_of_semitones % 12
5959
60    def __str__(self):60    def __str__(self):
61        return self.INTERVALS[self.num_of_semitones]61        return self.INTERVALS[self.num_of_semitones]
6262
63    def __add__(self, other):63    def __add__(self, other):
64        """Add Interval + Interval."""64        """Add Interval + Interval."""
65        if isinstance(other, Tone):65        if isinstance(other, Tone):
66            raise TypeError("Invalid operation")66            raise TypeError("Invalid operation")
67        elif isinstance(other, Interval):67        elif isinstance(other, Interval):
68            return Interval(self.num_of_semitones + other.num_of_semitones)68            return Interval(self.num_of_semitones + other.num_of_semitones)
6969
70    def __sub__(self, other):70    def __sub__(self, other):
71        """Invalidate subtraction Interval - Tone."""71        """Invalidate subtraction Interval - Tone."""
72        if isinstance(other, Tone):72        if isinstance(other, Tone):
73            raise TypeError("Invalid operation")73            raise TypeError("Invalid operation")
7474
75    def __neg__(self):75    def __neg__(self):
76        """Support the creation of such intervals: -Interval(5)."""76        """Support the creation of such intervals: -Interval(5)."""
77        return Interval(len(Tone.TONES) - self.num_of_semitones)77        return Interval(len(Tone.TONES) - self.num_of_semitones)
7878
79    @classmethod79    @classmethod
80    def init_from_tones(cls, tone1, tone2):80    def init_from_tones(cls, tone1, tone2):
81        """Initialize interval from 2 tones."""81        """Initialize interval from 2 tones."""
82        return cls(abs(Tone.TONES.index(tone1) - Tone.TONES.index(tone2)))82        return cls(abs(Tone.TONES.index(tone1) - Tone.TONES.index(tone2)))
8383
8484
85class Chord:85class Chord:
86    """Implement a chord."""86    """Implement a chord."""
8787
88    INDEX_MINOR = 3 # index in interval.INTERVALS for minor 3rd88    INDEX_MINOR = 3 # index in interval.INTERVALS for minor 3rd
89    INDEX_MAJOR = 4 # index in interval.INTERVALS for major 3rd89    INDEX_MAJOR = 4 # index in interval.INTERVALS for major 3rd
9090
91    def __init__(self, main_tone, *args):91    def __init__(self, main_tone, *args):
92        self.main_tone = main_tone92        self.main_tone = main_tone
93        self.tones = list(args)93        self.tones = list(args)
94        self.tones.append(self.main_tone)94        self.tones.append(self.main_tone)
95        self._sort_tones()95        self._sort_tones()
96        if len(self.tones) < 2:96        if len(self.tones) < 2:
97            raise TypeError('Cannot have a chord made of only 1 unique tone')97            raise TypeError('Cannot have a chord made of only 1 unique tone')
9898
99    def __str__(self):99    def __str__(self):
100        return f'{'-'.join(map(str, self.tones))}'100        return f'{'-'.join(map(str, self.tones))}'
101101
102    def __add__(self, other):102    def __add__(self, other):
103        """Add Chord + Tone or Chord + Chord."""103        """Add Chord + Tone or Chord + Chord."""
104        if isinstance(other, Tone):104        if isinstance(other, Tone):
105            return Chord(self.main_tone, *(self.tones + [other]))105            return Chord(self.main_tone, *(self.tones + [other]))
106        if isinstance(other, Chord):106        if isinstance(other, Chord):
107            return Chord(self.main_tone, *(self.tones + other.tones))107            return Chord(self.main_tone, *(self.tones + other.tones))
108108
109    def __sub__(self, other):109    def __sub__(self, other):
110        """Subtract Chord - Tone."""110        """Subtract Chord - Tone."""
111        if isinstance(other, Tone):111        if isinstance(other, Tone):
112            if other in self.tones:112            if other in self.tones:
nn113                new_tones = self.tones[:]
113                self.tones.remove(other)114                new_tones.remove(other)
114                if len(self.tones) < 2:
115                    raise TypeError('Cannot have a chord made of only 1 unique tone')
116                if other == self.main_tone:
117                    self.main_tone = self.tones[0]115                new_main_tone = new_tones[0]
118                return self116                return Chord(new_main_tone, *new_tones)
119            else:117            else:
120                raise TypeError(f'Cannot remove tone {other} from chord {self}')118                raise TypeError(f'Cannot remove tone {other} from chord {self}')
121119
122    def _sort_tones(self):120    def _sort_tones(self):
123        """Sort all tones in chord relative to the main tone."""121        """Sort all tones in chord relative to the main tone."""
124        main_tone_idx = Tone.TONES.index(self.main_tone)122        main_tone_idx = Tone.TONES.index(self.main_tone)
125        tones_unique = set(self.tones) # because of the hash func the tones are saved as strings123        tones_unique = set(self.tones) # because of the hash func the tones are saved as strings
126        all_tones_sorted = Tone.TONES[main_tone_idx:] + Tone.TONES[:main_tone_idx]124        all_tones_sorted = Tone.TONES[main_tone_idx:] + Tone.TONES[:main_tone_idx]
127        self.tones = list(map(Tone, filter(lambda tone: tone in tones_unique, all_tones_sorted)))125        self.tones = list(map(Tone, filter(lambda tone: tone in tones_unique, all_tones_sorted)))
128126
129    def _check_for_special_tone(self, idx):127    def _check_for_special_tone(self, idx):
130        """Check if there is any tone that makes a special tone with root based on idx given."""128        """Check if there is any tone that makes a special tone with root based on idx given."""
131        return any(str(Interval.init_from_tones(self.main_tone, tone)) == Interval.INTERVALS[idx] for tone in self.tones)129        return any(str(Interval.init_from_tones(self.main_tone, tone)) == Interval.INTERVALS[idx] for tone in self.tones)
132130
133    def is_minor(self):131    def is_minor(self):
134        """Check if chord is minor."""132        """Check if chord is minor."""
135        return self._check_for_special_tone(self.INDEX_MINOR)133        return self._check_for_special_tone(self.INDEX_MINOR)
136134
137    def is_major(self):135    def is_major(self):
138        """Check if chord is major."""136        """Check if chord is major."""
139        return self._check_for_special_tone(self.INDEX_MAJOR)137        return self._check_for_special_tone(self.INDEX_MAJOR)
140138
141    def is_power_chord(self):139    def is_power_chord(self):
142        """Check if tone is power chord."""140        """Check if tone is power chord."""
143        return not self.is_minor() and not self.is_major()141        return not self.is_minor() and not self.is_major()
144142
145    def transposed(self, interval):143    def transposed(self, interval):
146        """Transpose by interval."""144        """Transpose by interval."""
147        return Chord(self.main_tone + interval, *list(map(lambda tone: tone + interval, self.tones)))145        return Chord(self.main_tone + interval, *list(map(lambda tone: tone + interval, self.tones)))
tt146 
147 
148c_power_chord = Chord(Tone("C"), Tone("G"))
149result_chord = c_power_chord - Tone("E")
150# C-G
Legends
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op

f1class Tone:f1class Tone:
n2    """Implements a tone"""n2    """Implement a tone."""
33
4    TONES = ('C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B')4    TONES = ('C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B')
55
6    def __key(self):6    def __key(self):
n7        """Create private key so tone is hashable"""n7        """Create private key so tone is hashable."""
8        return str(self.tone)8        return str(self.tone)
99
10    def __init__(self, tone):10    def __init__(self, tone):
11        self.tone = tone11        self.tone = tone
1212
13    def __str__(self):13    def __str__(self):
14        return self.tone14        return self.tone
1515
16    def __eq__(self, other):16    def __eq__(self, other):
17        return str(self) == str(other)17        return str(self) == str(other)
1818
19    def __hash__(self):19    def __hash__(self):
20        return hash(self.__key())20        return hash(self.__key())
2121
22    def __add__(self, other):22    def __add__(self, other):
n23        """Addition supports Tone + Tone and Tone + Interval"""n23        """Add Tone + Tone or Tone + Interval."""
24        if isinstance(other, Tone):24        if isinstance(other, Tone):
25            return Chord(self, other)25            return Chord(self, other)
26        elif isinstance(other, Interval):26        elif isinstance(other, Interval):
27            idx = (Tone.TONES.index(self) + other.num_of_semitones) % len(Tone.TONES)27            idx = (Tone.TONES.index(self) + other.num_of_semitones) % len(Tone.TONES)
28            return Tone(Tone.TONES[idx])28            return Tone(Tone.TONES[idx])
2929
30    def __sub__(self, other):30    def __sub__(self, other):
n31        """Subtraction supports Tone - Tone and Tone - Interval"""n31        """Subtract Tone - Tone or Tone - Interval."""
32        if isinstance(other, Tone):32        if isinstance(other, Tone):
33            return Interval((Tone.TONES.index(self) - Tone.TONES.index(other)) % len(Tone.TONES))33            return Interval((Tone.TONES.index(self) - Tone.TONES.index(other)) % len(Tone.TONES))
34        elif isinstance(other, Interval):34        elif isinstance(other, Interval):
35            idx = (Tone.TONES.index(self) - other.num_of_semitones) % len(Tone.TONES)35            idx = (Tone.TONES.index(self) - other.num_of_semitones) % len(Tone.TONES)
36            return Tone(Tone.TONES[idx])36            return Tone(Tone.TONES[idx])
3737
3838
39class Interval:39class Interval:
n40    """Implements an interval"""n40    """Implement an interval."""
4141
42    INTERVALS = {42    INTERVALS = {
43        0: 'unison',43        0: 'unison',
44        1: 'minor 2nd',44        1: 'minor 2nd',
45        2: 'major 2nd',45        2: 'major 2nd',
46        3: 'minor 3rd',46        3: 'minor 3rd',
47        4: 'major 3rd',47        4: 'major 3rd',
48        5: 'perfect 4th',48        5: 'perfect 4th',
49        6: 'diminished 5th',49        6: 'diminished 5th',
50        7: 'perfect 5th',50        7: 'perfect 5th',
51        8: 'minor 6th',51        8: 'minor 6th',
52        9: 'major 6th',52        9: 'major 6th',
53        10: 'minor 7th',53        10: 'minor 7th',
54        11: 'major 7th'54        11: 'major 7th'
55    }55    }
5656
57    def __init__(self, num_of_semitones):57    def __init__(self, num_of_semitones):
58        self.num_of_semitones = num_of_semitones % 1258        self.num_of_semitones = num_of_semitones % 12
5959
60    def __str__(self):60    def __str__(self):
61        return self.INTERVALS[self.num_of_semitones]61        return self.INTERVALS[self.num_of_semitones]
6262
63    def __add__(self, other):63    def __add__(self, other):
n64        """Addition supports Interval + Interval"""n64        """Add Interval + Interval."""
65        if isinstance(other, Tone):65        if isinstance(other, Tone):
66            raise TypeError("Invalid operation")66            raise TypeError("Invalid operation")
67        elif isinstance(other, Interval):67        elif isinstance(other, Interval):
68            return Interval(self.num_of_semitones + other.num_of_semitones)68            return Interval(self.num_of_semitones + other.num_of_semitones)
6969
70    def __sub__(self, other):70    def __sub__(self, other):
n71        """Interval - Tone is invalid operation"""n71        """Invalidate subtraction Interval - Tone."""
72        if isinstance(other, Tone):72        if isinstance(other, Tone):
73            raise TypeError("Invalid operation")73            raise TypeError("Invalid operation")
7474
75    def __neg__(self):75    def __neg__(self):
n76        """Supports such intervals: -Interval(5)"""n76        """Support the creation of such intervals: -Interval(5)."""
77        return Interval(len(Tone.TONES) - self.num_of_semitones)77        return Interval(len(Tone.TONES) - self.num_of_semitones)
7878
79    @classmethod79    @classmethod
80    def init_from_tones(cls, tone1, tone2):80    def init_from_tones(cls, tone1, tone2):
n81        """Initialize interval from 2 tones"""n81        """Initialize interval from 2 tones."""
82        return cls(abs(Tone.TONES.index(tone1) - Tone.TONES.index(tone2)))82        return cls(abs(Tone.TONES.index(tone1) - Tone.TONES.index(tone2)))
8383
8484
85class Chord:85class Chord:
n86    """Implements a chord"""n86    """Implement a chord."""
8787
88    INDEX_MINOR = 3 # index in interval.INTERVALS for minor 3rd88    INDEX_MINOR = 3 # index in interval.INTERVALS for minor 3rd
89    INDEX_MAJOR = 4 # index in interval.INTERVALS for major 3rd89    INDEX_MAJOR = 4 # index in interval.INTERVALS for major 3rd
9090
91    def __init__(self, main_tone, *args):91    def __init__(self, main_tone, *args):
92        self.main_tone = main_tone92        self.main_tone = main_tone
93        self.tones = list(args)93        self.tones = list(args)
94        self.tones.append(self.main_tone)94        self.tones.append(self.main_tone)
95        self._sort_tones()95        self._sort_tones()
96        if len(self.tones) < 2:96        if len(self.tones) < 2:
97            raise TypeError('Cannot have a chord made of only 1 unique tone')97            raise TypeError('Cannot have a chord made of only 1 unique tone')
9898
99    def __str__(self):99    def __str__(self):
100        return f'{'-'.join(map(str, self.tones))}'100        return f'{'-'.join(map(str, self.tones))}'
101101
102    def __add__(self, other):102    def __add__(self, other):
n103        """Addition supports Chord + Tone and Chord + Chord"""n103        """Add Chord + Tone or Chord + Chord."""
104        if isinstance(other, Tone):104        if isinstance(other, Tone):
105            return Chord(self.main_tone, *(self.tones + [other]))105            return Chord(self.main_tone, *(self.tones + [other]))
106        if isinstance(other, Chord):106        if isinstance(other, Chord):
107            return Chord(self.main_tone, *(self.tones + other.tones))107            return Chord(self.main_tone, *(self.tones + other.tones))
108108
109    def __sub__(self, other):109    def __sub__(self, other):
n110        """Subtraction supports Chord - Tone"""n110        """Subtract Chord - Tone."""
111        if isinstance(other, Tone):111        if isinstance(other, Tone):
112            if other in self.tones:112            if other in self.tones:
113                self.tones.remove(other)113                self.tones.remove(other)
114                if len(self.tones) < 2:114                if len(self.tones) < 2:
115                    raise TypeError('Cannot have a chord made of only 1 unique tone')115                    raise TypeError('Cannot have a chord made of only 1 unique tone')
116                if other == self.main_tone:116                if other == self.main_tone:
117                    self.main_tone = self.tones[0]117                    self.main_tone = self.tones[0]
118                return self118                return self
119            else:119            else:
120                raise TypeError(f'Cannot remove tone {other} from chord {self}')120                raise TypeError(f'Cannot remove tone {other} from chord {self}')
121121
122    def _sort_tones(self):122    def _sort_tones(self):
n123        """Sorts all tones in chord relative to the main tone"""n123        """Sort all tones in chord relative to the main tone."""
124        main_tone_idx = Tone.TONES.index(self.main_tone)124        main_tone_idx = Tone.TONES.index(self.main_tone)
125        tones_unique = set(self.tones) # because of the hash func the tones are saved as strings125        tones_unique = set(self.tones) # because of the hash func the tones are saved as strings
126        all_tones_sorted = Tone.TONES[main_tone_idx:] + Tone.TONES[:main_tone_idx]126        all_tones_sorted = Tone.TONES[main_tone_idx:] + Tone.TONES[:main_tone_idx]
127        self.tones = list(map(Tone, filter(lambda tone: tone in tones_unique, all_tones_sorted)))127        self.tones = list(map(Tone, filter(lambda tone: tone in tones_unique, all_tones_sorted)))
128128
129    def _check_for_special_tone(self, idx):129    def _check_for_special_tone(self, idx):
n130        """Checks if there is any tone that makes a special tone with root based on idx given"""n130        """Check if there is any tone that makes a special tone with root based on idx given."""
131        return any(str(Interval.init_from_tones(self.main_tone, tone)) == Interval.INTERVALS[idx] for tone in self.tones)131        return any(str(Interval.init_from_tones(self.main_tone, tone)) == Interval.INTERVALS[idx] for tone in self.tones)
132132
133    def is_minor(self):133    def is_minor(self):
n134        """Checks if chord is minor"""n134        """Check if chord is minor."""
135        return self._check_for_special_tone(self.INDEX_MINOR)135        return self._check_for_special_tone(self.INDEX_MINOR)
136136
137    def is_major(self):137    def is_major(self):
n138        """Checks if chord is major"""n138        """Check if chord is major."""
139        return self._check_for_special_tone(self.INDEX_MAJOR)139        return self._check_for_special_tone(self.INDEX_MAJOR)
140140
141    def is_power_chord(self):141    def is_power_chord(self):
n142        """Checks if tone is power chord"""n142        """Check if tone is power chord."""
143        return not self.is_minor() and not self.is_major()143        return not self.is_minor() and not self.is_major()
144144
145    def transposed(self, interval):145    def transposed(self, interval):
n146        """Transposes by interval"""n146        """Transpose by interval."""
147        return Chord(self.main_tone + interval, *list(map(lambda tone: tone + interval, self.tones)))147        return Chord(self.main_tone + interval, *list(map(lambda tone: tone + interval, self.tones)))
t148    t
Legends
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op

f1class Tone:f1class Tone:
nn2    """Implements a tone"""
23
n3    tones = ('C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B')n4    TONES = ('C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B')
45
5    def __key(self):6    def __key(self):
6        """Create private key so tone is hashable"""7        """Create private key so tone is hashable"""
7        return str(self.tone)8        return str(self.tone)
89
9    def __init__(self, tone):10    def __init__(self, tone):
10        self.tone = tone11        self.tone = tone
1112
12    def __str__(self):13    def __str__(self):
13        return self.tone14        return self.tone
1415
15    def __eq__(self, other):16    def __eq__(self, other):
16        return str(self) == str(other)17        return str(self) == str(other)
1718
18    def __hash__(self):19    def __hash__(self):
19        return hash(self.__key())20        return hash(self.__key())
2021
21    def __add__(self, other):22    def __add__(self, other):
22        """Addition supports Tone + Tone and Tone + Interval"""23        """Addition supports Tone + Tone and Tone + Interval"""
23        if isinstance(other, Tone):24        if isinstance(other, Tone):
24            return Chord(self, other)25            return Chord(self, other)
25        elif isinstance(other, Interval):26        elif isinstance(other, Interval):
n26            idx = (Tone.tones.index(self) + other.num_of_semitones) % len(Tone.tones)n27            idx = (Tone.TONES.index(self) + other.num_of_semitones) % len(Tone.TONES)
27            return Tone(Tone.tones[idx])28            return Tone(Tone.TONES[idx])
2829
29    def __sub__(self, other):30    def __sub__(self, other):
30        """Subtraction supports Tone - Tone and Tone - Interval"""31        """Subtraction supports Tone - Tone and Tone - Interval"""
31        if isinstance(other, Tone):32        if isinstance(other, Tone):
n32            return Interval((Tone.tones.index(self) - Tone.tones.index(other)) % len(Tone.tones))n33            return Interval((Tone.TONES.index(self) - Tone.TONES.index(other)) % len(Tone.TONES))
33        elif isinstance(other, Interval):34        elif isinstance(other, Interval):
n34            idx = (Tone.tones.index(self) - other.num_of_semitones) % len(Tone.tones)n35            idx = (Tone.TONES.index(self) - other.num_of_semitones) % len(Tone.TONES)
35            return Tone(Tone.tones[idx])36            return Tone(Tone.TONES[idx])
3637
3738
38class Interval:39class Interval:
nn40    """Implements an interval"""
3941
n40    intervals = {n42    INTERVALS = {
41        0: 'unison',43        0: 'unison',
42        1: 'minor 2nd',44        1: 'minor 2nd',
43        2: 'major 2nd',45        2: 'major 2nd',
44        3: 'minor 3rd',46        3: 'minor 3rd',
n45        4: 'major 3th',n47        4: 'major 3rd',
46        5: 'perfect 4th',48        5: 'perfect 4th',
47        6: 'diminished 5th',49        6: 'diminished 5th',
48        7: 'perfect 5th',50        7: 'perfect 5th',
49        8: 'minor 6th',51        8: 'minor 6th',
50        9: 'major 6th',52        9: 'major 6th',
51        10: 'minor 7th',53        10: 'minor 7th',
52        11: 'major 7th'54        11: 'major 7th'
53    }55    }
5456
n55    def __init__(self, *args):n57    def __init__(self, num_of_semitones):
56        if len(args) == 1:
57            self.num_of_semitones = args[0] % 1258        self.num_of_semitones = num_of_semitones % 12
58        elif len(args) == 2: # create interval by using two tones
59            self.num_of_semitones = abs(Tone.tones.index(args[0]) - Tone.tones.index(args[1]))
6059
61    def __str__(self):60    def __str__(self):
n62        return self.intervals[self.num_of_semitones]n61        return self.INTERVALS[self.num_of_semitones]
6362
64    def __add__(self, other):63    def __add__(self, other):
65        """Addition supports Interval + Interval"""64        """Addition supports Interval + Interval"""
66        if isinstance(other, Tone):65        if isinstance(other, Tone):
67            raise TypeError("Invalid operation")66            raise TypeError("Invalid operation")
68        elif isinstance(other, Interval):67        elif isinstance(other, Interval):
69            return Interval(self.num_of_semitones + other.num_of_semitones)68            return Interval(self.num_of_semitones + other.num_of_semitones)
7069
71    def __sub__(self, other):70    def __sub__(self, other):
72        """Interval - Tone is invalid operation"""71        """Interval - Tone is invalid operation"""
73        if isinstance(other, Tone):72        if isinstance(other, Tone):
74            raise TypeError("Invalid operation")73            raise TypeError("Invalid operation")
7574
76    def __neg__(self):75    def __neg__(self):
n77        self.num_of_semitones = len(Tone.tones) - self.num_of_semitonesn76        """Supports such intervals: -Interval(5)"""
78        return self77        return Interval(len(Tone.TONES) - self.num_of_semitones)
78 
79    @classmethod
80    def init_from_tones(cls, tone1, tone2):
81        """Initialize interval from 2 tones"""
82        return cls(abs(Tone.TONES.index(tone1) - Tone.TONES.index(tone2)))
7983
8084
81class Chord:85class Chord:
nn86    """Implements a chord"""
87 
88    INDEX_MINOR = 3 # index in interval.INTERVALS for minor 3rd
89    INDEX_MAJOR = 4 # index in interval.INTERVALS for major 3rd
8290
83    def __init__(self, main_tone, *args):91    def __init__(self, main_tone, *args):
84        self.main_tone = main_tone92        self.main_tone = main_tone
85        self.tones = list(args)93        self.tones = list(args)
86        self.tones.append(self.main_tone)94        self.tones.append(self.main_tone)
87        self._sort_tones()95        self._sort_tones()
88        if len(self.tones) < 2:96        if len(self.tones) < 2:
89            raise TypeError('Cannot have a chord made of only 1 unique tone')97            raise TypeError('Cannot have a chord made of only 1 unique tone')
9098
91    def __str__(self):99    def __str__(self):
92        return f'{'-'.join(map(str, self.tones))}'100        return f'{'-'.join(map(str, self.tones))}'
93101
94    def __add__(self, other):102    def __add__(self, other):
95        """Addition supports Chord + Tone and Chord + Chord"""103        """Addition supports Chord + Tone and Chord + Chord"""
96        if isinstance(other, Tone):104        if isinstance(other, Tone):
n97            self.tones.append(other)n105            return Chord(self.main_tone, *(self.tones + [other]))
98            self._sort_tones()
99            return self
100        if isinstance(other, Chord):106        if isinstance(other, Chord):
101            return Chord(self.main_tone, *(self.tones + other.tones))107            return Chord(self.main_tone, *(self.tones + other.tones))
102108
103    def __sub__(self, other):109    def __sub__(self, other):
104        """Subtraction supports Chord - Tone"""110        """Subtraction supports Chord - Tone"""
105        if isinstance(other, Tone):111        if isinstance(other, Tone):
106            if other in self.tones:112            if other in self.tones:
107                self.tones.remove(other)113                self.tones.remove(other)
108                if len(self.tones) < 2:114                if len(self.tones) < 2:
109                    raise TypeError('Cannot have a chord made of only 1 unique tone')115                    raise TypeError('Cannot have a chord made of only 1 unique tone')
110                if other == self.main_tone:116                if other == self.main_tone:
111                    self.main_tone = self.tones[0]117                    self.main_tone = self.tones[0]
112                return self118                return self
113            else:119            else:
114                raise TypeError(f'Cannot remove tone {other} from chord {self}')120                raise TypeError(f'Cannot remove tone {other} from chord {self}')
115121
116    def _sort_tones(self):122    def _sort_tones(self):
117        """Sorts all tones in chord relative to the main tone"""123        """Sorts all tones in chord relative to the main tone"""
n118        main_tone_idx = Tone.tones.index(self.main_tone)n124        main_tone_idx = Tone.TONES.index(self.main_tone)
119        tones_unique = set(self.tones) # because of the hash func the tones are saved as strings125        tones_unique = set(self.tones) # because of the hash func the tones are saved as strings
n120        all_tones_sorted = Tone.tones[main_tone_idx:] + Tone.tones[:main_tone_idx]n126        all_tones_sorted = Tone.TONES[main_tone_idx:] + Tone.TONES[:main_tone_idx]
121        self.tones = list(map(Tone, filter(lambda tone: tone in tones_unique, all_tones_sorted)))127        self.tones = list(map(Tone, filter(lambda tone: tone in tones_unique, all_tones_sorted)))
122128
n123    def _check_tone(self, idx):n129    def _check_for_special_tone(self, idx):
124        """Checks if there is any tone that makes a special tone with root based on idx given"""130        """Checks if there is any tone that makes a special tone with root based on idx given"""
n125        if any(str(Interval(self.main_tone, tone)) == Interval.intervals[idx] for tone in self.tones):n131        return any(str(Interval.init_from_tones(self.main_tone, tone)) == Interval.INTERVALS[idx] for tone in self.tones)
126            return True
127        return False
128132
129    def is_minor(self):133    def is_minor(self):
130        """Checks if chord is minor"""134        """Checks if chord is minor"""
n131        return self._check_tone(3)n135        return self._check_for_special_tone(self.INDEX_MINOR)
132136
133    def is_major(self):137    def is_major(self):
134        """Checks if chord is major"""138        """Checks if chord is major"""
n135        return self._check_tone(4)n139        return self._check_for_special_tone(self.INDEX_MAJOR)
136140
137    def is_power_chord(self):141    def is_power_chord(self):
138        """Checks if tone is power chord"""142        """Checks if tone is power chord"""
139        return not self.is_minor() and not self.is_major()143        return not self.is_minor() and not self.is_major()
140144
141    def transposed(self, interval):145    def transposed(self, interval):
142        """Transposes by interval"""146        """Transposes by interval"""
143        return Chord(self.main_tone + interval, *list(map(lambda tone: tone + interval, self.tones)))147        return Chord(self.main_tone + interval, *list(map(lambda tone: tone + interval, self.tones)))
tt148    
Legends
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op