Source code for mingus.core.intervals

#!/usr/bin/python
# -*- coding: utf-8 -*-

#    Music theory Python package, intervals module.
#    Copyright (C) 2008-2009, Bart Spaans
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.

"""Module to create intervals from notes.

When you are working in a key (for instance 'F'), you can use the functions
second ('G'), third ('A'), fourth ('Bb'), fifth ('C'), sixth ('D') and
seventh ('E') to get to the respective natural intervals of that note.

When you want to get the absolute intervals you can use the minor and major
functions. For example: minor_third('F') returns 'Ab' while major_third('F')
returns 'A'.

This modules also contains other useful helper functions like measure,
determine, invert, is_consonant and is_dissonant.
"""

import notes
import keys

[docs]def interval(key, start_note, interval): """Return the note found at the interval starting from start_note in the given key. Raise a KeyError exception if start_note is not a valid note. Example: >>> interval('C', 'D', 1) 'E' """ if not notes.is_valid_note(start_note): raise KeyError("The start note '%s' is not a valid note" % start_note) notes_in_key = keys.get_notes(key) for n in notes_in_key: if n[0] == start_note[0]: index = notes_in_key.index(n) return notes_in_key[(index + interval) % 7]
[docs]def unison(note, key=None): """Return the unison of note. Raise a KeyError exception if the note is not found in the given key. The key is not at all important, but is here for consistency reasons only. Example: >>> unison('C') 'C' """ return interval(key, note, 0)
[docs]def second(note, key): """Take the diatonic second of note in key. Raise a KeyError exception if the note is not found in the given key. Examples: >>> second('E', 'C') 'F' >>> second('E', 'D') 'F#' """ return interval(key, note, 1)
[docs]def third(note, key): """Take the diatonic third of note in key. Raise a KeyError exception if the note is not found in the given key. Examples: >>> third('E', 'C') 'G' >>> third('E', 'E') 'G#' """ return interval(key, note, 2)
[docs]def fourth(note, key): """Take the diatonic fourth of note in key. Raise a KeyError exception if the note is not found in the given key. Examples: >>> fourth('E', 'C') 'A' >>> fourth('E', 'B') 'A#' """ return interval(key, note, 3)
[docs]def fifth(note, key): """Take the diatonic fifth of note in key. Raise a KeyError exception if the note is not found in the given key. Examples: >>> fifth('E', 'C') 'B' >>> fifth('E', 'F') 'Bb' """ return interval(key, note, 4)
[docs]def sixth(note, key): """Take the diatonic sixth of note in key. Raise a KeyError exception if the note is not found in the given key. Examples: >>> sixth('E', 'C') 'C' >>> sixth('E', 'B') 'C#' """ return interval(key, note, 5)
[docs]def seventh(note, key): """Take the diatonic seventh of note in key. Raise a KeyError exception if the note is not found in the given key. Examples: >>> seventh('E', 'C') 'D' >>> seventh('E', 'B') 'D#' """ return interval(key, note, 6)
[docs]def minor_unison(note): return notes.diminish(note)
[docs]def major_unison(note): return note
[docs]def augmented_unison(note): return notes.augment(note)
[docs]def minor_second(note): sec = second(note[0], 'C') return augment_or_diminish_until_the_interval_is_right(note, sec, 1)
[docs]def major_second(note): sec = second(note[0], 'C') return augment_or_diminish_until_the_interval_is_right(note, sec, 2)
[docs]def minor_third(note): trd = third(note[0], 'C') return augment_or_diminish_until_the_interval_is_right(note, trd, 3)
[docs]def major_third(note): trd = third(note[0], 'C') return augment_or_diminish_until_the_interval_is_right(note, trd, 4)
[docs]def minor_fourth(note): frt = fourth(note[0], 'C') return augment_or_diminish_until_the_interval_is_right(note, frt, 4)
[docs]def major_fourth(note): frt = fourth(note[0], 'C') return augment_or_diminish_until_the_interval_is_right(note, frt, 5)
[docs]def perfect_fourth(note): return major_fourth(note)
[docs]def minor_fifth(note): fif = fifth(note[0], 'C') return augment_or_diminish_until_the_interval_is_right(note, fif, 6)
[docs]def major_fifth(note): fif = fifth(note[0], 'C') return augment_or_diminish_until_the_interval_is_right(note, fif, 7)
[docs]def perfect_fifth(note): return major_fifth(note)
[docs]def minor_sixth(note): sth = sixth(note[0], 'C') return augment_or_diminish_until_the_interval_is_right(note, sth, 8)
[docs]def major_sixth(note): sth = sixth(note[0], 'C') return augment_or_diminish_until_the_interval_is_right(note, sth, 9)
[docs]def minor_seventh(note): sth = seventh(note[0], 'C') return augment_or_diminish_until_the_interval_is_right(note, sth, 10)
[docs]def major_seventh(note): sth = seventh(note[0], 'C') return augment_or_diminish_until_the_interval_is_right(note, sth, 11)
[docs]def get_interval(note, interval, key='C'): """Return the note an interval (in half notes) away from the given note. This will produce mostly theoretical sound results, but you should use the minor and major functions to work around the corner cases. """ intervals = map(lambda x: (notes.note_to_int(key) + x) % 12, [ 0, 2, 4, 5, 7, 9, 11, ]) key_notes = keys.get_notes(key) for x in key_notes: if x[0] == note[0]: result = (intervals[key_notes.index(x)] + interval) % 12 if result in intervals: return key_notes[intervals.index(result)] + note[1:] else: return notes.diminish(key_notes[intervals.index((result + 1) % 12)] + note[1:])
[docs]def measure(note1, note2): """Return an integer in the range of 0-11, determining the half note steps between note1 and note2. Examples: >>> measure('C', 'D') 2 >>> measure('D', 'C') 10 """ res = notes.note_to_int(note2) - notes.note_to_int(note1) if res < 0: return 12 - res * -1 else: return res
[docs]def augment_or_diminish_until_the_interval_is_right(note1, note2, interval): """A helper function for the minor and major functions. You should probably not use this directly. """ cur = measure(note1, note2) while cur != interval: if cur > interval: note2 = notes.diminish(note2) elif cur < interval: note2 = notes.augment(note2) cur = measure(note1, note2) # We are practically done right now, but we need to be able to create the # minor seventh of Cb and get Bbb instead of B######### as the result val = 0 for token in note2[1:]: if token == '#': val += 1 elif token == 'b': val -= 1 # These are some checks to see if we have generated too much #'s or too much # b's. In these cases we need to convert #'s to b's and vice versa. if val > 6: val = val % 12 val = -12 + val elif val < -6: val = val % -12 val = 12 + val # Rebuild the note result = note2[0] while val > 0: result = notes.augment(result) val -= 1 while val < 0: result = notes.diminish(result) val += 1 return result
[docs]def invert(interval): """Invert an interval. Example: >>> invert(['C', 'E']) ['E', 'C'] """ interval.reverse() res = list(interval) interval.reverse() return res
[docs]def determine(note1, note2, shorthand=False): """Name the interval between note1 and note2. Examples: >>> determine('C', 'E') 'major third' >>> determine('C', 'Eb') 'minor third' >>> determine('C', 'E#') 'augmented third' >>> determine('C', 'Ebb') 'diminished third' This works for all intervals. Note that there are corner cases for major fifths and fourths: >>> determine('C', 'G') 'perfect fifth' >>> determine('C', 'F') 'perfect fourth' """ # Corner case for unisons ('A' and 'Ab', for instance) if note1[0] == note2[0]: def get_val(note): """Private function: count the value of accidentals.""" r = 0 for x in note[1:]: if x == 'b': r -= 1 elif x == '#': r += 1 return r x = get_val(note1) y = get_val(note2) if x == y: if not shorthand: return 'major unison' return '1' elif x < y: if not shorthand: return 'augmented unison' return '#1' elif x - y == 1: if not shorthand: return 'minor unison' return 'b1' else: if not shorthand: return 'diminished unison' return 'bb1' # Other intervals n1 = notes.fifths.index(note1[0]) n2 = notes.fifths.index(note2[0]) number_of_fifth_steps = n2 - n1 if n2 < n1: number_of_fifth_steps = len(notes.fifths) - n1 + n2 # [name, shorthand_name, half notes for major version of this interval] fifth_steps = [ ['unison', '1', 0], ['fifth', '5', 7], ['second', '2', 2], ['sixth', '6', 9], ['third', '3', 4], ['seventh', '7', 11], ['fourth', '4', 5], ] # Count half steps between note1 and note2 half_notes = measure(note1, note2) # Get the proper list from the number of fifth steps current = fifth_steps[number_of_fifth_steps] # maj = number of major steps for this interval maj = current[2] # if maj is equal to the half steps between note1 and note2 the interval is # major or perfect if maj == half_notes: # Corner cases for perfect fifths and fourths if current[0] == 'fifth': if not shorthand: return 'perfect fifth' elif current[0] == 'fourth': if not shorthand: return 'perfect fourth' if not shorthand: return 'major ' + current[0] return current[1] elif maj + 1 <= half_notes: # if maj + 1 is equal to half_notes, the interval is augmented. if not shorthand: return 'augmented ' + current[0] return '#' * (half_notes - maj) + current[1] elif maj - 1 == half_notes: # etc. if not shorthand: return 'minor ' + current[0] return 'b' + current[1] elif maj - 2 >= half_notes: if not shorthand: return 'diminished ' + current[0] return 'b' * (maj - half_notes) + current[1]
[docs]def from_shorthand(note, interval, up=True): """Return the note on interval up or down. Examples: >>> from_shorthand('A', 'b3') 'C' >>> from_shorthand('D', '2') 'E' >>> from_shorthand('E', '2', False) 'D' """ # warning should be a valid note. if not notes.is_valid_note(note): return False # [shorthand, interval function up, interval function down] shorthand_lookup = [ ['1', major_unison, major_unison], ['2', major_second, minor_seventh], ['3', major_third, minor_sixth], ['4', major_fourth, major_fifth], ['5', major_fifth, major_fourth], ['6', major_sixth, minor_third], ['7', major_seventh, minor_second], ] # Looking up last character in interval in shorthand_lookup and calling that # function. val = False for shorthand in shorthand_lookup: if shorthand[0] == interval[-1]: if up: val = shorthand[1](note) else: val = shorthand[2](note) # warning Last character in interval should be 1-7 if val == False: return False # Collect accidentals for x in interval: if x == '#': if up: val = notes.augment(val) else: val = notes.diminish(val) elif x == 'b': if up: val = notes.diminish(val) else: val = notes.augment(val) else: return val
[docs]def is_consonant(note1, note2, include_fourths=True): """Return True if the interval is consonant. A consonance is a harmony, chord, or interval considered stable, as opposed to a dissonance. This function tests whether the given interval is consonant. This basically means that it checks whether the interval is (or sounds like) a unison, third, sixth, perfect fourth or perfect fifth. In classical music the fourth is considered dissonant when used contrapuntal, which is why you can choose to exclude it. """ return (is_perfect_consonant(note1, note2, include_fourths) or is_imperfect_consonant(note1, note2))
[docs]def is_perfect_consonant(note1, note2, include_fourths=True): """Return True if the interval is a perfect consonant one. Perfect consonances are either unisons, perfect fourths or fifths, or octaves (which is the same as a unison in this model). Perfect fourths are usually included as well, but are considered dissonant when used contrapuntal, which is why you can exclude them. """ dhalf = measure(note1, note2) return dhalf in [0, 7] or include_fourths and dhalf == 5
[docs]def is_imperfect_consonant(note1, note2): """Return True id the interval is an imperfect consonant one. Imperfect consonances are either minor or major thirds or minor or major sixths. """ return measure(note1, note2) in [3, 4, 8, 9]
[docs]def is_dissonant(note1, note2, include_fourths=False): """Return True if the insterval is dissonant. This function tests whether an interval is considered unstable, dissonant. In the default case perfect fourths are considered consonant, but this can be changed by setting exclude_fourths to True. """ return not is_consonant(note1, note2, not include_fourths)