diff --git a/mingus/containers/bar.py b/mingus/containers/bar.py index 51916e51..51772a45 100644 --- a/mingus/containers/bar.py +++ b/mingus/containers/bar.py @@ -49,12 +49,22 @@ def __init__(self, key="C", meter=(4, 4)): self.set_meter(meter) self.empty() + #Convenient Python operator overloading + + def __iadd__(self, other): + self.temporal_notes.append(other) + return self + def empty(self): """Empty the Bar, remove all the NoteContainers.""" + self.temporal_notes = [] self.bar = [] self.current_beat = 0.0 return self.bar + def set_chord_notes(self, chords): + self.chords = chords + def set_meter(self, meter): """Set the meter of this bar. @@ -77,6 +87,10 @@ def set_meter(self, meter): "Expecting a tuple." % meter ) + def extend(self, temporal_notes): + for note in temporal_notes: + self.temporal_notes.append(note) + def place_notes(self, notes, duration): """Place the notes on the current_beat. diff --git a/mingus/containers/note.py b/mingus/containers/note.py index 2b983f35..3bef14ec 100644 --- a/mingus/containers/note.py +++ b/mingus/containers/note.py @@ -51,6 +51,7 @@ def __init__(self, name="C", octave=4, dynamics=None): if dynamics is None: dynamics = {} if isinstance(name, six.string_types): + self.set_note(name, octave, dynamics) elif hasattr(name, "name"): # Hardcopy Note object @@ -66,6 +67,7 @@ def __init__(self, name="C", octave=4, dynamics=None): "Don't know what to do with name object: " "'%s'" % name ) + def set_channel(self, channel): self.channel = channel @@ -262,7 +264,7 @@ def from_shorthand(self, shorthand): def __int__(self): """Return the current octave multiplied by twelve and add notes.note_to_int to it. - + This means a C-0 returns 0, C-1 returns 12, etc. This method allows you to use int() on Notes. """ @@ -272,7 +274,7 @@ def __int__(self): res += 1 elif n == "b": res -= 1 - return res + return int(res) def __lt__(self, other): """Enable the comparing operators on Notes (>, <, \ ==, !=, >= and <=). @@ -293,6 +295,11 @@ def __eq__(self, other): """Compare Notes for equality by comparing their note values.""" if other is None: return False + + # added as was getting erros from play_Bar.set_key + if type(other) == str: + other = Note(other) + return int(self) == int(other) def __ne__(self, other): @@ -310,3 +317,4 @@ def __ge__(self, other): def __repr__(self): """Return a helpful representation for printing Note classes.""" return "'%s-%d'" % (self.name, self.octave) + diff --git a/mingus/containers/note_container.py b/mingus/containers/note_container.py index a48e6c5d..b370422b 100644 --- a/mingus/containers/note_container.py +++ b/mingus/containers/note_container.py @@ -23,7 +23,6 @@ from mingus.containers.mt_exceptions import UnexpectedObjectError import six - class NoteContainer(object): """A container for notes. diff --git a/mingus/containers/suite.py b/mingus/containers/suite.py index f63492c4..78aea00d 100644 --- a/mingus/containers/suite.py +++ b/mingus/containers/suite.py @@ -20,7 +20,6 @@ from mingus.containers.mt_exceptions import UnexpectedObjectError - class Suite(object): """A suite object. diff --git a/mingus/containers/track.py b/mingus/containers/track.py index 1a7e08e5..088b2dac 100644 --- a/mingus/containers/track.py +++ b/mingus/containers/track.py @@ -70,6 +70,7 @@ def add_notes(self, note, duration=None): "Note '%s' is not in range of the instrument (%s)" % (note, self.instrument) ) + if duration == None: duration = 4 diff --git a/mingus/core/chords.py b/mingus/core/chords.py index 317fd6e2..6c293448 100644 --- a/mingus/core/chords.py +++ b/mingus/core/chords.py @@ -53,6 +53,7 @@ * dominant_ninth * Elevenths * minor_eleventh + * minor_eleventh_flat_five (NEW) * eleventh * Thirteenths * minor_thirteenth @@ -109,61 +110,82 @@ _sevenths_cache = {} chord_shorthand_meaning = { # Triads Augmented chords Suspended chords Sevenths - # Sixths Ninths Elevenths Thirteenths Altered - # Chords Special - "m": " minor triad", - "M": " major triad", - "": " major triad", - "dim": " diminished triad", - "aug": " augmented triad", - "+": " augmented triad", - "7#5": " augmented minor seventh", - "M7+5": " augmented minor seventh", - "M7+": " augmented major seventh", - "m7+": " augmented minor seventh", - "7+": " augmented major seventh", - "sus47": " suspended seventh", - "7sus4": " suspended seventh", - "sus4": " suspended fourth triad", - "sus2": " suspended second triad", - "sus": " suspended fourth triad", - "11": " eleventh", - "add11": " eleventh", - "sus4b9": " suspended fourth ninth", - "susb9": " suspended fourth ninth", - "m7": " minor seventh", - "M7": " major seventh", - "dom7": " dominant seventh", - "7": " dominant seventh", - "m7b5": " half diminished seventh", - "dim7": " diminished seventh", - "m/M7": " minor/major seventh", - "mM7": " minor/major seventh", - "m6": " minor sixth", - "M6": " major sixth", - "6": " major sixth", - "6/7": " dominant sixth", - "67": " dominant sixth", - "6/9": " sixth ninth", - "69": " sixth ninth", - "9": " dominant ninth", - "add9": " dominant ninth", - "7b9": " dominant flat ninth", - "7#9": " dominant sharp ninth", - "M9": " major ninth", - "m9": " minor ninth", - "7#11": " lydian dominant seventh", - "m11": " minor eleventh", - "M13": " major thirteenth", - "m13": " minor thirteenth", - "13": " dominant thirteenth", - "add13": " dominant thirteenth", - "7b5": " dominant flat five", - "hendrix": " hendrix chord", - "7b12": " hendrix chord", - "5": " perfect fifth", -} + # Sixths Ninths Elevenths Thirteenths Altered + # Chords Special + 'm': ' minor triad', + 'M': ' major triad', + '': ' major triad', + 'dim': ' diminished triad', + 'aug': ' augmented triad', + '+': ' augmented triad', + '7#5': ' augmented minor seventh', + 'M7+5': ' augmented minor seventh', + 'M7+': ' augmented major seventh', + 'm7+': ' augmented minor seventh', + '7+': ' augmented major seventh', + 'sus47': ' suspended seventh', + '7sus4': ' suspended seventh', + 'sus4': ' suspended fourth triad', + 'sus2': ' suspended second triad', + 'sus': ' suspended fourth triad', + '11': ' eleventh', + 'add11': ' eleventh', + 'sus4b9': ' suspended fourth ninth', + 'susb9': ' suspended fourth ninth', + 'm7': ' minor seventh', + 'M7': ' major seventh', + 'dom7': ' dominant seventh', + '7': ' dominant seventh', + 'm7b5': ' half diminished seventh', + 'm11b5': ' minor eleven flat five', + 'dim7': ' diminished seventh', + 'm/M7': ' minor/major seventh', + 'mM7': ' minor/major seventh', + 'm6': ' minor sixth', + 'M6': ' major sixth', + '6': ' major sixth', + '6/7': ' dominant sixth', + '67': ' dominant sixth', + '6/9': ' sixth ninth', + '69': ' sixth ninth', + '9': ' dominant ninth', + 'add9': ' dominant ninth', + '7b9': ' dominant flat ninth', + '7#9': ' dominant sharp ninth', + 'M9': ' major ninth', + 'm9': ' minor ninth', + '7#11': ' lydian dominant seventh', + 'm11': ' minor eleventh', + 'M13': ' major thirteenth', + 'm13': ' minor thirteenth', + '13': ' dominant thirteenth', + 'add13': ' dominant thirteenth', + '7b5': ' dominant flat five', + 'hendrix': ' hendrix chord', + '7b12': ' hendrix chord', + '5': ' perfect fifth', + } + +def chord_note_and_family(chord_symbol): + """ + >>> chord_note_and_family("FM6") + ('F', 'M6') + >>> chord_note_and_family("Gbsus4") + ('Gb', 'sus4') + """ + from mingus.core.notes import reduce_accidentals + + + + chord_note=[chord_symbol[0]] + if '#' in chord_symbol or 'b' in chord_symbol: + for n in chord_symbol[1:]: + if n == '#' or n == 'b': + chord_note.append(n) + else: + break + return ''.join(chord_note), chord_symbol[len(chord_note):] def triad(note, key): """Return the triad on note in key as a list. @@ -233,6 +255,9 @@ def augmented_triad(note): ] +def create_dominant_seventh_symbol(note): + return determine_seventh(dominant_seventh(note), shorthand=True)[0] + def seventh(note, key): """Return the seventh chord on note in key. @@ -248,6 +273,7 @@ def sevenths(key): if key in _sevenths_cache: return _sevenths_cache[key] res = [seventh(x, key) for x in keys.get_notes(key)] + _sevenths_cache[key] = res return res @@ -301,7 +327,14 @@ def minor_seventh_flat_five(note): """ return half_diminished_seventh(note) + +def minor_eleventh_flat_five(note): + """Build a minor eleven flat five chord on note - new + """ + print('nb. this function (in mingus.core.chords) might need adjusting octave 4th -> 11th') + return half_diminished_seventh(note) + [intervals.major_fourth(note)] + def diminished_seventh(note): """Build a diminished seventh chord on note. @@ -555,9 +588,18 @@ def lydian_dominant_seventh(note): >>> lydian_dominant_seventh('C') ['C', 'E', 'G', 'Bb', 'F#'] """ - return dominant_seventh(note) + [notes.augment(intervals.perfect_fourth(note))] + return (dominant_seventh(note) + + [notes.augment(intervals.perfect_fourth(note))]) + + +def lydian_major_seventh(note): + """Build the lydian major seventh (M7#11) on note. + """ + return (major_seventh(note) + + [notes.augment(intervals.perfect_fourth(note))]) + def hendrix_chord(note): """Build the famous Hendrix chord (7b12). @@ -684,7 +726,7 @@ def I7(key): def ii(key): - return supertonic(key) + return supertonic(key) # note that progressions.to_chords['ii'] will come and pick this up (major 2nd chord due to mingus' classical approach). Instead, that fn has been re-written to try to except lower-casing and add a minor suffix def II(key): @@ -809,7 +851,7 @@ def from_shorthand(shorthand_string, slash=None): Recognised abbreviations: the letters "m" and "M" in the following abbreviations can always be substituted by respectively "min", "mi" or "-" and "maj" or "ma". - + Example: >>> from_shorthand('Amin7') == from_shorthand('Am7') True @@ -845,18 +887,18 @@ def from_shorthand(shorthand_string, slash=None): return [] # Shrink shorthand_string to a format recognised by chord_shorthand - shorthand_string = shorthand_string.replace("min", "m") - shorthand_string = shorthand_string.replace("mi", "m") - shorthand_string = shorthand_string.replace("-", "m") - shorthand_string = shorthand_string.replace("maj", "M") - shorthand_string = shorthand_string.replace("ma", "M") + shorthand_string = shorthand_string.replace('min', 'm') + shorthand_string = shorthand_string.replace('mi', 'm') + shorthand_string = shorthand_string.replace('-', 'm') + shorthand_string = shorthand_string.replace('maj', 'M') + shorthand_string = shorthand_string.replace('Maj', 'M') + shorthand_string = shorthand_string.replace('ma', 'M') + shorthand_string = shorthand_string.replace('o', 'dim').replace('ddimm','dom') # Co -> Cdim, Cdom -> Cddimm -> Cdom # Get the note name if not notes.is_valid_note(shorthand_string[0]): - raise NoteFormatError( - "Unrecognised note '%s' in chord '%s'" - % (shorthand_string[0], shorthand_string) - ) + raise NoteFormatError("Unrecognised note '{}' in chord '{}'".format(shorthand_string[0], shorthand_string)) + name = shorthand_string[0] # Look for accidentals @@ -901,11 +943,13 @@ def from_shorthand(shorthand_string, slash=None): if notes.is_valid_note(slash): res = [slash] + res else: + raise NoteFormatError( "Unrecognised note '%s' in slash chord'%s'" % (slash, slash + shorthand_string) ) elif isinstance(slash, list): + # Add polychords r = slash for n in res: @@ -914,9 +958,11 @@ def from_shorthand(shorthand_string, slash=None): return r return res else: + raise FormatError("Unknown shorthand: %s" % shorthand_string) + def determine(chord, shorthand=False, no_inversions=False, no_polychords=False): """Name a chord. @@ -1298,7 +1344,7 @@ def int_desc(tries): def determine_polychords(chord, shorthand=False): """Determine the polychords in chord. - + This function can handle anything from polychords based on two triads to 6 note extended chords. """ @@ -1340,52 +1386,56 @@ def determine_polychords(chord, shorthand=False): # A dictionairy that can be used to present chord abbreviations. This # dictionairy is also used in from_shorthand() chord_shorthand = { # Triads Augmented chords Suspended chords Sevenths Sixths - # Ninths Elevenths Thirteenths Altered Chords Special - "m": minor_triad, - "M": major_triad, - "": major_triad, - "dim": diminished_triad, - "aug": augmented_triad, - "+": augmented_triad, - "7#5": augmented_minor_seventh, - "M7+5": augmented_minor_seventh, - "M7+": augmented_major_seventh, - "m7+": augmented_minor_seventh, - "7+": augmented_major_seventh, - "sus47": suspended_seventh, - "sus4": suspended_fourth_triad, - "sus2": suspended_second_triad, - "sus": suspended_triad, - "11": eleventh, - "sus4b9": suspended_fourth_ninth, - "susb9": suspended_fourth_ninth, - "m7": minor_seventh, - "M7": major_seventh, - "7": dominant_seventh, - "dom7": dominant_seventh, - "m7b5": minor_seventh_flat_five, - "dim7": diminished_seventh, - "m/M7": minor_major_seventh, - "mM7": minor_major_seventh, - "m6": minor_sixth, - "M6": major_sixth, - "6": major_sixth, - "6/7": dominant_sixth, - "67": dominant_sixth, - "6/9": sixth_ninth, - "69": sixth_ninth, - "9": dominant_ninth, - "7b9": dominant_flat_ninth, - "7#9": dominant_sharp_ninth, - "M9": major_ninth, - "m9": minor_ninth, - "7#11": lydian_dominant_seventh, - "m11": minor_eleventh, - "M13": major_thirteenth, - "m13": minor_thirteenth, - "13": dominant_thirteenth, - "7b5": dominant_flat_five, - "hendrix": hendrix_chord, - "7b12": hendrix_chord, - "5": lambda x: [x, intervals.perfect_fifth(x)], -} + # Ninths Elevenths Thirteenths Altered Chords Special + 'm': minor_triad, + '-': minor_triad, + ' min': minor_triad, + 'M': major_triad, + '': major_triad, + 'dim': diminished_triad, + 'aug': augmented_triad, + '+': augmented_triad, + '7#5': augmented_minor_seventh, + 'M7+5': augmented_minor_seventh, + 'M7+': augmented_major_seventh, + 'm7+': augmented_minor_seventh, + '7+': augmented_major_seventh, + 'sus47': suspended_seventh, + 'sus4': suspended_fourth_triad, + 'sus2': suspended_second_triad, + 'sus': suspended_triad, + '11': eleventh, + 'sus4b9': suspended_fourth_ninth, + 'susb9': suspended_fourth_ninth, + 'm7': minor_seventh, + 'M7': major_seventh, + '7': dominant_seventh, + 'dom7': dominant_seventh, + 'm7b5': minor_seventh_flat_five, + 'm11b5': minor_eleventh_flat_five, + 'dim7': diminished_seventh, + 'm/M7': minor_major_seventh, + 'mM7': minor_major_seventh, + 'm6': minor_sixth, + 'M6': major_sixth, + '6': major_sixth, + '6/7': dominant_sixth, + '67': dominant_sixth, + '6/9': sixth_ninth, + '69': sixth_ninth, + '9': dominant_ninth, + '7b9': dominant_flat_ninth, + '7#9': dominant_sharp_ninth, + 'M9': major_ninth, + 'm9': minor_ninth, + '7#11': lydian_dominant_seventh, + 'M7(#11)': lydian_major_seventh, + 'm11': minor_eleventh, + 'M13': major_thirteenth, + 'm13': minor_thirteenth, + '13': dominant_thirteenth, + '7b5': dominant_flat_five, + 'hendrix': hendrix_chord, + '7b12': hendrix_chord, + '5': lambda x: [x, intervals.perfect_fifth(x)] + } \ No newline at end of file diff --git a/mingus/core/harmony.py b/mingus/core/harmony.py new file mode 100644 index 00000000..2af7cb6a --- /dev/null +++ b/mingus/core/harmony.py @@ -0,0 +1,53 @@ +from mingus.core import chords +from mingus.core.progressions import determine + +CIRCLE_OF_5THS = ['C', 'G', 'D', 'A', 'E', 'B', 'Gb', 'Db', 'Ab', 'Eb', 'Bb', 'F'] + +MODE_CHORD_FUNCTIONS = [chords.I7, chords.ii7, chords.iii7, chords.IV7, chords.V7, chords.vi7, chords.VII7] + +MODE_CHORD_TYPE = { + 'I': 'M7', + 'ii': 'm7', + 'iii': 'm7', + 'IV': 'M7', + 'V': '7', + 'vi': 'm7', + 'vii': 'm7b5' +} + +def analyze_7th_chord(chord): + """ + + :param chord: + :return: chord_notes, chord_type_full, chord_modes + + Example: + >>> chord_notes, chord_type_full, results = analyze_chord("G7") + >>> for chord_degree, key, harmonic_func_full in results: + ... print("{} (comprising {}) is a {} chord ({}) of the key of {}".format(chord_type_full,', '.join(chord_notes), + ... chord_degree, harmonic_func_full, + ... key)) + G dominant seventh (comprising G, B, D, F) is a V chord (dominant seventh) of the key of C + + """ + chord_notes = chords.from_shorthand(chord) + chord_type_full = chords.determine_seventh(chord_notes)[0] + chord_type_short = chords.determine_seventh(chord_notes, shorthand=True)[0] + type_key = 'M7' if chord_type_short[-2:] == 'M7' else ( + 'm7' if chord_type_short[-2:] == 'm7' else ('m7b5' if chord_type_short[-2:] == 'm7b5' else '7') + ) + + chord_modes = [] + for key in CIRCLE_OF_5THS: + harmonic_func = determine(chord_notes, key, shorthand=True)[0] + harmonic_func_full = determine(chord_notes, key, shorthand=False)[0] + chord_degree = harmonic_func[:-1] + if chord_degree in MODE_CHORD_TYPE: + func_chord_type = MODE_CHORD_TYPE[chord_degree] + if func_chord_type == type_key: + chord_modes.append((chord_degree, key, harmonic_func_full)) + return chord_notes, chord_type_full, chord_modes + +if __name__ == '__main__': + import doctest + doctest.testmod() diff --git a/mingus/core/intervals.py b/mingus/core/intervals.py index 706cbb05..386e20b4 100644 --- a/mingus/core/intervals.py +++ b/mingus/core/intervals.py @@ -239,7 +239,9 @@ def get_interval(note, interval, key="C"): This will produce mostly theoretical sound results, but you should use the minor and major functions to work around the corner cases. """ + intervals = [(notes.note_to_int(key) + x) % 12 for x in [0, 2, 4, 5, 7, 9, 11,]] + key_notes = keys.get_notes(key) for x in key_notes: if x[0] == note[0]: diff --git a/mingus/core/keys.py b/mingus/core/keys.py index 527cfd57..09d4162a 100644 --- a/mingus/core/keys.py +++ b/mingus/core/keys.py @@ -120,8 +120,10 @@ def get_notes(key="C"): result = [] # Calculate notes + altered_notes = [x[0] for x in get_key_signature_accidentals(key)] + if get_key_signature(key) < 0: symbol = "b" elif get_key_signature(key) > 0: @@ -178,6 +180,7 @@ def __init__(self, key="C"): else: self.mode = "major" + try: symbol = self.key[1] if symbol == "#": diff --git a/mingus/core/notes.py b/mingus/core/notes.py index 6c247ef1..f8247ba3 100644 --- a/mingus/core/notes.py +++ b/mingus/core/notes.py @@ -29,6 +29,7 @@ from mingus.core.mt_exceptions import NoteFormatError, RangeError, FormatError from six.moves import range + _note_dict = {"C": 0, "D": 2, "E": 4, "F": 5, "G": 7, "A": 9, "B": 11} fifths = ["F", "C", "G", "D", "A", "E", "B"] diff --git a/mingus/core/progressions.py b/mingus/core/progressions.py index b1137550..fdc18473 100644 --- a/mingus/core/progressions.py +++ b/mingus/core/progressions.py @@ -32,6 +32,7 @@ from mingus.core import notes from mingus.core import chords from mingus.core import intervals + import six from six.moves import range @@ -40,30 +41,33 @@ def to_chords(progression, key="C"): + """Convert a list of chord functions or a string to a list of chords. Examples: >>> to_chords(['I', 'V7']) - [['C', 'E', 'G'], ['G', 'B', 'D', 'F']] + [['C', 'E', 'G'], ['G', 'B', 'D', 'F']] >>> to_chords('I7') - [['C', 'E', 'G', 'B']] + [['C', 'E', 'G', 'B']] Any number of accidentals can be used as prefix to augment or diminish; for example: bIV or #I. - + All the chord abbreviations in the chord module can be used as suffixes; for example: Im7, IVdim7, etc. - + You can combine prefixes and suffixes to manage complex progressions: #vii7, #iidim7, iii7, etc. - + Using 7 as suffix is ambiguous, since it is classicly used to denote the seventh chord when talking about progressions instead of just the dominant seventh chord. We have taken the classic route; I7 will get you a major seventh chord. If you specifically want a dominanth seventh, use Idom7. """ + if isinstance(progression, six.string_types): + progression = [progression] result = [] for chord in progression: @@ -86,10 +90,12 @@ def to_chords(progression, key="C"): r = chords.chord_shorthand[suffix](r[0]) while acc < 0: + r = [notes.diminish(x) for x in r] acc += 1 while acc > 0: r = [notes.augment(x) for x in r] + acc -= 1 result.append(r) return result @@ -225,13 +231,17 @@ def parse_string(progression): roman_numeral = "" suffix = "" i = 0 + for c in progression: if c == "#": + acc += 1 elif c == "b": acc -= 1 + elif c.upper() == "I" or c.upper() == "V": roman_numeral += c.upper() + else: break i += 1 diff --git a/mingus/core/scales.py b/mingus/core/scales.py index 9b717db9..a6720693 100644 --- a/mingus/core/scales.py +++ b/mingus/core/scales.py @@ -25,7 +25,7 @@ The diatonic scales * Diatonic(note, semitones) -Ancient scales +Ancient scales (modes) * Ionian(note) * Dorian(note) * Phrygian(note) @@ -33,6 +33,7 @@ * Mixolydian(note) * Aeolian(note) * Locrian(note) + ** new in Sidewinder: modes of the harmonic and melodic minor scales The major scales * Major(note) @@ -44,11 +45,21 @@ * MelodicMinor(note) * Bachian(note) * MinorNeapolitan(note) + +The bebop scales (new in Sidewinder) + * MajorBebop(note) + * DorianBebop(note) + * AltDorianBebop(note) + * MixolydianBebop(note) # aka dominant bebop + * MelodicMinorBebop(note) + * HarmonicMinorBebop(note) Other scales * Chromatic(note) * WholeTone(note) * Octatonic(note) + ** new in Sidewinder: major and minor pentatonics + ** new in Sidewinder: super-ultra-hyper-mega-meta 'scales' (lydian and mixolydian) """ from __future__ import absolute_import @@ -145,6 +156,32 @@ def degree(self, degree_number, direction="a"): else: raise FormatError("Unrecognised direction '%s'" % direction) + def generate(self, length, ascending=True, start_degree=1, undulating=False, starting_octave=None): + """ + + Generate given number of notes from the scale, either continuously rising or falling or as + an undulating wave (i.e., up an octave, then down, then up again, etc.) + + >>> list(Blues('F').generate(length=9)) + ['F', 'Ab', 'Bb', 'B', 'C', 'Eb', 'F', 'Ab', 'Bb'] + + >>> list(Blues('F').generate(length=7, undulating=True)) + ['F', 'Ab', 'Bb', 'B', 'C', 'Eb', 'F'] + + >>> [repr(note) for note in Blues('F').generate(length=13, undulating=True, starting_octave=1)] + ["'F-1'", "'Ab-1'", "'Bb-1'", "'B-1'", "'C-1'", "'Eb-1'", "'F-2'", "'Eb-1'", "'C-1'", "'B-1'", "'Bb-1'", "'Ab-1'", "'F-1'"] + """ + wave = (self.ascending()[:-1] + self.descending()[:-1]) if ascending else ( + self.descending()[:-1] + self.ascending()[:-1]) + incline = (self.ascending() if ascending else self.descending())[:-1] + alternating_octaves = cycle([starting_octave, + starting_octave+1]) if starting_octave is not None else None + for key in cycle(wave if undulating else incline): + octave = starting_octave if (key != self.tonic or starting_octave is None) else next(alternating_octaves) + yield TemporalNote(key, octave=octave) if starting_octave is not None else key + length -= 1 + if length == 0: + break # The diatonic scales @@ -154,12 +191,13 @@ class Diatonic(_Scale): """The diatonic scale. Example: - >>> print Diatonic('C', (3, 7)) + >>> print(Diatonic('C', (3, 7))) Ascending: C D E F G A B C Descending: C B A G F E D C """ - type = "diatonic" + type = 'diatonic' + def __init__(self, note, semitones, octaves=1): """Create the diatonic scale starting on the chosen note. @@ -181,7 +219,7 @@ def ascending(self): return notes * self.octaves + [notes[0]] -# Ancient scales +# Major modes class Ionian(_Scale): @@ -189,7 +227,7 @@ class Ionian(_Scale): """The ionian scale. Example: - >>> print Ionian('C') + >>> print(Ionian('C')) Ascending: C D E F G A B C Descending: C B A G F E D C """ @@ -211,7 +249,7 @@ class Dorian(_Scale): """The dorian scale. Example: - >>> print Dorian('D') + >>> print(Dorian('D')) Ascending: D E F G A B C D Descending: D C B A G F E D """ @@ -233,7 +271,7 @@ class Phrygian(_Scale): """The phrygian scale. Example: - >>> print Phrygian('E') + >>> print(Phrygian('E')) Ascending: E F G A B C D E Descending: E D C B A G F E """ @@ -255,7 +293,7 @@ class Lydian(_Scale): """The lydian scale. Example: - >>> print Lydian('F') + >>> print(Lydian('F')) Ascending: F G A B C D E F Descending: F E D C B A G F """ @@ -277,7 +315,7 @@ class Mixolydian(_Scale): """The mixolydian scale. Example: - >>> print Mixolydian('G') + >>> print(Mixolydian('G')) Ascending: G A B C D E F G Descending: G F E D C B A G """ @@ -299,7 +337,7 @@ class Aeolian(_Scale): """The aeolian scale. Example: - >>> print Aeolian('A') + >>> print(Aeolian('A')) Ascending: A B C D E F G A Descending: A G F E D C B A """ @@ -321,7 +359,7 @@ class Locrian(_Scale): """The locrian scale. Example: - >>> print Locrian('B') + >>> print(Locrian('B')) Ascending: B C D E F G A B Descending: B A G F E D C B """ @@ -337,6 +375,49 @@ def ascending(self): notes = Diatonic(self.tonic, (1, 4)).ascending()[:-1] return notes * self.octaves + [notes[0]] +BLUES_INTERVALS = [ + intervals.minor_third, + intervals.major_second, + intervals.minor_second, + intervals.minor_second, + intervals.minor_third, + intervals.major_second, +] + +class Blues(_Scale): + + """The blues scale + + Example: + >>> print(Blues('C')) + Ascending: C Eb F Gb G Bb C + Descending: C Bb G Gb F Eb C + + >>> f_blues = Blues('F') + >>> print(f_blues) + Ascending: F Ab Bb B C Eb F + Descending: F Eb C B Bb Ab F + + >>> f_blues.degree(4) + 'B' + """ + + type = 'major' + + def __init__(self, note, octaves=1): + """Create the major scale starting on the chosen note.""" + super(Blues, self).__init__(note, octaves) + self.name = '{} blues'.format(self.tonic) + + def ascending(self): + notes = [self.tonic] + current_note = self.tonic + for ival in BLUES_INTERVALS: + new_note = reduce_accidentals(ival(current_note)) + notes.append(new_note) + current_note = new_note + + return notes # The major scales @@ -346,7 +427,7 @@ class Major(_Scale): """The major scale. Example: - >>> print Major('A') + >>> print(Major('A')) Ascending: A B C# D E F# G# A Descending: A G# F# E D C# B A """ @@ -359,7 +440,10 @@ def __init__(self, note, octaves=1): self.name = "{0} major".format(self.tonic) def ascending(self): - notes = get_notes(self.tonic) + try: + notes = get_notes(self.tonic) # this fails with G# (for ex), see mingus.core.keys.keys (i.e. missing in that list) + except NoteFormatError: + notes = Diatonic(self.tonic, (3,7)).ascending() return notes * self.octaves + [notes[0]] @@ -368,7 +452,7 @@ class HarmonicMajor(_Scale): """The harmonic major scale. Example: - >>> print HarmonicMajor('C') + >>> print(HarmonicMajor('C')) Ascending: C D E F G Ab B C Descending: C B Ab G F E D C """ @@ -394,7 +478,7 @@ class NaturalMinor(_Scale): """The natural minor scale. Example: - >>> print NaturalMinor('A') + >>> print(NaturalMinor('A')) Ascending: A B C D E F G A Descending: A G F E D C B A """ @@ -416,7 +500,7 @@ class HarmonicMinor(_Scale): """The harmonic minor scale. Example: - >>> print HarmonicMinor('A') + >>> print(HarmonicMinor('A')) Ascending: A B C D E F G# A Descending: A G# F E D C B A """ @@ -439,7 +523,7 @@ class MelodicMinor(_Scale): """The melodic minor scale. Example: - >>> print MelodicMinor('A') + >>> print(MelodicMinor('A')) Ascending: A B C D E F# G# A Descending: A G F E D C B A """ @@ -467,7 +551,7 @@ class Bachian(_Scale): """The Bachian scale. Example: - >>> print Bachian('A') + >>> print(Bachian('A')) Ascending: A B C D E F# G# A Descending: A G# F# E D C B A """ @@ -490,7 +574,7 @@ class MinorNeapolitan(_Scale): """The minor Neapolitan scale. Example: - >>> print MinorNeapolitan('A') + >>> print(MinorNeapolitan('A')) Ascending: A Bb C D E F G# A Descending: A G F E D C Bb A """ @@ -512,6 +596,411 @@ def descending(self): notes[6] = diminish(notes[6]) return notes * self.octaves + [notes[0]] +# Harmonic minor modes + +class LocrianNat6(_Scale): + + """The locrian natural 6 scale. + + Example: + >>> print(LocrianNat6('C')) + Ascending: C Db Eb F Gb A Bb C + Descending: C Bb A Gb F Eb Db C + """ + + type = 'ancient' + + def __init__(self, note, octaves=1): + """Create the locrian nat6 mode scale starting on the chosen note.""" + super(LocrianNat6, self).__init__(note, octaves) + self.name = '{0} locrian nat6'.format(self.tonic) + + def ascending(self): + relative_tonic = TemporalNote().from_int(int(TemporalNote(self.tonic))-2).name + notes = HarmonicMinor(relative_tonic).ascending()[:-1][1:] + HarmonicMinor(relative_tonic).ascending()[:-1][:1] + return notes * self.octaves + [notes[0]] + +class IonianSharp5(_Scale): + + """The ionian #5 (augmented) scale. + + Example: + >>> print(IonianSharp5('C')) + Ascending: C D E F G# A B C + Descending: C B A G# F E D C + """ + + type = 'ancient' + + def __init__(self, note, octaves=1): + """Create the ionian #5 mode scale starting on the chosen note.""" + super(IonianSharp5, self).__init__(note, octaves) + self.name = '{0} ionian #5'.format(self.tonic) + + def ascending(self): + relative_tonic = TemporalNote().from_int(int(TemporalNote(self.tonic))-3).name + notes = HarmonicMinor(relative_tonic).ascending()[:-1][2:] + HarmonicMinor(relative_tonic).ascending()[:-1][:2] + return notes * self.octaves + [notes[0]] + +class DorianSharp4(_Scale): + + """The dorian #4 scale. + + Example: + >>> print(DorianSharp4('C')) + Ascending: C D Eb F# G A Bb C + Descending: C Bb A G F# Eb D C + """ + + type = 'ancient' + + def __init__(self, note, octaves=1): + """Create the dorian #4 mode scale starting on the chosen note.""" + super(DorianSharp4, self).__init__(note, octaves) + self.name = '{0} dorian #4'.format(self.tonic) + + def ascending(self): + relative_tonic = TemporalNote().from_int(int(TemporalNote(self.tonic))-5).name + notes = HarmonicMinor(relative_tonic).ascending()[:-1][3:] + HarmonicMinor(relative_tonic).ascending()[:-1][:3] + return notes * self.octaves + [notes[0]] + +class PhrygianDominant(_Scale): + + """The phrygian dominant scale. + + Example: + >>> print(PhrygianDominant('C')) + Ascending: C Db E F G Ab Bb C + Descending: C Bb Ab G F E Db C + """ + + type = 'ancient' + + def __init__(self, note, octaves=1): + """Create the phrygian dominant mode scale starting on the chosen note.""" + super(PhrygianDominant, self).__init__(note, octaves) + self.name = '{0} phrygian dominant'.format(self.tonic) + + def ascending(self): + relative_tonic = TemporalNote().from_int(int(TemporalNote(self.tonic))-7).name + notes = HarmonicMinor(relative_tonic).ascending()[:-1][4:] + HarmonicMinor(relative_tonic).ascending()[:-1][:4] + return notes * self.octaves + [notes[0]] + +class LydianSharp2(_Scale): + + """The lydian #2 (#9) scale. + + Example: + >>> print(LydianSharp2('C')) + Ascending: C D# E F# G A B C + Descending: C B A G F# E D# C + """ + + type = 'ancient' + + def __init__(self, note, octaves=1): + """Create the lydian #2 mode scale starting on the chosen note.""" + super(LydianSharp2, self).__init__(note, octaves) + self.name = '{0} lydian #2'.format(self.tonic) + + def ascending(self): + relative_tonic = TemporalNote().from_int(int(TemporalNote(self.tonic))-8).name + notes = HarmonicMinor(relative_tonic).ascending()[:-1][5:] + HarmonicMinor(relative_tonic).ascending()[:-1][:5] + return notes * self.octaves + [notes[0]] + +class AlteredDominantbb7(_Scale): + + """The altered dominant bb7 (superlocrian bb7) scale. + + Example: + >>> print(AlteredDominantbb7('C')) + Ascending: C Db Eb Fb Gb Ab Bbb C + Descending: C Bbb Ab Gb Fb Eb Db C + """ + + type = 'ancient' + + def __init__(self, note, octaves=1): + """Create the altered dominant bb7 (superlocrian bb7) mode scale starting on the chosen note.""" + super(AlteredDominantbb7, self).__init__(note, octaves) + self.name = '{0} altered dominant bb7'.format(self.tonic) + + def ascending(self): + relative_tonic = TemporalNote().from_int(int(TemporalNote(self.tonic))-11).name + notes = HarmonicMinor(relative_tonic).ascending()[:-1][6:] + HarmonicMinor(relative_tonic).ascending()[:-1][:6] + return notes * self.octaves + [notes[0]] + +# Melodic minor modes + +class Dorianb2(_Scale): + + """The dorian flat 2 (phrygian nat6) scale. + + Example: + >>> print(Dorianb2('C')) + Ascending: C Db Eb F G A Bb C + Descending: C Bb A G F Eb Db C + """ + + type = 'ancient' + + def __init__(self, note, octaves=1): + """Create the dorian b2 mode scale starting on the chosen note.""" + super(Dorianb2, self).__init__(note, octaves) + self.name = '{0} dorian b2'.format(self.tonic) + + def ascending(self): + relative_tonic = TemporalNote().from_int(int(TemporalNote(self.tonic))-2).name + notes = MelodicMinor(relative_tonic).ascending()[:-1][1:] + MelodicMinor(relative_tonic).ascending()[:-1][:1] + return notes * self.octaves + [notes[0]] + +class LydianSharp5(_Scale): + + """The lydian #5 (augmented) scale. + + Example: + >>> print(LydianSharp5('C')) + Ascending: C D E F# G# A B C + Descending: C B A G# F# E D C + """ + + type = 'ancient' + + def __init__(self, note, octaves=1): + """Create the lydian #5 mode scale starting on the chosen note.""" + super(LydianSharp5, self).__init__(note, octaves) + self.name = '{0} lydian #5'.format(self.tonic) + + def ascending(self): + relative_tonic = TemporalNote().from_int(int(TemporalNote(self.tonic))-3).name + notes = MelodicMinor(relative_tonic).ascending()[:-1][2:] + MelodicMinor(relative_tonic).ascending()[:-1][:2] + return notes * self.octaves + [notes[0]] + +class LydianDominant(_Scale): + + """The lydian dominant (overtone, lydian b7) scale. + + Example: + >>> print(LydianDominant('C')) + Ascending: C D E F# G A Bb C + Descending: C Bb A G F# E D C + """ + + type = 'ancient' + + def __init__(self, note, octaves=1): + """Create the lydian dominant mode scale starting on the chosen note.""" + super(LydianDominant, self).__init__(note, octaves) + self.name = '{0} lydian dominant'.format(self.tonic) + + def ascending(self): + relative_tonic = TemporalNote().from_int(int(TemporalNote(self.tonic))-5).name + notes = MelodicMinor(relative_tonic).ascending()[:-1][3:] + MelodicMinor(relative_tonic).ascending()[:-1][:3] + return notes * self.octaves + [notes[0]] + +class Mixolydianb6(_Scale): + + """The mixolydian b6 scale. + + Example: + >>> print(Mixolydianb6('C')) + Ascending: C D E F G Ab Bb C + Descending: C Bb Ab G F E D C + """ + + type = 'ancient' + + def __init__(self, note, octaves=1): + """Create the mixolydian b6 mode scale starting on the chosen note.""" + super(Mixolydianb6, self).__init__(note, octaves) + self.name = '{0} mixolydian b6'.format(self.tonic) + + def ascending(self): + relative_tonic = TemporalNote().from_int(int(TemporalNote(self.tonic))-7).name + notes = MelodicMinor(relative_tonic).ascending()[:-1][4:] + MelodicMinor(relative_tonic).ascending()[:-1][:4] + return notes * self.octaves + [notes[0]] + +class LocrianNat2(_Scale): + + """The locrian natural 2 (half-diminished) scale. + + Example: + >>> print(LocrianNat2('C')) + Ascending: C D Eb F Gb Ab Bb C + Descending: C Bb Ab Gb F Eb D C + """ + + type = 'ancient' + + def __init__(self, note, octaves=1): + """Create the locrian natural 2 mode scale starting on the chosen note.""" + super(LocrianNat2, self).__init__(note, octaves) + self.name = '{0} locrian nat2'.format(self.tonic) + + def ascending(self): + relative_tonic = TemporalNote().from_int(int(TemporalNote(self.tonic))-9).name + notes = MelodicMinor(relative_tonic).ascending()[:-1][5:] + MelodicMinor(relative_tonic).ascending()[:-1][:5] + return notes * self.octaves + [notes[0]] + +class AlteredDominant(_Scale): + + """The altered dominant (superlocrian) scale. + + Example: + >>> print(AlteredDominant('C')) + Ascending: C Db Eb Fb Gb Ab Bb C + Descending: C Bb Ab Gb Fb Eb Db C + """ + + type = 'ancient' + + def __init__(self, note, octaves=1): + """Create the altered dominant (superlocrian) mode scale starting on the chosen note.""" + super(AlteredDominant, self).__init__(note, octaves) + self.name = '{0} altered dominant'.format(self.tonic) + + def ascending(self): + relative_tonic = TemporalNote().from_int(int(TemporalNote(self.tonic))-11).name + notes = MelodicMinor(relative_tonic).ascending()[:-1][6:] + MelodicMinor(relative_tonic).ascending()[:-1][:6] + return notes * self.octaves + [notes[0]] + +# Bebop scales + +class MajorBebop(_Scale): + + """The major bebop scale. + + Example: + >>> print(Major('C')) + Ascending: C D E F G G# A B C + Descending: C B A G# F E D C + """ + + type = 'bebop' + + def __init__(self, note, octaves=1): + """Create the major bebop scale starting on the chosen note.""" + super(MajorBebop, self).__init__(note, octaves) + self.name = '{0} major bebop'.format(self.tonic) + + def ascending(self): + notes = Major(self.tonic).ascending()[:-3] + notes.append(intervals.minor_sixth(notes[0])) + notes += Major(self.tonic).ascending()[-3:-1] + return notes * self.octaves + [notes[0]] + +class DorianBebop(_Scale): + + """The dorian bebop scale. + + Example: + >>> print(DorianBebop('C')) + Ascending: C D Eb E F G A Bb C + Descending: C Bb A G F E Eb D C + """ + + type = 'bebop' + + def __init__(self, note, octaves=1): + """Create the dorian bebop scale starting on the chosen note.""" + super(DorianBebop, self).__init__(note, octaves) + self.name = '{0} dorian bebop'.format(self.tonic) + + def ascending(self): + notes = Dorian(self.tonic).ascending()[:3] + notes.append(intervals.major_third(notes[0])) + notes += Dorian(self.tonic).ascending()[3:-1] + return notes * self.octaves + [notes[0]] + +class DorianBebopAlt(_Scale): + + """The alternative dorian bebop scale. + + Example: + >>> print(DorianBebopAlt('C')) + Ascending: C D Eb F G A Bb B C + Descending: C B Bb A G F Eb D C + """ + + type = 'bebop' + + def __init__(self, note, octaves=1): + """Create the alternative dorian bebop scale starting on the chosen note.""" + super(DorianBebopAlt, self).__init__(note, octaves) + self.name = '{0} alternative dorian bebop'.format(self.tonic) + + def ascending(self): + notes = Dorian(self.tonic).ascending()[:7] + notes.append(intervals.major_seventh(notes[0])) + return notes * self.octaves + [notes[0]] + +class MixolydianBebop(_Scale): + + """The mixolydian (dominant) bebop scale. + + Example: + >>> print(MixolydianBebop('C')) + Ascending: C D E F G A Bb B C + Descending: C B Bb A G F E D C + """ + + type = 'bebop' + + def __init__(self, note, octaves=1): + """Create the mixolydian (dominant) bebop scale starting on the chosen note.""" + super(MixolydianBebop, self).__init__(note, octaves) + self.name = '{0} mixolydian bebop'.format(self.tonic) + + def ascending(self): + notes = Mixolydian(self.tonic).ascending()[:7] + notes.append(intervals.major_seventh(notes[0])) + return notes * self.octaves + [notes[0]] + +class MelodicMinorBebop(_Scale): + + """The melodic minor bebop scale. + + Example: + >>> print(MelodicMinorBebop('C')) + Ascending: C D Eb F G Ab A B C + Descending: C B A Ab G F Eb D C + """ + + type = 'bebop' + + def __init__(self, note, octaves=1): + """Create the melodic minor bebop scale starting on the chosen note.""" + super(MelodicMinorBebop, self).__init__(note, octaves) + self.name = '{0} melodic minor bebop'.format(self.tonic) + + def ascending(self): + notes = MelodicMinor(self.tonic).ascending()[:5] + notes.append(intervals.minor_sixth(notes[0])) + notes += MelodicMinor(self.tonic).ascending()[5:-1] + return notes * self.octaves + [notes[0]] + +class HarmonicMinorBebop(_Scale): + + """The harmonic minor bebop scale. + + Example: + >>> print(HarmonicMinorBebop('C')) + Ascending: C D Eb F G Ab Bb B C + Descending: C B Bb Ab G F Eb D C + """ + + type = 'bebop' + + def __init__(self, note, octaves=1): + """Create the harmonic minor bebop scale starting on the chosen note.""" + super(HarmonicMinorBebop, self).__init__(note, octaves) + self.name = '{0} harmonic minor bebop'.format(self.tonic) + + def ascending(self): + notes = HarmonicMinor(self.tonic).ascending()[:6] + notes.append(intervals.minor_seventh(notes[0])) + notes += HarmonicMinor(self.tonic).ascending()[6:-1] + return notes * self.octaves + [notes[0]] # Other scales @@ -521,10 +1010,10 @@ class Chromatic(_Scale): """The chromatic scale. Examples: - >>> print Chromatic('C') + >>> print(Chromatic('C')) Ascending: C C# D D# E F F# G G# A A# B C Descending: C B Bb A Ab G Gb F E Eb D Db C - >>> print Chromatic('f') + >>> print(Chromatic('f')) Ascending: F F# G Ab A Bb B C Db D Eb E F Descending: F E Eb D Db C B Bb A Ab G Gb F """ @@ -566,7 +1055,7 @@ class WholeTone(_Scale): """The whole tone scale. Example: - >>> print WholeTone('C') + >>> print(WholeTone('C')) Ascending: C D E F# G# A# C Descending: C A# G# F# E D C """ @@ -583,14 +1072,63 @@ def ascending(self): for note in range(5): notes.append(intervals.major_second(notes[-1])) return notes * self.octaves + [notes[0]] + +class Pentatonic(_Scale): + + """The (major) pentatonic scale. + + Example: + >>> print(Pentatonic('C')) + Ascending: C D E G A C + Descending: C A G E D C + """ + + type = 'other' + + def __init__(self, note, octaves=1): + """Create the pentatonic scale starting on the chosen note.""" + super(Pentatonic, self).__init__(note, octaves) + self.name = '{0} pentatonic'.format(self.tonic) + + def ascending(self): + notes = [self.tonic] + notes.append(intervals.major_second(notes[0])) + notes.append(intervals.major_third(notes[0])) + notes.append(intervals.perfect_fifth(notes[0])) + notes.append(intervals.major_sixth(notes[0])) + return notes * self.octaves + [notes[0]] + +class MinorPentatonic(_Scale): + """The minor pentatonic scale. + + Example: + >>> print(MinorPentatonic('C')) + Ascending: C Eb F G Bb C + Descending: C Bb G F Eb C + """ + + type = 'other' + + def __init__(self, note, octaves=1): + """Create the minor pentatonic scale starting on the chosen note.""" + super(MinorPentatonic, self).__init__(note, octaves) + self.name = '{0} minor pentatonic'.format(self.tonic) + + def ascending(self): + notes = [self.tonic] + notes.append(intervals.minor_third(notes[0])) + notes.append(intervals.perfect_fourth(notes[0])) + notes.append(intervals.perfect_fifth(notes[0])) + notes.append(intervals.minor_seventh(notes[0])) + return notes * self.octaves + [notes[0]] class Octatonic(_Scale): """The octatonic scale. Example: - >>> print Octatonic('C') + >>> print(Octatonic('C')) Ascending: C D Eb F Gb Ab A B C Descending: C B A Ab Gb F Eb D C """ @@ -598,7 +1136,7 @@ class Octatonic(_Scale): type = "other" def __init__(self, note, octaves=1): - """Create the octatonic (also known as "diminshed") scale starting + """Create the octatonic (also known as "whole-half diminished") scale starting on the chosen note.""" super(Octatonic, self).__init__(note, octaves) self.name = "{0} octatonic".format(self.tonic) @@ -612,3 +1150,130 @@ def ascending(self): notes.append(intervals.major_seventh(notes[0])) notes[-2] = intervals.major_sixth(notes[0]) return notes * self.octaves + [notes[0]] + + +class HalfWholeDiminished(_Scale): + + """The second octatonic scale. + + Example: + >>> print(HalfWholeDiminished('C')) + Ascending: C Db Eb E Gb G A Bb C + Descending: C Bb A G Gb E Eb Db C + """ + + type = 'other' + + def __init__(self, note, octaves=1): + """Create the half-whole diminished scale starting + on the chosen note.""" + super(HalfWholeDiminished, self).__init__(note, octaves) + self.name = '{0} half-whole diminished'.format(self.tonic) + + def ascending(self): + notes = [self.tonic] + for i in range(3): + notes.extend( + [intervals.minor_second(notes[-1]), + intervals.minor_third(notes[-1])]) + notes.append(intervals.minor_seventh(notes[0])) + notes[-2] = intervals.major_sixth(notes[0]) + return notes * self.octaves + [notes[0]] + +# Super-ultra-hyper-mega-meta scales (https://www.youtube.com/watch?v=DnBr070vcNE) (more 'sounds' than 'scales') + +class SuperUltraHyperMegaMetaLydian(_Scale): + + """The super-ultra-hyper-mega-meta lydian scale. + + This is the sound of continually brightening around the cycle of fifths (getting 'more lydian'), + in both ascending and descending forms. + + Example: + >>> print(SuperUltraHyperMegaMetaLydian('C')) + Ascending: C D E F#, G A B C#, D E F# G, A B C# D#, ... + Descending: C B A, G F# E, D C# B, A G# F#, ... + """ + + type = 'other' + + def __init__(self, note, octaves=1): + """Create the SUHMM lydian scale starting on the chosen note.""" + super(SuperUltraHyperMegaMetaLydian, self).__init__(note, octaves) + self.name = '{0} SUHMM lydian'.format(self.tonic) + + def ascending(self): + tonic = self.tonic + notes = [] + for i in range(self.octaves*2): + notes += [reduce_accidentals(note) for note in list(Lydian(tonic).ascending()[:4])] # 4 creates overlap on the fifth + tonic = intervals.perfect_fifth(tonic) + + if self.octaves == 1: + return notes + else: + return notes[:1-self.octaves] + + def descending(self): + tonic = self.tonic + notes = [] + for i in range(self.octaves*3): + notes += [reduce_accidentals(note) for note in list(Lydian(tonic).descending()[:3])] # 3 = 7 - 4 + tonic = intervals.perfect_fifth(tonic) + + if self.octaves == 1: + return notes[:-1] + else: + return notes[:-1-self.octaves] + + def generate(self, i): + print('Please use ascending() and descending() methods instead of generate() for SUHMM scales') + return False + +class SuperUltraHyperMegaMetaMixolydian(_Scale): + + """The super-ultra-hyper-mega-meta mixolydian scale. + + This is the sound of continually darkening around the cycle of fifths (getting 'more mixolydian'), + in both ascending and descending forms. + + Example: + >>> print(SuperUltraHyperMegaMetaMixolydian('C')) + Ascending: C D E, F G A, Bb C D, Eb F G, ... + Descending: C Bb A G, F Eb D C, Bb Ab G F, Eb Db C Bb, ... + """ + + type = 'other' + + def __init__(self, note, octaves=1): + """Create the SUHMM mixolydian scale starting on the chosen note.""" + super(SuperUltraHyperMegaMetaMixolydian, self).__init__(note, octaves) + self.name = '{0} SUHMM mixolydian'.format(self.tonic) + + def ascending(self): + tonic = self.tonic + notes = [] + for i in range(self.octaves*3): + notes += [reduce_accidentals(note) for note in list(Mixolydian(tonic).ascending()[:3])] + tonic = intervals.perfect_fourth(tonic) + + if self.octaves == 1: + return notes[:-1] + else: + return notes[:-self.octaves] + + def descending(self): + tonic = self.tonic + notes = [] + for i in range(self.octaves*2): + notes += [reduce_accidentals(note) for note in list(Mixolydian(tonic).descending()[:4])] + tonic = intervals.perfect_fourth(tonic) + + if self.octaves == 1: + return notes + else: + return notes[:-self.octaves] + + def generate(self, i): + print('Please use ascending() and descending() methods instead of generate() for SUHMM scales') + return False diff --git a/mingus/extra/fft.py b/mingus/extra/fft.py index 5e785424..fffb59a6 100644 --- a/mingus/extra/fft.py +++ b/mingus/extra/fft.py @@ -40,6 +40,9 @@ # So before we do any performance critical calculations we set up a cache of all # the frequencies we need to look up. +def xrange(x): + return iter(range(x)) + _log_cache = [] for x in range(129): _log_cache.append(Note().from_int(x).to_hertz()) diff --git a/mingus/extra/lilypond.py b/mingus/extra/lilypond.py index af8b471d..3f29203d 100644 --- a/mingus/extra/lilypond.py +++ b/mingus/extra/lilypond.py @@ -26,6 +26,76 @@ import os import subprocess +from mingus.core.chords import from_shorthand, determine, chord_note_and_family +from mingus.core import chords +from mingus.containers.bar import Bar +from mingus.containers.note import TemporalNote +from mingus.core.chords import TemporalChord + +CHORDS_WITH_EQUIVALENT_NOTATION = ['sus2', 'sus4', 'm', '7', 'm7', 'aug', 'dim', 'dim7', '11', 'm11', '6', 'm6', + '9', 'm9', '11', 'm11', '13', 'm13'] + +CHORD_FAMILY_TRANSLATION = { + 'M7': '{note_lowercase}:maj7', + 'M': '{note_lowercase}:maj', + 'm7b5': '{note_lowercase}:m7.5-', + 'mM7': '{note_lowercase}:m7+', + 'M9': '{note_lowercase}:maj9', + 'M13': '{note_lowercase}:maj13.11', +} + +def from_Chord(chord): + """ + Returns Lilypad notation for (temporal) chords + + + >>> chord = determine(chords.suspended_fourth_triad('C'), shorthand=True)[0] + >>> chord + 'Csus4' + >>> from_shorthand(chord) + ['C', 'F', 'G'] + >>> from_Chord(TemporalChord(chord)) + 'c4:sus4' + + >>> from_Chord(TemporalChord(determine(chords.major_seventh('F'), shorthand=True)[0])) + 'f4:maj7' + + >>> from_Chord(TemporalChord(determine(chords.major_seventh('Fb'), shorthand=True)[0])) + 'fes4:maj7' + + >>> from_Chord(TemporalChord(determine(chords.major_triad('C'), shorthand=True)[0])) + 'c4:maj' + + >>> chord = determine(chords.major_triad('C'), shorthand=True)[0] + >>> from_Chord(TemporalChord(chord, duration_denominator=2)) + 'c2:maj' + + >>> from_Chord(TemporalChord(determine(chords.minor_triad('C'), shorthand=True)[0])) + 'c4:m' + + >>> from_Chord(TemporalChord(determine(chords.augmented_triad('C'), shorthand=True)[0])) + 'c4:aug' + + >>> from_Chord(TemporalChord(determine(chords.suspended_second_triad('C'), shorthand=True)[0])) + 'c4:sus2' + + >>> from_Chord(TemporalChord(determine(chords.eleventh('C'), shorthand=True)[0])) + 'c4:11' + + >>> from_Chord(TemporalChord(determine(chords.half_diminished_seventh('C'), shorthand=True)[0])) + 'c4:m7.5-' + + """ + if not isinstance(chord, TemporalChord): + raise SyntaxError("Takes TemporalChord instances only") + # note, family = chord_note_and_family(chord.name) + if chord.family in CHORDS_WITH_EQUIVALENT_NOTATION: + format_string = '{note_lowercase}:{family}' + else: + format_string = CHORD_FAMILY_TRANSLATION[chord.family] + lowered_note = from_Note(chord.get_temporal_root(), standalone=False) + note_name = "{}{}".format(lowered_note, chord.duration_denominator) + return format_string.format(note_lowercase=note_name, family=chord.family) from six.moves import range @@ -41,6 +111,15 @@ def from_Note(note, process_octaves=True, standalone=True): ignored. If standalone is True, the result can be used by functions like to_png and will produce a valid output. The argument is mostly here to let from_NoteContainer make use of this function. + + >>> from_Note(TemporalNote('C', octave=5),standalone=False) + "c''" + + >>> from_Note(TemporalNote('C', octave=3),standalone=False) + 'c' + + >>> from_Note(TemporalNote('C', octave=1),standalone=False) + 'c,,' """ # Throw exception if not hasattr(note, "name"): @@ -125,7 +204,19 @@ def from_Bar(bar, showkey=True, showtime=True): The showkey and showtime parameters can be set to determine whether the key and the time should be shown. + + >>> from mingus.containers.note import QuarterNoteFactory as Q, HalfNoteFactory as H + >>> from mingus.core.chords import HalfNoteChordFactory as HC + >>> bar = Bar() + >>> bar.extend(Q(['C','D'])) + >>> bar += H('E') + >>> cmaj_triad = determine(chords.major_triad('C'),shorthand=True)[0] + >>> c7 = determine(chords.dominant_seventh('C'),shorthand=True)[0] + >>> bar.set_chord_notes([HC(cmaj_triad), HC(c7)]) + >>> print(from_Bar(bar)) + << \\time 4/4 \chords { c2:maj c2:7 } { c'4 d'4 e'2 }>> """ + # Throw exception if not hasattr(bar, "bar"): return False @@ -242,7 +333,12 @@ def save_string_and_execute_LilyPond(ly_string, filename, command): except: return False command = 'lilypond %s -o "%s" "%s.ly"' % (command, filename, filename) + print("Executing: %s" % command) p = subprocess.Popen(command, shell=True).wait() os.remove(filename + ".ly") return True + +if __name__ == '__main__': + import doctest + doctest.testmod() diff --git a/mingus/extra/tunings.py b/mingus/extra/tunings.py index d1b76721..17989f5f 100644 --- a/mingus/extra/tunings.py +++ b/mingus/extra/tunings.py @@ -421,6 +421,7 @@ def get_tuning(instrument, description, nr_of_strings=None, nr_of_courses=None): searchd = str.upper(description) keys = list(_known.keys()) for x in keys: + if ( searchi not in keys and x.find(searchi) == 0 @@ -428,6 +429,7 @@ def get_tuning(instrument, description, nr_of_strings=None, nr_of_courses=None): and x == searchi ): for (desc, tun) in six.iteritems(_known[x][1]): + if desc.find(searchd) == 0: if nr_of_strings is None and nr_of_courses is None: return tun diff --git a/mingus/midi/__init__.py b/mingus/midi/__init__.py index 43b1b82a..c713bbfe 100644 --- a/mingus/midi/__init__.py +++ b/mingus/midi/__init__.py @@ -22,6 +22,7 @@ from mingus.midi.sequencer_observer import SequencerObserver __all__ = [ + "Sequencer", "SequencerObserver", "midi_file_in", @@ -29,3 +30,4 @@ "midi_track", "fluidsynth", ] + diff --git a/mingus/midi/fluidsynth.py b/mingus/midi/fluidsynth.py index 5a07ce28..14cd09d9 100644 --- a/mingus/midi/fluidsynth.py +++ b/mingus/midi/fluidsynth.py @@ -35,6 +35,8 @@ """ from __future__ import absolute_import + + import time import wave diff --git a/mingus/midi/libfluidsynth-1.dll b/mingus/midi/libfluidsynth-1.dll new file mode 100644 index 00000000..0217619f Binary files /dev/null and b/mingus/midi/libfluidsynth-1.dll differ diff --git a/mingus/midi/libfluidsynth-2.dll b/mingus/midi/libfluidsynth-2.dll new file mode 100644 index 00000000..0217619f Binary files /dev/null and b/mingus/midi/libfluidsynth-2.dll differ diff --git a/mingus/midi/midi_events.py b/mingus/midi/midi_events.py index 76e8c1e3..cf06a8ee 100644 --- a/mingus/midi/midi_events.py +++ b/mingus/midi/midi_events.py @@ -1,9 +1,11 @@ # -*- coding: utf-8 -*- # Headers + FILE_HEADER = b"MThd" TRACK_HEADER = b"MTrk" + # MIDI Channel Events NOTE_OFF = 0x08 NOTE_ON = 0x09 @@ -12,8 +14,10 @@ PROGRAM_CHANGE = 0x0C CHANNEL_AFTERTOUCH = 0x0D PITCH_BEND = 0x0E + META_EVENT = b"\xff" + # MIDI Controller Type BANK_SELECT = 0x00 MODULATION = 0x01 @@ -37,6 +41,7 @@ LYRICS = b"\x05" MARKER = b"\x06" CUE_POINT = b"\x07" + MIDI_CHANNEL_PREFIX = b"\x20" END_OF_TRACK = b"\x2F" SET_TEMPO = b"\x51" diff --git a/mingus/midi/midi_file_in.py b/mingus/midi/midi_file_in.py index 8ab46a73..04002b5b 100644 --- a/mingus/midi/midi_file_in.py +++ b/mingus/midi/midi_file_in.py @@ -30,7 +30,7 @@ import mingus.core.intervals as intervals import binascii from six.moves import range - +import numpy as np def MIDI_to_Composition(file): """Convert a MIDI file to a mingus.containers.Composition and return it @@ -67,7 +67,15 @@ class MidiFile(object): def MIDI_to_Composition(self, file): (header, track_data) = self.parse_midi_file(file) +# print('MIDI_to_Composition:', track_data[1]) +# print('===') +# print(track_data[2]) +# for j in track_data[2]: +# if j[1]['event'] == 9 or j[1]['event'] == 8: +# print('MIDI_to_Composition:', j[0], j[1]['event'], Note(notes.int_to_note(j[1]['param1'] % 12))) + c = Composition() + if header[2]["fps"]: print("Don't know how to parse this yet") return c @@ -78,10 +86,17 @@ def MIDI_to_Composition(self, file): metronome = 1 # Tick once every quarter note thirtyseconds = 8 # 8 thirtyseconds in a quarter note meter = (4, 4) + key = "C" for e in track: (deltatime, event) = e duration = float(deltatime) / (ticks_per_beat * 4.0) + + if NOTE_ON_counter == 0 and duration != 0 and event['event'] == 9: # then we need to add a rest before playing + b.place_rest(1.0/duration) + NOTE_ON_counter += 1 + + # this logic is nice and clear... if duration != 0.0: duration = 1.0 / duration if len(b.bar) > 0: @@ -100,12 +115,11 @@ def MIDI_to_Composition(self, file): pass elif event["event"] == 9: # note on - n = Note( - notes.int_to_note(event["param1"] % 12), - event["param1"] / 12 - 1, - ) - n.channel = event["channel"] - n.velocity = event["param2"] + n = Note(notes.int_to_note(event['param1'] % 12), + np.floor(event['param1'] / 12)) # this was event['param1']/12 - 1 but that gives skewed, incorrect octaves + n.channel = event['channel'] + n.velocity = event['param2'] + if len(b.bar) > 0: b.bar[-1][2] + n else: @@ -153,13 +167,16 @@ def MIDI_to_Composition(self, file): b.set_meter(meter) elif event["meta_event"] == 89: # Key Signature + d = event["data"] sharps = self.bytes_to_int(d[0]) minor = self.bytes_to_int(d[0]) + if minor: key = "A" else: key = "C" + for i in range(abs(sharps)): if sharps < 0: key = intervals.major_fourth(key) @@ -187,6 +204,7 @@ def parse_midi_file_header(self, fp): except: raise IOError("Couldn't read from file.") + # Parse chunk size try: chunk_size = self.bytes_to_int(fp.read(4)) @@ -215,11 +233,11 @@ def parse_midi_file_header(self, fp): "Couldn't read number of tracks " "and/or time division from tracks." ) - chunk_size -= 6 + #chunk_size -= 6 if chunk_size % 2 == 1: raise FormatError("Won't parse this.") - fp.read(chunk_size / 2) - self.bytes_read += chunk_size / 2 +# fp.read(chunk_size // 2) +# self.bytes_read += chunk_size // 2 return (format_type, number_of_tracks, time_division) def bytes_to_int(self, bytes): @@ -242,9 +260,11 @@ def parse_time_division(self, bytes): SMPTE_frames = (value & 0x7F00) >> 2 if SMPTE_frames not in [24, 25, 29, 30]: raise TimeDivisionError( + "'%d' is not a valid value for the number of SMPTE frames" % SMPTE_frames ) + clock_ticks = (value & 0x00FF) >> 2 return { "fps": True, @@ -267,7 +287,9 @@ def parse_track(self, fp): chunk_size -= chunk_delta events.append([delta_time, event]) if chunk_size < 0: + print("yikes.", self.bytes_read, chunk_size) + return events def parse_midi_event(self, fp): @@ -283,6 +305,7 @@ def parse_midi_event(self, fp): except: raise IOError("Couldn't read event type " "and channel data from file.") + # Get the nibbles event_type = (ec & 0xF0) >> 4 channel = ec & 0x0F @@ -332,6 +355,12 @@ def parse_midi_event(self, fp): raise IOError("Couldn't read MIDI event parameters from file.") param1 = self.bytes_to_int(param1) param2 = self.bytes_to_int(param2) + + # "It is common for a note on with 0 velocity to be interpreted as NOTE OFF." https://stackoverflow.com/questions/48687756/midi-note-on-event-without-off-event + # this should resolve Lilypad midi files which don't seem to use ec 8 to signify NOTE OFF + if param2 == 0: + event_type = 8 + return ( { "event": event_type, @@ -342,6 +371,7 @@ def parse_midi_event(self, fp): chunk_size, ) + def parse_track_header(self, fp): """Return the size of the track chunk.""" # Check the header @@ -349,6 +379,7 @@ def parse_track_header(self, fp): h = fp.read(4) self.bytes_read += 4 except: + raise IOError( "Couldn't read track header from file. Byte %d." % self.bytes_read ) @@ -372,7 +403,9 @@ def parse_midi_file(self, file): track data and the number of bytes read. """ try: + f = open(file, "rb") + except: raise IOError("File not found") self.bytes_read = 0 diff --git a/mingus/midi/midi_file_out.py b/mingus/midi/midi_file_out.py index 63a9e444..04a7ad46 100644 --- a/mingus/midi/midi_file_out.py +++ b/mingus/midi/midi_file_out.py @@ -15,6 +15,11 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +# +# +# SG: +# Have needed to change strings to bstrings to get concat to work +# """Functions that can generate MIDI files from the objects in mingus.containers.""" @@ -23,6 +28,7 @@ from mingus.midi.midi_track import MidiTrack from binascii import a2b_hex + from six.moves import range @@ -41,6 +47,7 @@ def __init__(self, tracks=None): def get_midi_data(self): """Collect and return the raw, binary MIDI data from the tracks.""" + tracks = [t.get_midi_data() for t in self.tracks if t.track_data != b""] return self.header() + b"".join(tracks) diff --git a/mingus/midi/midi_track.py b/mingus/midi/midi_track.py index 25b09659..7fdfd60a 100644 --- a/mingus/midi/midi_track.py +++ b/mingus/midi/midi_track.py @@ -26,6 +26,7 @@ from binascii import a2b_hex from math import log + from struct import pack from six.moves import range @@ -33,18 +34,17 @@ from mingus.core.keys import Key, major_keys, minor_keys from mingus.midi.midi_events import * - class MidiTrack(object): """A class used to generate MIDI events from the objects in mingus.containers.""" - track_data = b"" delta_time = b"\x00" delay = 0 bpm = 120 change_instrument = False instrument = 1 + def __init__(self, start_bpm=120): self.track_data = b"" @@ -75,6 +75,7 @@ def play_Note(self, note): if self.change_instrument: self.set_instrument(channel, self.instrument) self.change_instrument = False + self.track_data += self.note_on(channel, int(note) + 12, velocity) def play_NoteContainer(self, notecontainer): @@ -256,7 +257,7 @@ def set_key(self, key="C"): def key_signature_event(self, key="C"): """Return the bytes for a key signature event.""" - if key.islower(): + if str(key).islower(): val = minor_keys.index(key) - 7 mode = b"\x01" else: diff --git a/mingus/midi/sequencer.py b/mingus/midi/sequencer.py index 9ba00666..18341c8f 100644 --- a/mingus/midi/sequencer.py +++ b/mingus/midi/sequencer.py @@ -38,7 +38,7 @@ class Sequencer(object): You can use the Sequencer object either by creating a subclass and implementing some of the events (init, play_event, stop_event, cc_event, - instr_event) or by attaching observer objects via 'attach' and catching + instr_event) or by attaching observer objects via 'attach' and catching the messages in the notify(msg_type, param_dict) function of your object. See SequencerObserver for a pre made, easy to extend base class that can