Source code for parker.scales

from .mixins import Aug
from .mixins import Dim
from .mixins import OctaveMixin
from .notes import Note
from .notes import NoteGroupBase


[docs]class Scale(NoteGroupBase, OctaveMixin): """ Source: https://en.wikipedia.org/wiki/Scale_(music) """ ASCENDING = 'ascending' DESCENDING = 'descending' ORDER_CHOICES = [ASCENDING, DESCENDING] generic_notes = [] def __init__(self, root, order=None): """ Create scales """ self.notes = [] self.root = Note(root) if not order: self.order = self.ASCENDING elif order in self.ORDER_CHOICES: self.order = order else: raise Exception("Order must be one of: {0}".format( self.ORDER_CHOICES)) self.build_scale() def __str__(self): return str(self.root) def __repr__(self): if self.order == self.DESCENDING: return "{0}('{1}', order='{2}')".format(type(self).__name__, str(self), self.DESCENDING) else: return "{0}('{1}')".format(type(self).__name__, str(self)) def __eq__(self, other): # Scales must be of the same class or derived from the Scale class if not (isinstance(other, self.__class__) or isinstance(other, Scale)): return False if self.__dict__ == other.__dict__: return True # Scales are identical if their generic notes are also identical if self.generic_notes == other.generic_notes: return True return False
[docs] def build_scale(self): intervals = self.intervals self.notes = self.root.transpose_list(intervals) if self.order == self.DESCENDING: self.notes.reverse() # Get a list of the notes without the octave generic_notes = [n.normalize(use_sharps=True) for n in self.notes] self.generic_notes = set(generic_notes)
@property def intervals(self): return [0]
[docs] def get_whole_half_construction(self): intervals = self.intervals if len(intervals) < 2: return [] wh = [] current = intervals[0] for i in intervals[1:]: if isinstance(i, Aug): diff = i.amount + 1 - current current = i.amount + 1 elif isinstance(i, Dim): diff = i.amount - 1 - current current = i.amount - 1 else: diff = i - current current = i if diff == 2: wh.append('W') elif diff == 1: wh.append('H') else: wh.append(diff) return wh
[docs] def get_tone_semitone_construction(self): wh = self.get_whole_half_construction() ts = [] for current in wh: if current == 'W': ts.append('T') elif current == 'H': ts.append('s') else: ts.append(current) return ts
[docs] def is_generic_note_in_scale(self, note): if isinstance(note, str): return note in self.generic_notes if isinstance(note, Note): gen_note = Note(int(note)).generalize() return gen_note in self.generic_notes
[docs]class Diatonic(Scale): @classmethod def _generate_intervals(cls, tonic, aug_dim_cls=None): """ Create the diatonic interval from the tonic. This uses the interavl T-T-s-T-T-T-s (or W-W-h-W-W-W-h) as the basis for creating a diatonic scale. It returns only 7 notes instead of the full octave. aug_dim_cls - a list of tuples that includes the index and the class to apply. For instance, in Lydian scales you want to Augment the 4th note, so you'd give the tuple (3, Aug) in the list. Source: https://en.wikipedia.org/wiki/Diatonic_scale#Modes """ if tonic not in range(1, 8): raise Exception("Tonic must be a number between 1 and 7") rot = tonic - 1 tonal_sequence = [2, 2, 1, 2, 2, 2, 1] seq = tonal_sequence[rot:] + tonal_sequence[:rot] interval = [0] for i, s in enumerate(seq): interval.append(s + interval[i]) # Apply Aug and Dim classes # This logic is a bit convoluted and should be cleaned up if not aug_dim_cls: aug_dim_cls = [] for i, cls in aug_dim_cls: if cls == Aug: interval[i] = cls(interval[i] - 1) elif cls == Dim: interval[i] = cls(interval[i] + 1) else: msg = "Must use Aug or Dim classes for modifying intervals" raise Exception(msg) return interval
[docs]class Ionian(Diatonic): @property def intervals(self): return self._generate_intervals(1)
[docs]class Major(Ionian): pass
[docs]class HarmonicMajor(Major): @property def intervals(self): intervals = self._generate_intervals(1) intervals[6] -= 1 return intervals
[docs]class Dorian(Diatonic): @property def intervals(self): return self._generate_intervals(2)
[docs]class Phrygian(Diatonic): @property def intervals(self): return self._generate_intervals(3)
class PhrygianDominant(Diatonic): @property def intervals(self): intervals = self._generate_intervals(3) intervals[2] += 1 return intervals
[docs]class MedievalLydian(Diatonic): @property def intervals(self): return self._generate_intervals(4)
[docs]class Lydian(Diatonic): """ The Lydian scale can be described as a major scale with the fourth scale degree raised a semitone, e.g., a C-major scale with an F# rather than F-natural. Source: https://en.wikipedia.org/wiki/Lydian_mode """ @property def intervals(self): return self._generate_intervals(4, aug_dim_cls=[(3, Aug)])
[docs]class Mixolydian(Diatonic): @property def intervals(self): return self._generate_intervals(5)
[docs]class Dominant(Mixolydian): pass
[docs]class Aeolian(Diatonic): @property def intervals(self): return self._generate_intervals(6)
[docs]class Minor(Aeolian): pass
class NaturalMinor(Minor): pass
[docs]class HarmonicMinor(Minor): @property def intervals(self): intervals = self._generate_intervals(6) intervals[6] += 1 return intervals
class MelodicMinor(Minor): @property def intervals(self): intervals = self._generate_intervals(6) intervals[5] += 1 intervals[6] += 1 return intervals
[docs]class Locrian(Diatonic): @property def intervals(self): return self._generate_intervals(7)
[docs]class SuperLocrian(Diatonic): @property def intervals(self): intervals = self._generate_intervals(7) intervals[3] -= 1 return intervals
[docs]class MajorPentatonic(Major): """ Major Pentatonic drops 4th and 7th from the Diatonic Major and consists of only 5 notes. """ @property def intervals(self): intervals = super(MajorPentatonic, self).intervals intervals.pop(6) intervals.pop(3) return intervals[0:5]
[docs]class MinorPentatonic(Minor): """ Minor Pentatonic drops 2nd and 6th from the Diatonic Minor and consists of only 5 notes. """ @property def intervals(self): intervals = super(MinorPentatonic, self).intervals intervals.pop(5) intervals.pop(1) return intervals[0:5]
[docs]class MajorBlues(MajorPentatonic): """ Major Blues is the same as the Major Pentatonic but it adds a diminished 4th and consists of 6 notes. """ @property def intervals(self): intervals = super(MajorBlues, self).intervals intervals.insert(2, Dim(4)) return intervals
[docs]class MinorBlues(MinorPentatonic): """ Minor Blues is the same as the Minor Pentatonic but it adds an augmented 5th and consists of 6 notes. """ @property def intervals(self): intervals = super(MinorBlues, self).intervals intervals.insert(3, Aug(5)) return intervals
[docs]class Altered(Scale): @classmethod def _generate_intervals(cls, tonic): """ Source: https://en.wikipedia.org/wiki/Altered_scale s-T-s-T-T-T-T """ if tonic not in range(1, 8): raise Exception("Tonic must be a number between 1 and 7") rot = tonic - 1 tonal_sequence = [1, 2, 1, 2, 2, 2, 2] seq = tonal_sequence[rot:] + tonal_sequence[:rot] interval = [0] for i, s in enumerate(seq): interval.append(s + interval[i]) return interval @property def intervals(self): return self._generate_intervals(1)
[docs]class DiminishedWholeTone(Altered): pass
[docs]class Chromatic(Scale): @property def intervals(self): return range(0, 13)
[docs]class Octatonic(Scale): @classmethod def _generate_intervals(cls, tonic): """ Source: https://en.wikipedia.org/wiki/Octatonic_scale """ if tonic not in range(1, 3): raise Exception("Tonic must be a number between 1 and 2") rot = tonic - 1 tonal_sequence = [1, 2, 1, 2, 1, 2, 1, 2] seq = tonal_sequence[rot:] + tonal_sequence[:rot] interval = [0] for i, s in enumerate(seq): interval.append(s + interval[i]) return interval
[docs]class OctatonicModeOne(Octatonic): @property def intervals(self): return self._generate_intervals(1)
class HalfWholeDiminished(OctatonicModeOne): pass
[docs]class OctatonicModeTwo(Octatonic): @property def intervals(self): return self._generate_intervals(2)
class WholeHalfDiminished(OctatonicModeTwo): pass def circle_of_fifths(): root = 'C' scales = [] while len(scales) < 8: scale = Major(Note(root)) scales.append(scale) root = scale[4].generalize() return scales def circle_of_fourths(): root = 'C' scales = [] while len(scales) < 8: scale = Major(Note(root)) scales.append(scale) root = scale[3].generalize() return scales def _scale_creator(scale_cls, root='C4'): scale = Chromatic(root) scales = [] for note in scale.notes[:-1]: scales.append(scale_cls(note)) return scales def dorian_scales(root='C4'): return _scale_creator(Dorian, root=root) def mixolydian_scales(root='C4'): return _scale_creator(Mixolydian, root=root) def major_scales(root='C4'): return _scale_creator(Major, root=root) def minor_scales(root='C4'): return _scale_creator(Minor, root=root) def major_pentatonic_scales(root='C4'): return _scale_creator(MajorPentatonic, root=root) def minor_pentatonic_scales(root='C4'): return _scale_creator(MinorPentatonic, root=root) def major_blues_scales(root='C4'): return _scale_creator(MajorBlues, root=root) def minor_blues_scales(root='C4'): return _scale_creator(MinorBlues, root=root)