#!/usr/bin/python
# -*- coding: utf-8 -*-
# mingus - Music theory Python package, scales module.
# Copyright (C) 2008-2009, Bart Spaans
# Copyright (C) 2011, Carlo Stemberger
#
# 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 for dealing with scales.
The scales module allows you to create a plethora of scales. Here's a
little overview:
The diatonic scales
* Diatonic(note, semitones)
Ancient scales
* Ionian(note)
* Dorian(note)
* Phrygian(note)
* Lydian(note)
* Mixolydian(note)
* Aeolian(note)
* Locrian(note)
The major scales
* Major(note)
* HarmonicMajor(note)
The minor scales
* NaturalMinor(note)
* HarmonicMinor(note)
* MelodicMinor(note)
* Bachian(note)
* MinorNeapolitan(note)
Other scales
* Chromatic(note)
* WholeTone(note)
* Octatonic(note)
"""
import intervals
from notes import augment, diminish, reduce_accidentals
from keys import keys, get_notes
from mt_exceptions import NoteFormatError, FormatError, RangeError
[docs]def determine(notes):
"""Determine the scales containing the notes.
All major and minor scales are recognized.
Example:
>>> determine(['A', 'Bb', 'E', 'F#', 'G'])
['G melodic minor', 'G Bachian', 'D harmonic major']
"""
notes = set(notes)
res = []
for key in keys:
for scale in _Scale.__subclasses__():
if scale.type == 'major':
if (notes <= set(scale(key[0]).ascending()) or
notes <= set(scale(key[0]).descending())):
res.append(scale(key[0]).name)
elif scale.type == 'minor':
if (notes <= set(scale(get_notes(key[1])[0]).ascending()) or
notes <= set(scale(get_notes(key[1])[0]).descending())):
res.append(scale(get_notes(key[1])[0]).name)
return res
[docs]class _Scale(object):
"""General class implementing general methods.
Not to be used by the final user.
"""
[docs] def __init__(self, note, octaves):
if note.islower():
raise NoteFormatError("Unrecognised note '%s'" % note)
self.tonic = note
self.octaves = octaves
[docs] def __repr__(self):
return "<Scale object ('{0}')>".format(self.name)
[docs] def __str__(self):
return 'Ascending: {0}\nDescending: {1}'.format(
' '.join(self.ascending()), ' '.join(self.descending()))
[docs] def __eq__(self, other):
if self.ascending() == other.ascending():
if self.descending() == other.descending():
return True
return False
[docs] def __ne__(self, other):
return not self.__eq__(other)
[docs] def __len__(self):
return len(self.ascending())
[docs] def ascending(self):
"""Return the list of ascending notes."""
raise NotImplementedError
[docs] def descending(self):
"""Return the list of descending notes."""
return list(reversed(self.ascending()))
[docs] def degree(self, degree_number, direction='a'):
"""Return the asked scale degree.
The direction of the scale is 'a' for ascending (default) and 'd'
for descending.
"""
if degree_number < 1:
raise RangeError("degree '%s' out of range" % degree_number)
if direction == 'a':
notes = self.ascending()[:-1]
return notes[degree_number-1]
elif direction == 'd':
notes = reversed(self.descending())[:-1]
return notes[degree_number-1]
else:
raise FormatError("Unrecognised direction '%s'" % direction)
# The diatonic scales
[docs]class Diatonic(_Scale):
"""The diatonic scale.
Example:
>>> 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'
[docs] def __init__(self, note, semitones, octaves=1):
"""Create the diatonic scale starting on the chosen note.
The second parameter is a tuple representing the position of
semitones.
"""
super(Diatonic, self).__init__(note, octaves)
self.semitones = semitones
self.name = '{0} diatonic, semitones in {1}'.format(self.tonic,
self.semitones)
[docs] def ascending(self):
notes = [self.tonic]
for n in range(1, 7):
if n in self.semitones:
notes.append(intervals.minor_second(notes[-1]))
else:
notes.append(intervals.major_second(notes[-1]))
return notes * self.octaves + [notes[0]]
# Ancient scales
[docs]class Ionian(_Scale):
"""The ionian scale.
Example:
>>> print Ionian('C')
Ascending: C D E F G A B C
Descending: C B A G F E D C
"""
type = 'ancient'
[docs] def __init__(self, note, octaves=1):
"""Create the ionian mode scale starting on the chosen note."""
super(Ionian, self).__init__(note, octaves)
self.name = '{0} ionian'.format(self.tonic)
[docs] def ascending(self):
notes = Diatonic(self.tonic, (3, 7)).ascending()[:-1]
return notes * self.octaves + [notes[0]]
[docs]class Dorian(_Scale):
"""The dorian scale.
Example:
>>> print Dorian('D')
Ascending: D E F G A B C D
Descending: D C B A G F E D
"""
type = 'ancient'
[docs] def __init__(self, note, octaves=1):
"""Create the dorian mode scale starting on the chosen note."""
super(Dorian, self).__init__(note, octaves)
self.name = '{0} dorian'.format(self.tonic)
[docs] def ascending(self):
notes = Diatonic(self.tonic, (2, 6)).ascending()[:-1]
return notes * self.octaves + [notes[0]]
[docs]class Phrygian(_Scale):
"""The phrygian scale.
Example:
>>> print Phrygian('E')
Ascending: E F G A B C D E
Descending: E D C B A G F E
"""
type = 'ancient'
[docs] def __init__(self, note, octaves=1):
"""Create the phrygian mode scale starting on the chosen note."""
super(Phrygian, self).__init__(note, octaves)
self.name = '{0} phrygian'.format(self.tonic)
[docs] def ascending(self):
notes = Diatonic(self.tonic, (1, 5)).ascending()[:-1]
return notes * self.octaves + [notes[0]]
[docs]class Lydian(_Scale):
"""The lydian scale.
Example:
>>> print Lydian('F')
Ascending: F G A B C D E F
Descending: F E D C B A G F
"""
type = 'ancient'
[docs] def __init__(self, note, octaves=1):
"""Create the lydian mode scale starting on the chosen note."""
super(Lydian, self).__init__(note, octaves)
self.name = '{0} lydian'.format(self.tonic)
[docs] def ascending(self):
notes = Diatonic(self.tonic, (4, 7)).ascending()[:-1]
return notes * self.octaves + [notes[0]]
[docs]class Mixolydian(_Scale):
"""The mixolydian scale.
Example:
>>> print Mixolydian('G')
Ascending: G A B C D E F G
Descending: G F E D C B A G
"""
type = 'ancient'
[docs] def __init__(self, note, octaves=1):
"""Create the mixolydian mode scale starting on the chosen note."""
super(Mixolydian, self).__init__(note, octaves)
self.name = '{0} mixolydian'.format(self.tonic)
[docs] def ascending(self):
notes = Diatonic(self.tonic, (3, 6)).ascending()[:-1]
return notes * self.octaves + [notes[0]]
[docs]class Aeolian(_Scale):
"""The aeolian scale.
Example:
>>> print Aeolian('A')
Ascending: A B C D E F G A
Descending: A G F E D C B A
"""
type = 'ancient'
[docs] def __init__(self, note, octaves=1):
"""Create the aeolian mode scale starting on the chosen note."""
super(Aeolian, self).__init__(note, octaves)
self.name = '{0} aeolian'.format(self.tonic)
[docs] def ascending(self):
notes = Diatonic(self.tonic, (2, 5)).ascending()[:-1]
return notes * self.octaves + [notes[0]]
[docs]class Locrian(_Scale):
"""The locrian scale.
Example:
>>> print Locrian('B')
Ascending: B C D E F G A B
Descending: B A G F E D C B
"""
type = 'ancient'
[docs] def __init__(self, note, octaves=1):
"""Create the locrian mode scale starting on the chosen note."""
super(Locrian, self).__init__(note, octaves)
self.name = '{0} locrian'.format(self.tonic)
[docs] def ascending(self):
notes = Diatonic(self.tonic, (1, 4)).ascending()[:-1]
return notes * self.octaves + [notes[0]]
# The major scales
[docs]class Major(_Scale):
"""The major scale.
Example:
>>> print Major('A')
Ascending: A B C# D E F# G# A
Descending: A G# F# E D C# B A
"""
type = 'major'
[docs] def __init__(self, note, octaves=1):
"""Create the major scale starting on the chosen note."""
super(Major, self).__init__(note, octaves)
self.name = '{0} major'.format(self.tonic)
[docs] def ascending(self):
notes = get_notes(self.tonic)
return notes * self.octaves + [notes[0]]
[docs]class HarmonicMajor(_Scale):
"""The harmonic major scale.
Example:
>>> print HarmonicMajor('C')
Ascending: C D E F G Ab B C
Descending: C B Ab G F E D C
"""
type = 'major'
[docs] def __init__(self, note, octaves=1):
"""Create the harmonic major scale starting on the chosen note."""
super(HarmonicMajor, self).__init__(note, octaves)
self.name = '{0} harmonic major'.format(self.tonic)
[docs] def ascending(self):
notes = Major(self.tonic).ascending()[:-1]
notes[5] = diminish(notes[5])
return notes * self.octaves + [notes[0]]
# The minor scales
[docs]class NaturalMinor(_Scale):
"""The natural minor scale.
Example:
>>> print NaturalMinor('A')
Ascending: A B C D E F G A
Descending: A G F E D C B A
"""
type = 'minor'
[docs] def __init__(self, note, octaves=1):
"""Return the natural minor scale starting on the chosen note."""
super(NaturalMinor, self).__init__(note, octaves)
self.name = '{0} natural minor'.format(self.tonic)
[docs] def ascending(self):
notes = get_notes(self.tonic.lower())
return notes * self.octaves + [notes[0]]
[docs]class HarmonicMinor(_Scale):
"""The harmonic minor scale.
Example:
>>> print HarmonicMinor('A')
Ascending: A B C D E F G# A
Descending: A G# F E D C B A
"""
type = 'minor'
[docs] def __init__(self, note, octaves=1):
"""Create the harmonic minor scale starting on the chosen note."""
super(HarmonicMinor, self).__init__(note, octaves)
self.name = '{0} harmonic minor'.format(self.tonic)
[docs] def ascending(self):
notes = NaturalMinor(self.tonic).ascending()[:-1]
notes[6] = augment(notes[6])
return notes * self.octaves + [notes[0]]
[docs]class MelodicMinor(_Scale):
"""The melodic minor scale.
Example:
>>> print MelodicMinor('A')
Ascending: A B C D E F# G# A
Descending: A G F E D C B A
"""
type = 'minor'
[docs] def __init__(self, note, octaves=1):
"""Create the melodic minor scale starting on the chosen note."""
super(MelodicMinor, self).__init__(note, octaves)
self.name = '{0} melodic minor'.format(self.tonic)
[docs] def ascending(self):
notes = NaturalMinor(self.tonic).ascending()[:-1]
notes[5] = augment(notes[5])
notes[6] = augment(notes[6])
return notes * self.octaves + [notes[0]]
[docs] def descending(self):
notes = NaturalMinor(self.tonic).descending()[:-1]
return notes * self.octaves + [notes[0]]
[docs]class Bachian(_Scale):
"""The Bachian scale.
Example:
>>> print Bachian('A')
Ascending: A B C D E F# G# A
Descending: A G# F# E D C B A
"""
type = 'minor'
[docs] def __init__(self, note, octaves=1):
"""Create the Bachian (also known as "real melodic minor" and "jazz")
scale starting on the chosen note."""
super(Bachian, self).__init__(note, octaves)
self.name = '{0} Bachian'.format(self.tonic)
[docs] def ascending(self):
notes = MelodicMinor(self.tonic).ascending()[:-1]
return notes * self.octaves + [notes[0]]
[docs]class MinorNeapolitan(_Scale):
"""The minor Neapolitan scale.
Example:
>>> print MinorNeapolitan('A')
Ascending: A Bb C D E F G# A
Descending: A G F E D C Bb A
"""
type = 'minor'
[docs] def __init__(self, note, octaves=1):
"""Create the minor Neapolitan scale starting on the chosen note."""
super(MinorNeapolitan, self).__init__(note, octaves)
self.name = '{0} minor Neapolitan'.format(self.tonic)
[docs] def ascending(self):
notes = HarmonicMinor(self.tonic).ascending()[:-1]
notes[1] = diminish(notes[1])
return notes * self.octaves + [notes[0]]
[docs] def descending(self):
notes = NaturalMinor(self.tonic).descending()[:-1]
notes[6] = diminish(notes[6])
return notes * self.octaves + [notes[0]]
# Other scales
[docs]class Chromatic(_Scale):
"""The chromatic scale.
Examples:
>>> 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')
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
"""
type = 'other'
[docs] def __init__(self, key, octaves=1):
"""Create the chromatic scale in the chosen key."""
self.key = key
self.tonic = get_notes(key)[0]
self.octaves = octaves
self.name = '{0} chromatic'.format(self.tonic)
[docs] def ascending(self):
notes = [self.tonic]
for note in get_notes(self.key)[1:] + [self.tonic]:
if intervals.determine(notes[-1], note) == ('major second'):
notes.append(augment(notes[-1]))
notes.append(note)
else:
notes.append(note)
notes.pop()
return notes * self.octaves + [notes[0]]
[docs] def descending(self):
notes = [self.tonic]
for note in reversed(get_notes(self.key)):
if intervals.determine(note, notes[-1]) == ('major second'):
notes.append(reduce_accidentals(diminish(notes[-1])))
notes.append(note)
else:
notes.append(note)
notes.pop()
return notes * self.octaves + [notes[0]]
[docs]class WholeTone(_Scale):
"""The whole tone scale.
Example:
>>> print WholeTone('C')
Ascending: C D E F# G# A# C
Descending: C A# G# F# E D C
"""
type = 'other'
[docs] def __init__(self, note, octaves=1):
"""Create the whole tone scale starting on the chosen note."""
super(WholeTone, self).__init__(note, octaves)
self.name = '{0} whole tone'.format(self.tonic)
[docs] def ascending(self):
notes = [self.tonic]
for note in range(5):
notes.append(intervals.major_second(notes[-1]))
return notes * self.octaves + [notes[0]]
[docs]class Octatonic(_Scale):
"""The octatonic scale.
Example:
>>> print Octatonic('C')
Ascending: C D Eb F Gb Ab A B C
Descending: C B A Ab Gb F Eb D C
"""
type = 'other'
[docs] def __init__(self, note, octaves=1):
"""Create the octatonic (also known as "diminshed") scale starting
on the chosen note."""
super(Octatonic, self).__init__(note, octaves)
self.name = '{0} octatonic'.format(self.tonic)
[docs] def ascending(self):
notes = [self.tonic]
for i in range(3):
notes.extend(
[intervals.major_second(notes[-1]),
intervals.minor_third(notes[-1])])
notes.append(intervals.major_seventh(notes[0]))
notes[-2] = intervals.major_sixth(notes[0])
return notes * self.octaves + [notes[0]]