f | TONES = ('C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B') | f | TONES = ('C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B') |
| INTERVALS = ('unison', 'minor 2nd', 'major 2nd', 'minor 3rd', 'major 3rd', | | INTERVALS = ('unison', 'minor 2nd', 'major 2nd', 'minor 3rd', 'major 3rd', |
| 'perfect 4th', 'diminished 5th', 'perfect 5th', | | 'perfect 4th', 'diminished 5th', 'perfect 5th', |
| 'minor 6th', 'major 6th', 'minor 7th', 'major 7th') | | 'minor 6th', 'major 6th', 'minor 7th', 'major 7th') |
| | | |
| | | |
| class Tone(int): | | class Tone(int): |
| """Represent a musical tone.""" | | """Represent a musical tone.""" |
| | | |
| def __new__(cls, tone): | | def __new__(cls, tone): |
| """Create instance based on string or normalized int.""" | | """Create instance based on string or normalized int.""" |
| if isinstance(tone, str): | | if isinstance(tone, str): |
| tone = TONES.index(tone) | | tone = TONES.index(tone) |
| if isinstance(tone, int): | | if isinstance(tone, int): |
| tone = tone % len(TONES) | | tone = tone % len(TONES) |
| return super().__new__(cls, tone) | | return super().__new__(cls, tone) |
| | | |
| def __str__(self): | | def __str__(self): |
| """String representation of a tone.""" | | """String representation of a tone.""" |
| return TONES[self] | | return TONES[self] |
| | | |
| def __add__(self, other): | | def __add__(self, other): |
| """Define adding something to tone.""" | | """Define adding something to tone.""" |
| if isinstance(other, Tone): | | if isinstance(other, Tone): |
| return Chord(Tone(self), Tone(other)) | | return Chord(Tone(self), Tone(other)) |
| if isinstance(other, Interval): | | if isinstance(other, Interval): |
| return Tone(int(self) + int(other)) | | return Tone(int(self) + int(other)) |
| return super().__add__(other) | | return super().__add__(other) |
| | | |
| def __sub__(self, other): | | def __sub__(self, other): |
| """Define subtracting something from tone.""" | | """Define subtracting something from tone.""" |
| if isinstance(other, Tone): | | if isinstance(other, Tone): |
| return Interval(int(self) - int(other)) | | return Interval(int(self) - int(other)) |
| if isinstance(other, Interval): | | if isinstance(other, Interval): |
| return Tone(int(self) - int(other)) | | return Tone(int(self) - int(other)) |
| return super().__sub__(other) | | return super().__sub__(other) |
| | | |
| | | |
| class Interval(int): | | class Interval(int): |
| """Represent a musical interval.""" | | """Represent a musical interval.""" |
| | | |
| def __new__(cls, length): | | def __new__(cls, length): |
| """Create instance based on normalized int.""" | | """Create instance based on normalized int.""" |
| if isinstance(length, int): | | if isinstance(length, int): |
| length = length % len(INTERVALS) | | length = length % len(INTERVALS) |
| return super().__new__(cls, length) | | return super().__new__(cls, length) |
| | | |
| def __str__(self): | | def __str__(self): |
| """String representation of an interval.""" | | """String representation of an interval.""" |
| return INTERVALS[self] | | return INTERVALS[self] |
| | | |
| def __neg__(self): | | def __neg__(self): |
| """Negative value of an interval.""" | | """Negative value of an interval.""" |
| return Interval(-int(self)) | | return Interval(-int(self)) |
| | | |
| def __add__(self, other): | | def __add__(self, other): |
| """Define adding something to interval.""" | | """Define adding something to interval.""" |
| if isinstance(other, Tone): | | if isinstance(other, Tone): |
| raise TypeError('Invalid operation') | | raise TypeError('Invalid operation') |
| if isinstance(other, Interval): | | if isinstance(other, Interval): |
| return Interval(int(self) + int(other)) | | return Interval(int(self) + int(other)) |
| return super().__add__(other) | | return super().__add__(other) |
| | | |
| def __sub__(self, other): | | def __sub__(self, other): |
| """Define subtacting something from interval.""" | | """Define subtacting something from interval.""" |
| if isinstance(other, Tone): | | if isinstance(other, Tone): |
| raise TypeError('Invalid operation') | | raise TypeError('Invalid operation') |
| if isinstance(other, Interval): | | if isinstance(other, Interval): |
| return Interval(int(self) - int(other)) | | return Interval(int(self) - int(other)) |
| return super().__add__(other) | | return super().__add__(other) |
| | | |
| | | |
| class Chord: | | class Chord: |
| """Represent a musical chord.""" | | """Represent a musical chord.""" |
| | | |
| MIN_TONES = 2 | | MIN_TONES = 2 |
| MINOR_3RD = 'minor 3rd' | | MINOR_3RD = 'minor 3rd' |
| MAJOR_3RD = 'major 3rd' | | MAJOR_3RD = 'major 3rd' |
| MINOR_3RD_INTERVAL = Interval(INTERVALS.index(MINOR_3RD)) | | MINOR_3RD_INTERVAL = Interval(INTERVALS.index(MINOR_3RD)) |
| MAJOR_3RD_INTERVAL = Interval(INTERVALS.index(MAJOR_3RD)) | | MAJOR_3RD_INTERVAL = Interval(INTERVALS.index(MAJOR_3RD)) |
| | | |
| def __init__(self, root, *rest): | | def __init__(self, root, *rest): |
| """Initialize, keeping all tones passed as input.""" | | """Initialize, keeping all tones passed as input.""" |
| sorted_tones = sorted(set((root,) + rest), | | sorted_tones = sorted(set((root,) + rest), |
| key=lambda tone: TONES.index(str(tone))) | | key=lambda tone: TONES.index(str(tone))) |
| root_index = sorted_tones.index(root) | | root_index = sorted_tones.index(root) |
| self.tones = sorted_tones[root_index:] + sorted_tones[:root_index] | | self.tones = sorted_tones[root_index:] + sorted_tones[:root_index] |
| if len(self.tones) < self.MIN_TONES: | | if len(self.tones) < self.MIN_TONES: |
| raise TypeError('Cannot have a chord made of only 1 unique tone') | | raise TypeError('Cannot have a chord made of only 1 unique tone') |
| | | |
| def __str__(self): | | def __str__(self): |
| """String representation of the chord.""" | | """String representation of the chord.""" |
| return "-".join(map(str, self.tones)) | | return "-".join(map(str, self.tones)) |
| | | |
| def __add__(self, other): | | def __add__(self, other): |
| """Define adding something to a chord.""" | | """Define adding something to a chord.""" |
| if isinstance(other, Tone): | | if isinstance(other, Tone): |
| # Root is also found in self.tones, but who cares... | | # Root is also found in self.tones, but who cares... |
| # Initializing a Chord will remove the duplicates anyway. | | # Initializing a Chord will remove the duplicates anyway. |
| # No need to duplicate this behaviour here. | | # No need to duplicate this behaviour here. |
| return Chord(self.root, *(self.tones + [other])) | | return Chord(self.root, *(self.tones + [other])) |
| if isinstance(other, Chord): | | if isinstance(other, Chord): |
| return Chord(*(self.tones + other.tones)) | | return Chord(*(self.tones + other.tones)) |
| raise NotImplementedError | | raise NotImplementedError |
| | | |
| def __sub__(self, other): | | def __sub__(self, other): |
| """Define subtracting something from a chord.""" | | """Define subtracting something from a chord.""" |
| if isinstance(other, Tone): | | if isinstance(other, Tone): |
| if other not in self.tones: | | if other not in self.tones: |
| raise TypeError(f'Cannot remove tone {other} from chord {self}') | | raise TypeError(f'Cannot remove tone {other} from chord {self}') |
| return Chord(*list(filter(other.__ne__, self.tones))) | | return Chord(*list(filter(other.__ne__, self.tones))) |
| raise NotImplementedError | | raise NotImplementedError |
| | | |
| @property | | @property |
| def root(self): | | def root(self): |
| """Get root of the chord.""" | | """Get root of the chord.""" |
| return self.tones[0] | | return self.tones[0] |
| | | |
| def transposed(self, interval): | | def transposed(self, interval): |
| """Transpose a chord by an interval.""" | | """Transpose a chord by an interval.""" |
n | return Chord(*list(map(Tone, map(interval.__radd__, self.tones)))) | n | return Chord(*map(Tone, map(interval.__radd__, self.tones))) |
| | | |
| def is_minor(self): | | def is_minor(self): |
| """Return boolean based on whether the chord is minor.""" | | """Return boolean based on whether the chord is minor.""" |
| return self.root + self.MINOR_3RD_INTERVAL in self.tones | | return self.root + self.MINOR_3RD_INTERVAL in self.tones |
| | | |
| def is_major(self): | | def is_major(self): |
| """Return boolean based on whether the chord is major.""" | | """Return boolean based on whether the chord is major.""" |
| return self.root + self.MAJOR_3RD_INTERVAL in self.tones | | return self.root + self.MAJOR_3RD_INTERVAL in self.tones |
| | | |
| def is_power_chord(self): | | def is_power_chord(self): |
| """Return boolean based on whether the chord is power.""" | | """Return boolean based on whether the chord is power.""" |
| return not (self.is_minor() or self.is_major()) | | return not (self.is_minor() or self.is_major()) |
t | | t | |