VocalEasel/mma/MMA/roman.py
Matthias Neeracher f54adbeec5 Update to MMA 1.7
2011-07-26 22:49:39 +00:00

168 lines
6.0 KiB
Python

# roman.py
"""
This module is an integeral part of the program
MMA - Musical Midi Accompaniment.
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 2 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, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Bob van der Poel <bob@mellowood.ca>
Roman numeral chord to standard notations.
"""
from common import *
from MMA.keysig import keySig
# Table of scales ... a list of 7 notes for each possible Major/Minor scale
majTable = { 'C': ('C', 'D', 'E', 'F', 'G', 'A', 'B'),
'C#': ('C#', 'D#', 'F', 'F#', 'G#', 'A#', 'C'),
'D': ('D', 'E', 'F#', 'G', 'A', 'B', 'C#'),
'Db': ('Db', 'Eb', 'F', 'Gb', 'Ab', 'Bb', 'C'),
'D#': ('D#', 'F', 'G', 'G#', 'A#', 'C', 'D'),
'Eb': ('Eb', 'F', 'G', 'Ab', 'Bb', 'C', 'D'),
'E': ('E', 'F#', 'G#', 'A', 'B', 'C#', 'D#'),
'F': ('F', 'G', 'A', 'Bb', 'C', 'D', 'E'),
'F#': ('F#', 'G#', 'A#', 'B', 'C#', 'D#', 'F'),
'Gb': ('Gb', 'Ab', 'Bb', 'B', 'Db', 'Eb', 'F'),
'G': ('G', 'A', 'B', 'C', 'D', 'E', 'F#'),
'G#': ('G#', 'A#', 'C', 'C#', 'D#', 'F', 'G'),
'Ab': ('Ab', 'Bb', 'C', 'Db', 'Eb', 'F', 'G'),
'A': ('A', 'B', 'C#', 'D', 'E', 'F#', 'G#'),
'A#': ('A#', 'C', 'D', 'D#', 'F', 'G', 'A'),
'Bb': ('Bb', 'C', 'D', 'Eb', 'F', 'G', 'A'),
'B': ('B', 'C#', 'D#', 'E', 'F#', 'G#', 'A#') }
minTable = { 'C': ('C', 'D', 'Eb', 'F', 'G', 'Ab', 'Bb'),
'C#': ('C#', 'D#', 'E', 'F#', 'G#', 'A', 'B'),
'Db': ('Db', 'Eb', 'E', 'Gb', 'Ab', 'A', 'B'),
'D': ('D', 'E', 'F', 'G', 'A', 'Bb', 'C'),
'D#': ('D#', 'F', 'F#', 'G#', 'A#', 'B', 'C#'),
'Eb': ('Eb', 'F', 'Gb', 'Ab', 'Bb', 'B', 'Db'),
'E': ('E', 'F#', 'G', 'A', 'B', 'C', 'D'),
'F': ('F', 'G', 'Ab', 'Bb', 'C', 'Db', 'Eb'),
'F#': ('F#', 'G#', 'A', 'B', 'C#', 'D', 'E'),
'Gb': ('Gb', 'Ab', 'A', 'B', 'Db', 'D', 'E'),
'F': ('F', 'G', 'G#', 'A#', 'C', 'C#', 'D#'),
'G': ('G', 'A', 'A#', 'C', 'D', 'D#', 'F'),
'G#': ('G#', 'A#', 'B', 'C#', 'D#', 'E', 'F#'),
'Ab': ('Ab', 'Bb', 'B', 'Db', 'Eb', 'E', 'Gb'),
'A': ('A', 'B', 'C', 'D', 'E', 'F', 'G'),
'A#': ('A#', 'C', 'C#', 'D#', 'F', 'F#', 'G#'),
'Bb': ('Bb', 'C', 'Db', 'Eb', 'F', 'Gb', 'Ab'),
'B': ('B', 'C#', 'D', 'E', 'F#', 'G', 'A') }
uroman = {'I':0, 'II':1, 'III':2, 'IV':3, 'V':4, 'VI':5, 'VII':6}
lroman = {'i':0, 'ii':1, 'iii':2, 'iv':3, 'v':4, 'vi':5, 'vii':6}
arabic = {'1':0, '2':1, '3':2, '4':3, '5':4, '6':5, '7':6 }
doubleflat = { 'Cbb':'Bb', 'Dbb':'C', 'Ebb':'D', 'Fbb':'Eb',
'Gbb':'F', 'Abb':'G', 'Bbb':'A' }
doubleshart = { 'C##':'D', 'D##':'E', 'E##':'F#', 'F##':'G',
'G##':'A', 'A##':'B', 'B##':'C#' }
convertable = { 'm'+chr(176):'dim3', 'm0':'dim3', 'mo':'dim3', 'mO':'dim3',
'm'+chr(176)+'7':'dim7', 'm07':'dim7', 'mo7':'dim7', 'mO7':'dim7',
'm'+chr(248)+'7':'m7b5', 'm-07':'m7b5', 'm-o7':'m7b5', 'm-O7':'m7b5' }
def rvalue(s):
""" Convert a roman or arabic numeral to value (-1). """
if s in uroman:
return uroman[s]
elif s in lroman:
return lroman[s]
elif s in arabic:
return arabic[s]
else:
if s[0].isdigit:
error ("Unknown Arabic value '%s'. Use 1 to 7." % s)
else:
error("Unknown Roman numeral '%s'. Use 'I' to 'VII' in all u/l case." % s)
def convert(sym):
""" Convert a roman numeral to a standard chord name. """
keysig, minor = keySig.kName
# figure number of roman numerals leading symbol
sym=list(sym)
rm=''
while(sym) and sym[0] in ('I', 'V', 'i', 'v'):
rm += sym.pop(0)
sym=''.join(sym)
if rm[0].islower():
isminor = True
else:
isminor = False
offset = rvalue(rm)
# convert the roman to a pitch ... just a table lookup
if minor:
pitch = minTable[keysig][offset]
else:
pitch = majTable[keysig][offset]
"""
Adjust the pitch if the remainder starts with a # or b. (Note, '&'
was converted to 'b' early in the chord parser.
This permits technically incorrect things like 'Ib' which end up (in C)
as 'Cb'. Useful with dim chords. We also need to worry about doubles!
"""
if sym.startswith('b') or sym.startswith('#'):
pitch += sym[0]
sym = sym[1:]
if pitch.endswith('#b') or pitch.endswith('b#'): # 'b#' cancel each other
pitch = pitch[:-2]
elif pitch.endswith('##'):
pitch = doublesharp[pitch]
elif pitch.endswith('bb'):
pitch = doubleflat[pitch]
""" Now translate the quality. This is whatever was left after
stripping off the number and sharp/flat. Two uglies:
- some names are different in RN and standard, ie 07 .. dim7
- lowercase == minor, so we add in 'm' to start of the
leftover quality, unless it's there already. "v07" becomes "Xdim7",
and "Vm0" (wrong!) still works and becomes Xdim3"
- special trap for double 'm's. Hide conversion of "vm7" which becomes
"Xmm7" and then "Xm7".
"""
if isminor and not sym.startswith('m'):
sym = 'm'+sym
if sym in convertable:
sym = convertable[sym]
return pitch + sym