#!/usr/bin/python
# -*- coding: utf-8 -*-
# mingus - Music theory Python package, keys module.
# Copyright (C) 2010-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 keys.
This module provides a simple interface for dealing with keys.
"""
from mt_exceptions import FormatError, NoteFormatError, RangeError
import notes
import operator
from itertools import cycle, islice
keys = [
('Cb', 'ab'), # 7 b
('Gb', 'eb'), # 6 b
('Db', 'bb'), # 5 b
('Ab', 'f'), # 4 b
('Eb', 'c'), # 3 b
('Bb', 'g'), # 2 b
('F', 'd'), # 1 b
('C', 'a'), # nothing
('G', 'e'), # 1 #
('D', 'b'), # 2 #
('A', 'f#'), # 3 #
('E', 'c#'), # 4 #
('B', 'g#'), # 5 #
('F#', 'd#'), # 6 #
('C#', 'a#') # 7 #
]
major_keys = [couple[0] for couple in keys]
minor_keys = [couple[1] for couple in keys]
base_scale = ['C', 'D', 'E', 'F', 'G', 'A', 'B']
_key_cache = {}
[docs]def is_valid_key(key):
"""Return True if key is in a recognized format. False if not."""
for couple in keys:
if key in couple:
return True
return False
[docs]def get_key(accidentals=0):
"""Return the key corrisponding to accidentals.
Return the tuple containing the major key corrensponding to the
accidentals put as input, and his relative minor; negative numbers for
flats, positive numbers for sharps.
"""
if accidentals not in range(-7, 8):
raise RangeError('integer not in range (-7)-(+7).')
return keys[accidentals+7]
[docs]def get_key_signature(key='C'):
"""Return the key signature.
0 for C or a, negative numbers for flat key signatures, positive numbers
for sharp key signatures.
"""
if not is_valid_key(key):
raise NoteFormatError("unrecognized format for key '%s'" % key)
for couple in keys:
if key in couple:
accidentals = keys.index(couple) - 7
return accidentals
[docs]def get_key_signature_accidentals(key='C'):
"""Return the list of accidentals present into the key signature."""
accidentals = get_key_signature(key)
res = []
if accidentals < 0:
for i in range(-accidentals):
res.append('{0}{1}'.format(list(reversed(notes.fifths))[i], 'b'))
elif accidentals > 0:
for i in range(accidentals):
res.append('{0}{1}'.format(notes.fifths[i], '#'))
return res
[docs]def get_notes(key='C'):
"""Return an ordered list of the notes in this natural key.
Examples:
>>> get_notes('F')
['F', 'G', 'A', 'Bb', 'C', 'D', 'E']
>>> get_notes('c')
['C', 'D', 'Eb', 'F', 'G', 'Ab', 'Bb']
"""
if _key_cache.has_key(key):
return _key_cache[key]
if not is_valid_key(key):
raise NoteFormatError("unrecognized format for key '%s'" % key)
result = []
# Calculate notes
altered_notes = map(operator.itemgetter(0),
get_key_signature_accidentals(key))
if get_key_signature(key) < 0:
symbol = 'b'
elif get_key_signature(key) > 0:
symbol = '#'
raw_tonic_index = base_scale.index(key.upper()[0])
for note in islice(cycle(base_scale), raw_tonic_index, raw_tonic_index+7):
if note in altered_notes:
result.append('%s%s' % (note, symbol))
else:
result.append(note)
# Save result to cache
_key_cache[key] = result
return result
[docs]def relative_major(key):
"""Return the relative major of a minor key.
Example:
>>> relative_major('a')
'C'
"""
for couple in keys:
if key == couple[1]:
return couple[0]
raise NoteFormatError("'%s' is not a minor key" % key)
[docs]def relative_minor(key):
"""Return the relative minor of a major key.
Example:
>>> relative_minor('C')
'a'
"""
for couple in keys:
if key == couple[0]:
return couple[1]
raise NoteFormatError("'%s' is not a major key" % key)
[docs]class Key(object):
"""A key object."""
[docs] def __init__(self, key='C'):
self.key = key
if self.key[0].islower():
self.mode = 'minor'
else:
self.mode = 'major'
try:
symbol = self.key[1]
if symbol == '#':
symbol = 'sharp '
else:
symbol = 'flat '
except:
symbol = ''
self.name = '{0} {1}{2}'.format(self.key[0].upper(), symbol, self.mode)
self.signature = get_key_signature(self.key)
[docs] def __eq__(self, other):
if self.key == other.key:
return True
return False
[docs] def __ne__(self, other):
return not self.__eq__(other)