1# -*- coding: utf-8 -*-
2# -----------------------------------------------------------------------------
3# Name:         roman.py
4# Purpose:      music21 classes for doing Roman Numeral / Tonal analysis
5#
6# Authors:      Michael Scott Cuthbert
7#               Christopher Ariza
8#
9# Copyright:    Copyright © 2011-2013 Michael Scott Cuthbert and the music21
10#               Project
11# License:      BSD, see license.txt
12# -----------------------------------------------------------------------------
13'''
14Music21 class for dealing with Roman Numeral analysis
15'''
16import enum
17import unittest
18import copy
19import re
20from typing import Union, Optional, List, Tuple
21
22# when python 3.7 is removed from support:
23# from typing import Literal
24
25from collections import namedtuple
26
27from music21 import environment
28from music21.figuredBass import notation as fbNotation
29from music21 import scale
30from music21 import pitch
31from music21 import key
32from music21 import note
33from music21 import interval
34from music21 import harmony
35from music21 import exceptions21
36from music21 import common
37from music21 import chord
38
39FigureTuple = namedtuple('FigureTuple', 'aboveBass alter prefix')
40ChordFigureTuple = namedtuple('ChordFigureTuple', 'aboveBass alter prefix pitch')
41
42
43_MOD = 'roman'
44environLocal = environment.Environment(_MOD)
45
46# TODO: setting inversion should change the figure
47
48# -----------------------------------------------------------------------------
49
50
51SHORTHAND_RE = re.compile(r'#*-*b*o*[1-9xyz]')
52ENDWITHFLAT_RE = re.compile(r'[b\-]$')
53
54# cache all Key/Scale objects created or passed in; re-use
55# permits using internally scored pitch segments
56_scaleCache = {}
57_keyCache = {}
58
59# create a single notation object for RN initialization, for type-checking,
60# but it will always be replaced.
61_NOTATION_SINGLETON = fbNotation.Notation()
62
63
64def _getKeyFromCache(keyStr: str) -> key.Key:
65    '''
66    get a key from the cache if it is there; otherwise
67    create a new key and put it in the cache and return it.
68    '''
69    if keyStr in _keyCache:
70        # adding copy.copy will at least prevent small errors at a cost of only 3 nano-seconds
71        # of added time.  A deepcopy, unfortunately, take 2.8ms, which is longer than not
72        # caching at all.  And not caching at all really slows down things like RomanText.
73        # This at least will prevent what happens if `.key.mode` is changed
74        keyObj = copy.copy(_keyCache[keyStr])
75    else:
76        keyObj = key.Key(keyStr)
77        _keyCache[keyObj.tonicPitchNameWithCase] = keyObj
78    return keyObj
79
80
81# figure shorthands for all modes.
82figureShorthands = {
83    '53': '',
84    '3': '',
85    '63': '6',
86    '753': '7',
87    '75': '7',  # controversial perhaps
88    '73': '7',  # controversial perhaps
89    '9753': '9',
90    '975': '9',  # controversial perhaps
91    '953': '9',  # controversial perhaps
92    '97': '9',  # controversial perhaps
93    '95': '9',  # controversial perhaps
94    '93': '9',  # controversial perhaps
95    '653': '65',
96    '6b53': '6b5',
97    '643': '43',
98    '642': '42',
99    # '6b5bb3': 'o65',
100    'bb7b5b3': 'o7',
101    'b7b5b3': 'ø7',
102    'bb7b53': 'o7',
103    'b7b53': 'ø7',
104}
105
106figureShorthandsMode = {
107    'major': {
108    },
109    'minor': {
110    }
111}
112
113
114# this is sort of a crock...  :-)  but it's very helpful.
115functionalityScores = {
116    'I': 100,
117    'i': 90,
118    'V7': 80,
119    'V': 70,
120    'V65': 68,
121    'I6': 65,
122    'V6': 63,
123    'V43': 61,
124    'I64': 60,
125    'IV': 59,
126    'i6': 58,
127    'viio7': 57,
128    'V42': 55,
129    'viio65': 53,
130    'viio6': 52,
131    '#viio65': 51,
132    'ii': 50,
133    '#viio6': 49,
134    'ii65': 48,
135    'ii43': 47,
136    'ii42': 46,
137    'IV6': 45,
138    'ii6': 43,
139    'VI': 42,
140    '#VI': 41,
141    'vi': 40,
142    'viio': 39,
143    '#viio': 38,
144    'iio': 37,  # common in Minor
145    'iio42': 36,
146    'bII6': 35,  # Neapolitan
147    'It6': 34,
148    'Ger65': 33,
149    'iio43': 32,
150    'iio65': 31,
151    'Fr43': 30,
152    '#vio': 28,
153    '#vio6': 27,
154    'III': 22,
155    'Sw43': 21,
156    'v': 20,
157    'VII': 19,
158    'VII7': 18,
159    'IV65': 17,
160    'IV7': 16,
161    'iii': 15,
162    'iii6': 12,
163    'vi6': 10,
164}
165
166
167# -----------------------------------------------------------------------------
168
169
170def expandShortHand(shorthand):
171    '''
172    Expands shorthand notation into a list with all figures expanded:
173
174    >>> roman.expandShortHand('64')
175    ['6', '4']
176
177    >>> roman.expandShortHand('973')
178    ['9', '7', '3']
179
180    >>> roman.expandShortHand('11b3')
181    ['11', 'b3']
182
183    >>> roman.expandShortHand('b13#9-6')
184    ['b13', '#9', '-6']
185
186    >>> roman.expandShortHand('-')
187    ['5', '-3']
188
189
190    Slashes don't matter
191
192    >>> roman.expandShortHand('6/4')
193    ['6', '4']
194
195    Note that this is not where abbreviations get expanded
196
197    >>> roman.expandShortHand('')
198    []
199
200    >>> roman.expandShortHand('7')  # not 7, 5, 3
201    ['7']
202
203    >>> roman.expandShortHand('4/3')  # not 6, 4, 3
204    ['4', '3']
205
206    Note that this is ['6'] not ['6', '3']:
207
208    >>> roman.expandShortHand('6')
209    ['6']
210
211
212    Returns a list of individual shorthands.
213    '''
214    shorthand = shorthand.replace('/', '')  # this line actually seems unnecessary.
215    if ENDWITHFLAT_RE.match(shorthand):
216        shorthand += '3'
217    shorthand = re.sub('11', 'x', shorthand)
218    shorthand = re.sub('13', 'y', shorthand)
219    shorthand = re.sub('15', 'z', shorthand)
220    shorthandGroups = SHORTHAND_RE.findall(shorthand)
221    if len(shorthandGroups) == 1 and shorthandGroups[0].endswith('3'):
222        shorthandGroups = ['5', shorthandGroups[0]]
223
224    shGroupOut = []
225    for sh in shorthandGroups:
226        sh = re.sub('x', '11', sh)
227        sh = re.sub('y', '13', sh)
228        sh = re.sub('z', '15', sh)
229        shGroupOut.append(sh)
230    return shGroupOut
231
232
233def correctSuffixForChordQuality(chordObj, inversionString):
234    '''
235    Correct a given inversionString suffix given a chord of various qualities.
236
237    >>> c = chord.Chord('E3 C4 G4')
238    >>> roman.correctSuffixForChordQuality(c, '6')
239    '6'
240
241    >>> c = chord.Chord('E3 C4 G-4')
242    >>> roman.correctSuffixForChordQuality(c, '6')
243    'o6'
244
245    '''
246    fifthType = chordObj.semitonesFromChordStep(5)
247    if fifthType == 6:
248        qualityName = 'o'
249    elif fifthType == 8:
250        qualityName = '+'
251    else:
252        qualityName = ''
253
254    if inversionString and (inversionString.startswith('o')
255                            or inversionString.startswith('°')
256                            or inversionString.startswith('/o')
257                            or inversionString.startswith('ø')):
258        if qualityName == 'o':  # don't call viio7, viioo7.
259            qualityName = ''
260
261    seventhType = chordObj.semitonesFromChordStep(7)
262    if seventhType and fifthType == 6:
263        # there is a seventh and this is a diminished 5
264        if seventhType == 10 and qualityName == 'o':
265            qualityName = 'ø'
266        elif seventhType != 9:
267            pass  # do something for odd odd chords built on diminished triad.
268    # print(inversionString, fifthName)
269    return qualityName + inversionString
270
271
272def postFigureFromChordAndKey(chordObj, keyObj=None):
273    '''
274    Returns the post RN figure for a given chord in a given key.
275
276    If keyObj is none, it uses the root as a major key:
277
278    >>> from music21 import roman
279    >>> roman.postFigureFromChordAndKey(
280    ...     chord.Chord(['F#2', 'D3', 'A-3', 'C#4']),
281    ...     key.Key('C'),
282    ...     )
283    'o6#5b3'
284
285    The function substitutes shorthand (e.g., '6' not '63')
286
287    >>> roman.postFigureFromChordAndKey(
288    ...     chord.Chord(['E3', 'C4', 'G4']),
289    ...     key.Key('C'),
290    ...     )
291    '6'
292
293    >>> roman.postFigureFromChordAndKey(
294    ...     chord.Chord(['E3', 'C4', 'G4', 'B-5']),
295    ...     key.Key('F'),
296    ...     )
297    '65'
298
299    >>> roman.postFigureFromChordAndKey(
300    ...     chord.Chord(['E3', 'C4', 'G4', 'B-5']),
301    ...     key.Key('C'),
302    ...     )
303    '6b5'
304
305
306    We reduce common omissions from seventh chords to be '7' instead
307    of '75', '73', etc.
308
309    >>> roman.postFigureFromChordAndKey(
310    ...     chord.Chord(['A3', 'E-4', 'G-4']),
311    ...     key.Key('b-'),
312    ...     )
313    'o7'
314
315    Returns string.
316
317    OMIT_FROM_DOCS
318
319    Fails on German Augmented 6th chords in root position.  Calls them
320    half-diminished chords.
321    '''
322    if keyObj is None:
323        keyObj = key.Key(chordObj.root())
324    chordFigureTuples = figureTuples(chordObj, keyObj)
325    bassFigureAlter = chordFigureTuples[0].alter
326
327    allFigureStringList = []
328
329    third = chordObj.third
330    fifth = chordObj.fifth
331    # seventh = chordObj.seventh
332
333    chordCardinality = chordObj.pitchClassCardinality
334    if chordCardinality != 3:
335        chordObjIsStandardTriad = False
336        isMajorTriad = False
337        isMinorTriad = False
338    else:
339        isMajorTriad = chordObj.isMajorTriad()
340        # short-circuit this expensive call if we know it's not going to be true.
341        isMinorTriad = False if isMajorTriad else chordObj.isMinorTriad()
342        chordObjIsStandardTriad = (
343            isMajorTriad
344            or isMinorTriad
345            or chordObj.isDiminishedTriad()  # check most common first
346            or chordObj.isAugmentedTriad()  # then least common.
347        )
348
349    for ft in sorted(chordFigureTuples,
350                     key=lambda tup: (-1 * tup.aboveBass, tup.alter, tup.pitch.ps)):
351        # (diatonicIntervalNum, alter, alterStr, pitchObj) = figureTuple
352        prefix = ft.prefix
353
354        if ft.aboveBass != 1 and ft.pitch is third:
355            if isMajorTriad or isMinorTriad:
356                prefix = ''  # alterStr[1:]
357            # elif isMinorTriad and ft.alter > 0:
358            #    prefix = ''  # alterStr[1:]
359        elif (ft.aboveBass != 1
360              and ft.pitch is fifth
361              and chordObjIsStandardTriad):
362            prefix = ''  # alterStr[1:]
363
364        if ft.aboveBass == 1:
365            if ft.alter != bassFigureAlter and prefix != '':
366                # mark altered octaves as 8 not 1
367                figureString = prefix + '8'
368                if figureString not in allFigureStringList:
369                    # filter duplicates and put at beginning
370                    allFigureStringList.insert(0, figureString)
371        else:
372            figureString = prefix + str(ft.aboveBass)
373            # filter out duplicates.
374            if figureString not in allFigureStringList:
375                allFigureStringList.append(figureString)
376
377    allFigureString = ''.join(allFigureStringList)
378    key_mode = keyObj.mode
379    if key_mode in figureShorthandsMode and allFigureString in figureShorthandsMode[key_mode]:
380        allFigureString = figureShorthandsMode[allFigureString]
381    elif allFigureString in figureShorthands:
382        allFigureString = figureShorthands[allFigureString]
383
384    # simplify common omissions from 7th chords
385    if allFigureString in ('75', '73'):
386        allFigureString = '7'
387
388    allFigureString = correctSuffixForChordQuality(chordObj, allFigureString)
389
390    return allFigureString
391
392
393def figureTuples(chordObject, keyObject):
394    '''
395    Return a set of tuplets for each pitch showing the presence of a note, its
396    interval above the bass its alteration (float) from a step in the given
397    key, an `alterationString`, and the pitch object.
398
399    Note though that for roman numerals, the applicable key is almost always
400    the root.
401
402    For instance, in C major, F# D A- C# would be:
403
404    >>> from music21 import roman
405    >>> roman.figureTuples(
406    ...     chord.Chord(['F#2', 'D3', 'A-3', 'C#4']),
407    ...     key.Key('C'),
408    ...     )
409    [ChordFigureTuple(aboveBass=1, alter=1.0, prefix='#', pitch=<music21.pitch.Pitch F#2>),
410     ChordFigureTuple(aboveBass=6, alter=0.0, prefix='', pitch=<music21.pitch.Pitch D3>),
411     ChordFigureTuple(aboveBass=3, alter=-1.0, prefix='b', pitch=<music21.pitch.Pitch A-3>),
412     ChordFigureTuple(aboveBass=5, alter=1.0, prefix='#', pitch=<music21.pitch.Pitch C#4>)]
413
414    In c-minor, the A- is a normal note, so the prefix is '' not 'b'.  The natural minor is used
415    exclusively.
416
417    >>> roman.figureTuples(
418    ...     chord.Chord(['F#2', 'D3', 'A-3', 'C#4']),
419    ...     key.Key('c'),
420    ...     )
421    [ChordFigureTuple(aboveBass=1, alter=1.0, prefix='#', pitch=<music21.pitch.Pitch F#2>),
422     ChordFigureTuple(aboveBass=6, alter=0.0, prefix='', pitch=<music21.pitch.Pitch D3>),
423     ChordFigureTuple(aboveBass=3, alter=0.0, prefix='', pitch=<music21.pitch.Pitch A-3>),
424     ChordFigureTuple(aboveBass=5, alter=1.0, prefix='#', pitch=<music21.pitch.Pitch C#4>)]
425
426    A C dominant-seventh chord in c minor alters the bass but not the 7th degree
427
428    >>> roman.figureTuples(
429    ...     chord.Chord(['E3', 'C4', 'G4', 'B-5']),
430    ...     key.Key('c'),
431    ...     )
432    [ChordFigureTuple(aboveBass=1, alter=1.0, prefix='#', pitch=<music21.pitch.Pitch E3>),
433     ChordFigureTuple(aboveBass=6, alter=0.0, prefix='', pitch=<music21.pitch.Pitch C4>),
434     ChordFigureTuple(aboveBass=3, alter=0.0, prefix='', pitch=<music21.pitch.Pitch G4>),
435     ChordFigureTuple(aboveBass=5, alter=0.0, prefix='', pitch=<music21.pitch.Pitch B-5>)]
436
437    >>> roman.figureTuples(
438    ...     chord.Chord(['C4', 'E4', 'G4', 'C#4']),
439    ...     key.Key('C'),
440    ...     )
441    [ChordFigureTuple(aboveBass=1, alter=0.0, prefix='', pitch=<music21.pitch.Pitch C4>),
442     ChordFigureTuple(aboveBass=3, alter=0.0, prefix='', pitch=<music21.pitch.Pitch E4>),
443     ChordFigureTuple(aboveBass=5, alter=0.0, prefix='', pitch=<music21.pitch.Pitch G4>),
444     ChordFigureTuple(aboveBass=1, alter=1.0, prefix='#', pitch=<music21.pitch.Pitch C#4>)]
445    '''
446    result = []
447    bass = chordObject.bass()
448    for thisPitch in chordObject.pitches:
449        shortTuple = figureTupleSolo(thisPitch, keyObject, bass)
450        appendTuple = ChordFigureTuple(shortTuple.aboveBass,
451                                       shortTuple.alter,
452                                       shortTuple.prefix,
453                                       thisPitch)
454        result.append(appendTuple)
455    return result
456
457
458def figureTupleSolo(
459    pitchObj: pitch.Pitch,
460    keyObj: key.Key,
461    bass: pitch.Pitch
462) -> FigureTuple:
463    '''
464    Return a single tuple for a pitch and key showing the interval above
465    the bass, its alteration from a step in the given key, an alteration
466    string, and the pitch object.
467
468    For instance, in C major, an A-3 above an F# bass would be:
469
470    >>> roman.figureTupleSolo(
471    ...     pitch.Pitch('A-3'),
472    ...     key.Key('C'),
473    ...     pitch.Pitch('F#2'),
474    ...     )
475    FigureTuple(aboveBass=3, alter=-1.0, prefix='b')
476
477    These figures can be more complex in minor, so this is a good reference, showing
478    that natural minor is always used.
479
480    >>> c = key.Key('c')
481    >>> c_as_bass = pitch.Pitch('C3')
482    >>> for name in ('E--', 'E-', 'E', 'E#', 'A--', 'A-', 'A', 'A#', 'B--', 'B-', 'B', 'B#'):
483    ...     ft = roman.figureTupleSolo(pitch.Pitch(name + '4'), c, c_as_bass)
484    ...     print(f'{name:4s} {ft}')
485    E--  FigureTuple(aboveBass=3, alter=-1.0, prefix='b')
486    E-   FigureTuple(aboveBass=3, alter=0.0, prefix='')
487    E    FigureTuple(aboveBass=3, alter=1.0, prefix='#')
488    E#   FigureTuple(aboveBass=3, alter=2.0, prefix='##')
489    A--  FigureTuple(aboveBass=6, alter=-1.0, prefix='b')
490    A-   FigureTuple(aboveBass=6, alter=0.0, prefix='')
491    A    FigureTuple(aboveBass=6, alter=1.0, prefix='#')
492    A#   FigureTuple(aboveBass=6, alter=2.0, prefix='##')
493    B--  FigureTuple(aboveBass=7, alter=-1.0, prefix='b')
494    B-   FigureTuple(aboveBass=7, alter=0.0, prefix='')
495    B    FigureTuple(aboveBass=7, alter=1.0, prefix='#')
496    B#   FigureTuple(aboveBass=7, alter=2.0, prefix='##')
497
498    Returns a namedtuple called a FigureTuple.
499    '''
500    unused_scaleStep, scaleAccidental = keyObj.getScaleDegreeAndAccidentalFromPitch(pitchObj)
501
502    thisInterval = interval.notesToInterval(bass, pitchObj)
503    aboveBass = thisInterval.diatonic.generic.mod7
504    if scaleAccidental is None:
505        rootAlterationString = ''
506        alterDiff = 0.0
507    else:
508        alterDiff = scaleAccidental.alter
509        alter = int(alterDiff)
510        if alter < 0:
511            rootAlterationString = 'b' * (-1 * alter)
512        elif alter > 0:
513            rootAlterationString = '#' * alter
514        else:
515            rootAlterationString = ''
516
517    appendTuple = FigureTuple(aboveBass, alterDiff, rootAlterationString)
518    return appendTuple
519
520
521def identifyAsTonicOrDominant(
522    inChord: Union[list, tuple, chord.Chord],
523    inKey: key.Key
524) -> Union[str, bool]:
525    '''
526    Returns the roman numeral string expression (either tonic or dominant) that
527    best matches the inChord. Useful when you know inChord is either tonic or
528    dominant, but only two pitches are provided in the chord. If neither tonic
529    nor dominant is possibly correct, False is returned
530
531    >>> from music21 import roman
532    >>> roman.identifyAsTonicOrDominant(['B2', 'F5'], key.Key('C'))
533    'V65'
534
535    >>> roman.identifyAsTonicOrDominant(['B3', 'G4'], key.Key('g'))
536    'i6'
537
538    >>> roman.identifyAsTonicOrDominant(['C3', 'B-4'], key.Key('f'))
539    'V7'
540
541    Notice that this -- with B-natural is also identified as V7 because
542    it is returning the roman numeral root and the inversion name, not yet
543    checking for correctness.
544
545    >>> roman.identifyAsTonicOrDominant(['C3', 'B4'], key.Key('f'))
546    'V7'
547
548    >>> roman.identifyAsTonicOrDominant(['D3'], key.Key('f'))
549    False
550    '''
551    if isinstance(inChord, (list, tuple)):
552        inChord = chord.Chord(inChord)
553    elif not isinstance(inChord, chord.Chord):
554        raise ValueError('inChord must be a Chord or a list of strings')  # pragma: no cover
555
556    pitchNameList = []
557    for x in inChord.pitches:
558        pitchNameList.append(x.name)
559    oneRoot = inKey.pitchFromDegree(1)
560    fiveRoot = inKey.pitchFromDegree(5)
561    oneChordIdentified = False
562    fiveChordIdentified = False
563    if oneRoot.name in pitchNameList:
564        oneChordIdentified = True
565    elif fiveRoot.name in pitchNameList:
566        fiveChordIdentified = True
567    else:
568        oneRomanChord = RomanNumeral('I7', inKey).pitches
569        fiveRomanChord = RomanNumeral('V7', inKey).pitches
570
571        onePitchNameList = []
572        for x in oneRomanChord:
573            onePitchNameList.append(x.name)
574
575        fivePitchNameList = []
576        for x in fiveRomanChord:
577            fivePitchNameList.append(x.name)
578
579        oneMatches = len(set(onePitchNameList) & set(pitchNameList))
580        fiveMatches = len(set(fivePitchNameList) & set(pitchNameList))
581        if oneMatches > fiveMatches:
582            oneChordIdentified = True
583        elif oneMatches < fiveMatches:
584            fiveChordIdentified = True
585        else:  # both oneMatches and fiveMatches == 0
586            return False
587
588    if oneChordIdentified:
589        rootScaleDeg = common.toRoman(1)
590        if inKey.mode == 'minor':
591            rootScaleDeg = rootScaleDeg.lower()
592        else:
593            rootScaleDeg = rootScaleDeg.upper()
594        inChord.root(oneRoot)
595    elif fiveChordIdentified:
596        rootScaleDeg = common.toRoman(5)
597        inChord.root(fiveRoot)
598    else:
599        return False
600
601    return rootScaleDeg + romanInversionName(inChord)
602
603
604def romanInversionName(inChord, inv=None):
605    '''
606    Extremely similar to Chord's inversionName() method, but returns string
607    values and allows incomplete triads.
608    '''
609    if inv is None:
610        inv = inChord.inversion()
611
612    if inChord.isSeventh() or inChord.seventh is not None:
613        if inv == 0:
614            return '7'
615        elif inv == 1:
616            return '65'
617        elif inv == 2:
618            return '43'
619        elif inv == 3:
620            return '42'
621        else:
622            return ''
623    elif (inChord.isTriad()
624            or inChord.isIncompleteMajorTriad()
625            or inChord.isIncompleteMinorTriad()):
626        if inv == 0:
627            return ''  # not 53
628        elif inv == 1:
629            return '6'
630        elif inv == 2:
631            return '64'
632        else:
633            return ''
634    else:
635        return ''
636
637
638def correctRNAlterationForMinor(figureTuple, keyObj):
639    '''
640    Takes in a FigureTuple and a Key object and returns the same or a
641    new FigureTuple correcting for the fact that, for instance, Ab in c minor
642    is VI not vi.  Works properly only if the note is the root of the chord.
643
644    Used in RomanNumeralFromChord
645
646    These return new FigureTuple objects
647
648    >>> ft5 = roman.FigureTuple(aboveBass=6, alter=-1, prefix='')
649    >>> ft5a = roman.correctRNAlterationForMinor(ft5, key.Key('c'))
650    >>> ft5a
651    FigureTuple(aboveBass=6, alter=-1, prefix='b')
652    >>> ft5a is ft5
653    False
654
655    >>> ft6 = roman.FigureTuple(aboveBass=6, alter=0, prefix='')
656    >>> roman.correctRNAlterationForMinor(ft6, key.Key('c'))
657    FigureTuple(aboveBass=6, alter=0, prefix='b')
658
659    >>> ft7 = roman.FigureTuple(aboveBass=7, alter=1, prefix='#')
660    >>> roman.correctRNAlterationForMinor(ft7, key.Key('c'))
661    FigureTuple(aboveBass=7, alter=0, prefix='')
662
663
664
665
666    >>> ft1 = roman.FigureTuple(aboveBass=6, alter=-1, prefix='b')
667
668    Does nothing for major:
669
670    >>> ft2 = roman.correctRNAlterationForMinor(ft1, key.Key('C'))
671    >>> ft2
672    FigureTuple(aboveBass=6, alter=-1, prefix='b')
673    >>> ft1 is ft2
674    True
675
676    Does nothing for steps other than 6 or 7:
677
678    >>> ft3 = roman.FigureTuple(aboveBass=4, alter=-1, prefix='b')
679    >>> ft4 = roman.correctRNAlterationForMinor(ft3, key.Key('c'))
680    >>> ft4
681    FigureTuple(aboveBass=4, alter=-1, prefix='b')
682    >>> ft3 is ft4
683    True
684    '''
685    if keyObj.mode != 'minor':
686        return figureTuple
687    if figureTuple.aboveBass not in (6, 7):
688        return figureTuple
689
690    alter = figureTuple.alter
691    rootAlterationString = figureTuple.prefix
692
693    if alter == 1.0:
694        alter = 0
695        rootAlterationString = ''
696    elif alter == 0.0:
697        alter = 0  # NB! does not change!
698        rootAlterationString = 'b'
699    # more exotic:
700    elif alter > 1.0:
701        alter = alter - 1
702        rootAlterationString = rootAlterationString[1:]
703    elif alter < 0.0:
704        rootAlterationString = 'b' + rootAlterationString
705
706    return FigureTuple(figureTuple.aboveBass, alter, rootAlterationString)
707
708
709def romanNumeralFromChord(
710    chordObj,
711    keyObj: Union[key.Key, str] = None,
712    preferSecondaryDominants=False
713):
714    # noinspection PyShadowingNames
715    '''
716    Takes a chord object and returns an appropriate chord name.  If keyObj is
717    omitted, the root of the chord is considered the key (if the chord has a
718    major third, it's major; otherwise it's minor).  preferSecondaryDominants does not currently
719    do anything.
720
721    >>> rn = roman.romanNumeralFromChord(
722    ...     chord.Chord(['E-3', 'C4', 'G-6']),
723    ...     key.Key('g#'),
724    ...     )
725    >>> rn
726    <music21.roman.RomanNumeral bivo6 in g# minor>
727
728    The pitches remain the same with the same octaves:
729
730    >>> for p in rn.pitches:
731    ...     p
732    <music21.pitch.Pitch E-3>
733    <music21.pitch.Pitch C4>
734    <music21.pitch.Pitch G-6>
735
736    >>> romanNumeral2 = roman.romanNumeralFromChord(
737    ...     chord.Chord(['E3', 'C4', 'G4', 'B-4', 'E5', 'G5']),
738    ...     key.Key('F'),
739    ...     )
740    >>> romanNumeral2
741    <music21.roman.RomanNumeral V65 in F major>
742
743    Note that vi and vii in minor signifies what you might think of
744    alternatively as #vi and #vii:
745
746    >>> romanNumeral3 = roman.romanNumeralFromChord(
747    ...     chord.Chord(['A4', 'C5', 'E-5']),
748    ...     key.Key('c'),
749    ...     )
750    >>> romanNumeral3
751    <music21.roman.RomanNumeral vio in c minor>
752
753    >>> romanNumeral4 = roman.romanNumeralFromChord(
754    ...     chord.Chord(['A-4', 'C5', 'E-5']),
755    ...     key.Key('c'),
756    ...     )
757    >>> romanNumeral4
758    <music21.roman.RomanNumeral bVI in c minor>
759
760    >>> romanNumeral5 = roman.romanNumeralFromChord(
761    ...     chord.Chord(['B4', 'D5', 'F5']),
762    ...     key.Key('c'),
763    ...     )
764    >>> romanNumeral5
765    <music21.roman.RomanNumeral viio in c minor>
766
767    >>> romanNumeral6 = roman.romanNumeralFromChord(
768    ...     chord.Chord(['B-4', 'D5', 'F5']),
769    ...     key.Key('c'),
770    ...     )
771    >>> romanNumeral6
772    <music21.roman.RomanNumeral bVII in c minor>
773
774    Diminished and half-diminished seventh chords can omit the third and still
775    be diminished: (n.b. we also demonstrate that chords can be created from a
776    string):
777
778    >>> romanNumeralDim7 = roman.romanNumeralFromChord(
779    ...     chord.Chord('A3 E-4 G-4'),
780    ...     key.Key('b-'),
781    ...     )
782    >>> romanNumeralDim7
783    <music21.roman.RomanNumeral viio7 in b- minor>
784
785    For reference, odder notes:
786
787    >>> romanNumeral7 = roman.romanNumeralFromChord(
788    ...     chord.Chord(['A--4', 'C-5', 'E--5']),
789    ...     key.Key('c'),
790    ...     )
791    >>> romanNumeral7
792    <music21.roman.RomanNumeral bbVI in c minor>
793
794    >>> romanNumeral8 = roman.romanNumeralFromChord(
795    ...     chord.Chord(['A#4', 'C#5', 'E#5']),
796    ...     key.Key('c'),
797    ...     )
798    >>> romanNumeral8
799    <music21.roman.RomanNumeral #vi in c minor>
800
801    >>> romanNumeral10 = roman.romanNumeralFromChord(
802    ...     chord.Chord(['F#3', 'A3', 'E4', 'C5']),
803    ...     key.Key('d'),
804    ...     )
805    >>> romanNumeral10
806    <music21.roman.RomanNumeral #iiiø7 in d minor>
807
808
809    Augmented 6ths without key context
810
811    >>> roman.romanNumeralFromChord(
812    ...     chord.Chord('E-4 G4 C#5'),
813    ...     )
814    <music21.roman.RomanNumeral It6 in g minor>
815
816    >>> roman.romanNumeralFromChord(
817    ...     chord.Chord('E-4 G4 B-4 C#5'),
818    ...     )
819    <music21.roman.RomanNumeral Ger65 in g minor>
820
821    >>> roman.romanNumeralFromChord(
822    ...     chord.Chord('E-4 G4 A4 C#5'),
823    ...     )
824    <music21.roman.RomanNumeral Fr43 in g minor>
825
826    >>> roman.romanNumeralFromChord(
827    ...     chord.Chord('E-4 G4 A#4 C#5'),
828    ...     )
829    <music21.roman.RomanNumeral Sw43 in g minor>
830
831
832
833    With correct key context:
834
835    >>> roman.romanNumeralFromChord(
836    ...     chord.Chord('E-4 G4 C#5'),
837    ...     key.Key('G')
838    ...     )
839    <music21.roman.RomanNumeral It6 in G major>
840
841    With incorrect key context does not find an augmented 6th chord:
842
843    >>> roman.romanNumeralFromChord(
844    ...     chord.Chord('E-4 G4 C#5'),
845    ...     key.Key('C')
846    ...     )
847    <music21.roman.RomanNumeral #io6b3 in C major>
848
849    Empty chords, including :class:`~music21.harmony.NoChord` objects, give empty RomanNumerals:
850
851    >>> roman.romanNumeralFromChord(harmony.NoChord())
852    <music21.roman.RomanNumeral>
853
854    Augmented 6th chords in other inversions do not currently find correct roman numerals
855
856
857
858    Changed in v7 -- i7 is given for a tonic or subdominant minor-seventh chord in major:
859
860    >>> roman.romanNumeralFromChord(
861    ...     chord.Chord('C4 E-4 G4 B-4'),
862    ...     key.Key('C'))
863    <music21.roman.RomanNumeral i7 in C major>
864
865    >>> roman.romanNumeralFromChord(
866    ...     chord.Chord('E-4 G4 B-4 C5'),
867    ...     key.Key('G'))
868    <music21.roman.RomanNumeral iv65 in G major>
869
870    minor-Major chords are written with a [#7] modifier afterwards:
871
872    >>> roman.romanNumeralFromChord(
873    ...     chord.Chord('C4 E-4 G4 B4'),
874    ...     key.Key('C'))
875    <music21.roman.RomanNumeral i7[#7] in C major>
876    >>> roman.romanNumeralFromChord(
877    ...     chord.Chord('E-4 G4 B4 C5'),
878    ...     key.Key('C'))
879    <music21.roman.RomanNumeral i65[#7] in C major>
880
881
882    Former bugs that are now fixed:
883
884    >>> romanNumeral11 = roman.romanNumeralFromChord(
885    ...     chord.Chord(['E4', 'G4', 'B4', 'D5']),
886    ...     key.Key('C'),
887    ...     )
888    >>> romanNumeral11
889    <music21.roman.RomanNumeral iii7 in C major>
890
891    >>> roman.romanNumeralFromChord(chord.Chord('A3 C4 E-4 G4'), key.Key('c'))
892    <music21.roman.RomanNumeral viø7 in c minor>
893
894    >>> roman.romanNumeralFromChord(chord.Chord('A3 C4 E-4 G4'), key.Key('B-'))
895    <music21.roman.RomanNumeral viiø7 in B- major>
896
897    >>> romanNumeral9 = roman.romanNumeralFromChord(
898    ...     chord.Chord(['C4', 'E5', 'G5', 'C#6']),
899    ...     key.Key('C'),
900    ...     )
901    >>> romanNumeral9
902    <music21.roman.RomanNumeral I#853 in C major>
903
904
905    Not an augmented 6th:
906
907    >>> roman.romanNumeralFromChord(
908    ...     chord.Chord('E4 G4 B-4 C#5')
909    ...     )
910    <music21.roman.RomanNumeral io6b5b3 in c# minor>
911
912
913    OMIT_FROM_DOCS
914
915
916    Note that this should be III+642 gives III+#642 (# before 6 is unnecessary)
917
918    # >>> roman.romanNumeralFromChord(chord.Chord('B3 D3 E-3 G3'), key.Key('c'))
919    # <music21.roman.RomanNumeral III+642 in c minor>
920
921
922    These two are debatable -- is the harmonic minor or the natural minor used as the basis?
923
924    # >>> roman.romanNumeralFromChord(chord.Chord('F4 A4 C5 E-5'), key.Key('c'))
925    # <music21.roman.RomanNumeral IVb753 in c minor>
926    # <music21.roman.RomanNumeral IV75#3 in c minor>
927
928    # >>> roman.romanNumeralFromChord(chord.Chord('F4 A4 C5 E5'), key.Key('c'))
929    # <music21.roman.RomanNumeral IV7 in c minor>
930    # <music21.roman.RomanNumeral IV#75#3 in c minor>
931    '''
932
933    # use these when we know the key...  don't we need to know the mode?
934    aug6subs = {
935        '#ivo6b3': 'It6',
936        '#ivob64': 'It64',  # minor only
937        '#ivobb64': 'It64',  # major only
938        '#ivob5b3': 'It53',  # minor only
939        '#ivob5bb3': 'It53',  # major only
940
941        'IIø#643': 'Fr43',
942        'IIø75#3': 'Fr7',  # in minor
943        'IIø7b5#3': 'Fr7',  # in major
944        'IIø6#42': 'Fr42',  # in minor
945        'IIøb6#42': 'Fr42',  # in major
946        'IIø65': 'Fr65',  # in minor seems wrong...
947        'IIø65b3': 'Fr65',  # in major
948
949        '#ii64b3': 'Sw43',
950        '#iiø7': 'Sw7',  # minor; is wrong
951        '#iib7bb53': 'Sw7',  # major
952        '#iib642': 'Sw42',  # minor
953        '#iibb642': 'Sw42',  # major
954        '#ii6b5b3': 'Sw65',  # minor
955        '#ii6b5bb3': 'Sw65',  # major
956
957        '#ivo6b5b3': 'Ger65',  # in minor
958        '#ivo6bb5b3': 'Ger65',  # in major
959        '#ivob64b3': 'Ger43',  # in minor
960        '#ivobb64bb3': 'Ger43',  # in major
961        '#ivob6b42': 'Ger42',  # in minor
962        '#ivob6bb42': 'Ger42',  # in major
963        '#ivø7': 'Ger7',  # in minor -- seems wrong
964        '#ivobb7b5bb3': 'Ger7',  # in major
965    }
966    aug6NoKeyObjectSubs = {
967        'io6b3': 'It6',
968        'iob64': 'It64',
969        'iob5b3': 'It53',
970
971        'Iø64b3': 'Fr43',
972        'Iøb7b53': 'Fr7',
973        'Iøb642': 'Fr42',
974        'Iø6b5b3': 'Fr65',
975
976        'i64b3': 'Sw43',
977        'ib7bb53': 'Sw7',
978        'ibb642': 'Sw42',
979        'i6b5bb3': 'Sw65',
980
981        'io6b5b3': 'Ger65',
982        # Ger7 = iø7 -- is wrong...
983        'iob64b3': 'Ger43',
984        'iob6b42': 'Ger42',
985    }
986    minorSeventhSubs = {
987        'b75b3': '7',
988        '6b5': '65',
989        'b64b3': '43',
990        '6b42': '42',
991    }
992    minorMajorSeventhSubs = {
993        '75b3': '7[#7]',  # major key root
994        '65': '65[#7]',  # major key 1st inversion
995        'b643': '43[#7]',  # major key second inversion
996        '6b42': '42[#7]',  # major key form of 3rd inversion mM7...
997        '#753': '#7',  # root position in minor key
998        '6#53': '65[#7]',  # minor key 1st inversion
999        '64#3': '43[#7]',  # minor key 2nd inversion
1000        '42': '42[#7]',  # minor key form of 3rd inversion mM7...
1001    }
1002
1003    noKeyGiven = (keyObj is None)
1004
1005    if not chordObj.pitches:
1006        return RomanNumeral()
1007
1008    # TODO: Make sure 9 works
1009    # stepAdjustments = {'minor' : {3: -1, 6: -1, 7: -1},
1010    #                   'diminished' : {3: -1, 5: -1, 6: -1, 7: -2},
1011    #                   'half-diminished': {3: -1, 5: -1, 6: -1, 7: -1},
1012    #                   'augmented': {5: 1},
1013    #                   }
1014    root = chordObj.root()
1015    thirdType = chordObj.semitonesFromChordStep(3)
1016    if thirdType == 4:
1017        isMajorThird = True
1018    else:
1019        isMajorThird = False
1020
1021
1022    if keyObj is None:
1023        if isMajorThird:
1024            rootKeyObj = _getKeyFromCache(root.name.upper())
1025        else:
1026            rootKeyObj = _getKeyFromCache(root.name.lower())
1027        keyObj = rootKeyObj
1028    elif isinstance(keyObj, str):
1029        keyObj = key.Key(keyObj)
1030
1031    ft = figureTupleSolo(root, keyObj, keyObj.tonic)  # a FigureTuple
1032    ft = correctRNAlterationForMinor(ft, keyObj)
1033
1034    if ft.alter == 0:
1035        tonicPitch = keyObj.tonic
1036    else:
1037        # Altered scale degrees, such as #V require a different hypothetical
1038        # tonic:
1039
1040        # not worth caching yet -- 150 microseconds; we're trying to lower milliseconds
1041        transposeInterval = interval.intervalFromGenericAndChromatic(
1042            interval.GenericInterval(1),
1043            interval.ChromaticInterval(ft.alter))
1044        tonicPitch = transposeInterval.transposePitch(keyObj.tonic)
1045
1046    if keyObj.mode == 'major':
1047        tonicPitchName = tonicPitch.name.upper()
1048    else:
1049        tonicPitchName = tonicPitch.name.lower()
1050
1051    alteredKeyObj = _getKeyFromCache(tonicPitchName)
1052
1053    stepRoman = common.toRoman(ft.aboveBass)
1054    if isMajorThird:
1055        pass
1056    elif not isMajorThird:
1057        stepRoman = stepRoman.lower()
1058    inversionString = postFigureFromChordAndKey(chordObj, alteredKeyObj)
1059
1060    rnString = ft.prefix + stepRoman + inversionString
1061
1062    if (not isMajorThird
1063            and inversionString in minorSeventhSubs
1064            # only do expensive call in case it might be possible...
1065            and chordObj.isSeventhOfType((0, 3, 7, 10))):
1066        rnString = ft.prefix + stepRoman + minorSeventhSubs[inversionString]
1067    elif (not isMajorThird
1068              and inversionString in minorMajorSeventhSubs
1069              and chordObj.isSeventhOfType((0, 3, 7, 11))):
1070        rnString = ft.prefix + stepRoman + minorMajorSeventhSubs[inversionString]
1071
1072    elif (not noKeyGiven
1073          and rnString in aug6subs
1074          and chordObj.isAugmentedSixth(permitAnyInversion=True)):
1075        rnString = aug6subs[rnString]
1076    elif (noKeyGiven
1077          and rnString in aug6NoKeyObjectSubs
1078          and chordObj.isAugmentedSixth(permitAnyInversion=True)):
1079        rnString = aug6NoKeyObjectSubs[rnString]
1080        nationalityStart = rnString[:2]  # nb: Ger = Ge
1081        if nationalityStart in ('It', 'Ge'):
1082            keyObj = _getKeyFromCache(chordObj.fifth.name.lower())
1083        elif nationalityStart in ('Fr', 'Sw'):
1084            keyObj = _getKeyFromCache(chordObj.seventh.name.lower())
1085
1086    try:
1087        rn = RomanNumeral(rnString, keyObj, updatePitches=False)
1088    except fbNotation.ModifierException as strerror:
1089        raise RomanNumeralException(
1090            'Could not parse {0} from chord {1} as an RN '
1091            'in key {2}: {3}'.format(rnString, chordObj, keyObj, strerror))  # pragma: no cover
1092
1093    # Is this linking them in an unsafe way?
1094    rn.pitches = chordObj.pitches
1095    return rn
1096
1097
1098class Minor67Default(enum.Enum):
1099    '''
1100    Enumeration that can be passed into :class:`~music21.roman.RomanNumeral`'s
1101    keyword arguments `sixthMinor` and `seventhMinor` to define how Roman numerals
1102    on the sixth and seventh scale degrees are parsed in minor.
1103
1104    Showing how `sixthMinor` affects the interpretation of `vi`:
1105
1106    >>> vi = lambda sixChord, quality: ' '.join(p.name for p in roman.RomanNumeral(
1107    ...                                   sixChord, 'c',
1108    ...                                   sixthMinor=quality).pitches)
1109    >>> vi('vi', roman.Minor67Default.QUALITY)
1110    'A C E'
1111    >>> vi('vi', roman.Minor67Default.FLAT)
1112    'A- C- E-'
1113    >>> vi('vi', roman.Minor67Default.SHARP)
1114    'A C E'
1115
1116    >>> vi('VI', roman.Minor67Default.QUALITY)
1117    'A- C E-'
1118    >>> vi('VI', roman.Minor67Default.FLAT)
1119    'A- C E-'
1120    >>> vi('VI', roman.Minor67Default.SHARP)
1121    'A C# E'
1122
1123    For FLAT assumes lowered ^6 no matter what, while SHARP assumes raised
1124    ^6 no matter what.  So #vi is needed in FLAT and bVI is needed in SHARP
1125
1126    >>> vi('#vi', roman.Minor67Default.FLAT)
1127    'A C E'
1128    >>> vi('bVI', roman.Minor67Default.SHARP)
1129    'A- C E-'
1130
1131
1132    CAUTIONARY ignores the `#` in #vi and the `b` in bVI:
1133
1134    >>> vi('#vi', roman.Minor67Default.CAUTIONARY)
1135    'A C E'
1136    >>> vi('vi', roman.Minor67Default.CAUTIONARY)
1137    'A C E'
1138    >>> vi('bVI', roman.Minor67Default.CAUTIONARY)
1139    'A- C E-'
1140    >>> vi('VI', roman.Minor67Default.CAUTIONARY)
1141    'A- C E-'
1142
1143    Whereas QUALITY is closer to what a computer would produce, since vi is already
1144    sharpened, #vi raises it even more.  And since VI is already flattened, bVI lowers
1145    it even further:
1146
1147    >>> vi('vi', roman.Minor67Default.QUALITY)
1148    'A C E'
1149    >>> vi('#vi', roman.Minor67Default.QUALITY)
1150    'A# C# E#'
1151    >>> vi('VI', roman.Minor67Default.QUALITY)
1152    'A- C E-'
1153    >>> vi('bVI', roman.Minor67Default.QUALITY)
1154    'A-- C- E--'
1155
1156    To get these odd chords with CAUTIONARY, add another sharp or flat.
1157
1158    >>> vi('##vi', roman.Minor67Default.CAUTIONARY)
1159    'A# C# E#'
1160    >>> vi('bbVI', roman.Minor67Default.CAUTIONARY)
1161    'A-- C- E--'
1162
1163
1164    For other odd chords that are contrary to the standard minor interpretation
1165    in the "wrong" direction, the interpretation is the same as `QUALITY`
1166
1167    a major triad on raised 6?
1168
1169    >>> vi('#VI', roman.Minor67Default.QUALITY)
1170    'A C# E'
1171    >>> vi('#VI', roman.Minor67Default.CAUTIONARY)
1172    'A C# E'
1173
1174    a minor triad on lowered 6?
1175
1176    >>> vi('bvi', roman.Minor67Default.QUALITY)
1177    'A- C- E-'
1178    >>> vi('bvi', roman.Minor67Default.CAUTIONARY)
1179    'A- C- E-'
1180    '''
1181    QUALITY = 1
1182    CAUTIONARY = 2
1183    SHARP = 3
1184    FLAT = 4
1185
1186
1187# -----------------------------------------------------------------------------
1188
1189
1190class RomanException(exceptions21.Music21Exception):
1191    pass
1192
1193
1194class RomanNumeralException(exceptions21.Music21Exception):
1195    pass
1196
1197
1198# -----------------------------------------------------------------------------
1199
1200
1201class RomanNumeral(harmony.Harmony):
1202    '''
1203    A RomanNumeral object is a specialized type of
1204    :class:`~music21.harmony.Harmony` object that stores the function and scale
1205    degree of a chord within a :class:`~music21.key.Key`.
1206
1207    If no Key is given then it exists as a theoretical, keyless RomanNumeral;
1208    e.g., V in any key. but when realized, keyless RomanNumerals are
1209    treated as if they are in C major).
1210
1211    >>> from music21 import roman
1212    >>> V = roman.RomanNumeral('V')  # could also use 5
1213    >>> V.quality
1214    'major'
1215
1216    >>> V.inversion()
1217    0
1218
1219    >>> V.forteClass
1220    '3-11B'
1221
1222    >>> V.scaleDegree
1223    5
1224
1225    Default key is C Major
1226
1227    >>> for p in V.pitches:
1228    ...     p
1229    <music21.pitch.Pitch G4>
1230    <music21.pitch.Pitch B4>
1231    <music21.pitch.Pitch D5>
1232
1233    >>> neapolitan = roman.RomanNumeral('N6', 'c#')  # could also use 'bII6'
1234    >>> neapolitan.key
1235    <music21.key.Key of c# minor>
1236
1237    >>> neapolitan.isMajorTriad()
1238    True
1239
1240    >>> neapolitan.scaleDegreeWithAlteration
1241    (2, <music21.pitch.Accidental flat>)
1242
1243    >>> for p in neapolitan.pitches:  # default octaves
1244    ...     p
1245    <music21.pitch.Pitch F#4>
1246    <music21.pitch.Pitch A4>
1247    <music21.pitch.Pitch D5>
1248
1249    >>> neapolitan2 = roman.RomanNumeral('bII6', 'g#')
1250    >>> [str(p) for p in neapolitan2.pitches]
1251    ['C#5', 'E5', 'A5']
1252
1253    >>> neapolitan2.scaleDegree
1254    2
1255
1256    Here's a dominant seventh chord in minor:
1257
1258    >>> em = key.Key('e')
1259    >>> dominantV = roman.RomanNumeral('V7', em)
1260    >>> [str(p) for p in dominantV.pitches]
1261    ['B4', 'D#5', 'F#5', 'A5']
1262
1263    >>> minorV = roman.RomanNumeral('V43', em, caseMatters=False)
1264    >>> [str(p) for p in minorV.pitches]
1265    ['F#4', 'A4', 'B4', 'D5']
1266
1267    (We will do this `str(p) for p in...` thing enough that let's make a helper function:
1268
1269    >>> def cp(rn_in):  # cp = chord pitches
1270    ...     return [str(p) for p in rn_in.pitches]
1271    >>> cp(minorV)
1272    ['F#4', 'A4', 'B4', 'D5']
1273
1274
1275    In minor -- VII and VI are assumed to refer to the flattened scale degree.
1276    vii, viio, viio7, viiø7 and vi, vio, vio7, viø7 refer to the sharpened scale
1277    degree.  To get a minor triad on lowered 6 for instance, you will need to use 'bvi'
1278    while to get a major triad on raised 6, use '#VI'.
1279
1280    The actual rule is that if the chord implies minor, diminished, or half-diminished,
1281    an implied "#" is read before the figure.  Anything else does not add the sharp.
1282    The lowered (natural minor) is the assumed basic chord.
1283
1284    >>> majorFlatSeven = roman.RomanNumeral('VII', em)
1285    >>> cp(majorFlatSeven)
1286    ['D5', 'F#5', 'A5']
1287
1288    >>> minorSharpSeven = roman.RomanNumeral('vii', em)
1289    >>> cp(minorSharpSeven)
1290    ['D#5', 'F#5', 'A#5']
1291
1292    >>> majorFlatSix = roman.RomanNumeral('VI', em)
1293    >>> cp(majorFlatSix)
1294    ['C5', 'E5', 'G5']
1295
1296    >>> minorSharpSix = roman.RomanNumeral('vi', em)
1297    >>> cp(minorSharpSix)
1298    ['C#5', 'E5', 'G#5']
1299
1300
1301    These rules can be changed by passing in a `sixthMinor` or `seventhMinor` parameter set to
1302    a member of :class:`music21.roman.Minor67Default`:
1303
1304    >>> majorSharpSeven = roman.RomanNumeral('VII', em, seventhMinor=roman.Minor67Default.SHARP)
1305    >>> cp(majorSharpSeven)
1306    ['D#5', 'F##5', 'A#5']
1307
1308    For instance, if you prefer a harmonic minor context where VI (or vi) always refers
1309    to the lowered 6 and viio (or VII) always refers to the raised 7, send along
1310    `sixthMinor=roman.Minor67Default.FLAT` and `seventhMinor=roman.Minor67Default.SHARP`
1311
1312    >>> dimHarmonicSeven = roman.RomanNumeral('viio', em, seventhMinor=roman.Minor67Default.SHARP)
1313    >>> cp(dimHarmonicSeven)
1314    ['D#5', 'F#5', 'A5']
1315
1316    >>> majHarmonicSeven = roman.RomanNumeral('bVII', em, seventhMinor=roman.Minor67Default.SHARP)
1317    >>> cp(majHarmonicSeven)
1318    ['D5', 'F#5', 'A5']
1319
1320
1321    >>> majHarmonicSix = roman.RomanNumeral('VI', em, sixthMinor=roman.Minor67Default.FLAT)
1322    >>> cp(majHarmonicSix)
1323    ['C5', 'E5', 'G5']
1324    >>> minHarmonicSix = roman.RomanNumeral('#vi', em, sixthMinor=roman.Minor67Default.FLAT)
1325    >>> cp(minHarmonicSix)
1326    ['C#5', 'E5', 'G#5']
1327
1328
1329    See the docs for :class:`~music21.roman.Minor67Default`
1330    for more information on configuring sixth and seventh interpretation in minor
1331    along with the useful `CAUTIONARY` setting where CAUTIONARY sharp and flat accidentals
1332    are allowed but not required.
1333
1334
1335    Either of these is the same way of getting a minor iii in a minor key:
1336
1337    >>> minoriii = roman.RomanNumeral('iii', em, caseMatters=True)
1338    >>> cp(minoriii)
1339    ['G4', 'B-4', 'D5']
1340
1341    >>> minoriiiB = roman.RomanNumeral('IIIb', em, caseMatters=False)
1342    >>> cp(minoriiiB)
1343    ['G4', 'B-4', 'D5']
1344
1345    `caseMatters=False` will prevent `sixthMinor` or `seventhMinor` from having effect.
1346    >>> vii = roman.RomanNumeral('viio', 'a', caseMatters=False,
1347    ...                           seventhMinor=roman.Minor67Default.QUALITY)
1348    >>> cp(vii)
1349    ['G5', 'B-5', 'D-6']
1350
1351    Can also take a scale object, here we build a first-inversion chord
1352    on the raised-three degree of D-flat major, that is, F#-major (late
1353    Schubert would be proud.)
1354
1355    >>> sharp3 = roman.RomanNumeral('#III6', scale.MajorScale('D-'))
1356    >>> sharp3.scaleDegreeWithAlteration
1357    (3, <music21.pitch.Accidental sharp>)
1358
1359    >>> cp(sharp3)
1360    ['A#4', 'C#5', 'F#5']
1361
1362    >>> sharp3.figure
1363    '#III6'
1364
1365    Figures can be changed and pitches will change.
1366
1367    >>> sharp3.figure = 'V'
1368    >>> cp(sharp3)
1369    ['A-4', 'C5', 'E-5']
1370
1371    >>> leadingToneSeventh = roman.RomanNumeral(
1372    ...     'viio', scale.MajorScale('F'))
1373    >>> cp(leadingToneSeventh)
1374    ['E5', 'G5', 'B-5']
1375
1376    A little modal mixture:
1377
1378    >>> lessObviousDiminished = roman.RomanNumeral(
1379    ...     'vio', scale.MajorScale('c'))
1380    >>> for p in lessObviousDiminished.pitches:
1381    ...     p
1382    <music21.pitch.Pitch A4>
1383    <music21.pitch.Pitch C5>
1384    <music21.pitch.Pitch E-5>
1385
1386    >>> diminished7th = roman.RomanNumeral(
1387    ...     'vio7', scale.MajorScale('c'))
1388    >>> for p in diminished7th.pitches:
1389    ...     p
1390    <music21.pitch.Pitch A4>
1391    <music21.pitch.Pitch C5>
1392    <music21.pitch.Pitch E-5>
1393    <music21.pitch.Pitch G-5>
1394
1395    >>> diminished7th1stInv = roman.RomanNumeral(
1396    ...     'vio65', scale.MajorScale('c'))
1397    >>> for p in diminished7th1stInv.pitches:
1398    ...     p
1399    <music21.pitch.Pitch C4>
1400    <music21.pitch.Pitch E-4>
1401    <music21.pitch.Pitch G-4>
1402    <music21.pitch.Pitch A4>
1403
1404    >>> halfDim7th2ndInv = roman.RomanNumeral(
1405    ...     'ivø43', scale.MajorScale('F'))
1406    >>> for p in halfDim7th2ndInv.pitches:
1407    ...     p
1408    <music21.pitch.Pitch F-4>
1409    <music21.pitch.Pitch A-4>
1410    <music21.pitch.Pitch B-4>
1411    <music21.pitch.Pitch D-5>
1412
1413    >>> alteredChordHalfDim3rdInv = roman.RomanNumeral(
1414    ...     'biiø42', scale.MajorScale('F'))
1415    >>> cp(alteredChordHalfDim3rdInv)
1416    ['F-4', 'G-4', 'B--4', 'D--5']
1417
1418    >>> alteredChordHalfDim3rdInv.intervalVector
1419    [0, 1, 2, 1, 1, 1]
1420
1421    >>> alteredChordHalfDim3rdInv.commonName
1422    'half-diminished seventh chord'
1423
1424    >>> alteredChordHalfDim3rdInv.romanNumeral
1425    'bii'
1426
1427    >>> alteredChordHalfDim3rdInv.romanNumeralAlone
1428    'ii'
1429
1430    Tones may be omitted by putting the number in a bracketed [noX] clause.
1431    These numbers refer to the note above the root, not above the bass:
1432
1433    >>> openFifth = roman.RomanNumeral('V[no3]', key.Key('F'))
1434    >>> openFifth.pitches
1435    (<music21.pitch.Pitch C5>, <music21.pitch.Pitch G5>)
1436    >>> openFifthInv = roman.RomanNumeral('V64[no3]', key.Key('F'))
1437    >>> openFifthInv.pitches
1438    (<music21.pitch.Pitch G4>, <music21.pitch.Pitch C5>)
1439
1440
1441    Some theoretical traditions express a viio7 as a V9 chord with omitted
1442    root. Music21 allows that:
1443
1444    >>> fiveOhNine = roman.RomanNumeral('V9[no1]', key.Key('g'))
1445    >>> cp(fiveOhNine)
1446    ['F#5', 'A5', 'C6', 'E-6']
1447
1448    Putting [no] or [add] should never change the root
1449
1450    >>> fiveOhNine.root()
1451    <music21.pitch.Pitch D5>
1452
1453    Tones may be added by putting a number (with an optional accidental) in
1454    a bracketed [addX] clause:
1455
1456    >>> susChord = roman.RomanNumeral('I[add4][no3]', key.Key('C'))
1457    >>> susChord.pitches
1458    (<music21.pitch.Pitch C4>, <music21.pitch.Pitch F4>, <music21.pitch.Pitch G4>)
1459    >>> susChord.root()
1460    <music21.pitch.Pitch C4>
1461
1462    Putting it all together:
1463
1464    >>> weirdChord = roman.RomanNumeral('V65[no5][add#6][b3]', key.Key('C'))
1465    >>> cp(weirdChord)
1466    ['B-4', 'E#5', 'F5', 'G5']
1467    >>> weirdChord.root()
1468    <music21.pitch.Pitch G5>
1469
1470    Other scales besides major and minor can be used.
1471    Just for kicks (no worries if this is goobley-gook):
1472
1473    >>> ots = scale.OctatonicScale('C2')
1474    >>> rn_I9 = roman.RomanNumeral('I9', ots, caseMatters=False)
1475    >>> cp(rn_I9)
1476    ['C2', 'E-2', 'G-2', 'A2', 'C3']
1477
1478    >>> romanNumeral2 = roman.RomanNumeral(
1479    ...     'V7#5b3', ots, caseMatters=False)
1480    >>> cp(romanNumeral2)
1481    ['G-2', 'A-2', 'C#3', 'E-3']
1482
1483    >>> rn_minor_64_secondary = roman.RomanNumeral('v64/V', key.Key('e'))
1484    >>> rn_minor_64_secondary
1485    <music21.roman.RomanNumeral v64/V in e minor>
1486
1487    >>> rn_minor_64_secondary.figure
1488    'v64/V'
1489
1490    >>> cp(rn_minor_64_secondary)
1491    ['C#5', 'F#5', 'A5']
1492
1493    >>> rn_minor_64_secondary.secondaryRomanNumeral
1494    <music21.roman.RomanNumeral V in e minor>
1495
1496    Dominant 7ths can be specified by putting d7 at end:
1497
1498    >>> r = roman.RomanNumeral('bVIId7', key.Key('B-'))
1499    >>> r.figure
1500    'bVIId7'
1501
1502    >>> cp(r)
1503    ['A-5', 'C6', 'E-6', 'G-6']
1504
1505    >>> r = roman.RomanNumeral('VId7')
1506    >>> r.figure
1507    'VId7'
1508
1509    >>> r.key = key.Key('B-')
1510    >>> cp(r)
1511    ['G5', 'B5', 'D6', 'F6']
1512
1513    >>> r2 = roman.RomanNumeral('V42/V7/vi', key.Key('C'))
1514    >>> cp(r2)
1515    ['A4', 'B4', 'D#5', 'F#5']
1516
1517    >>> r2.secondaryRomanNumeral
1518    <music21.roman.RomanNumeral V7/vi in C major>
1519
1520    >>> r2.secondaryRomanNumeral.secondaryRomanNumeral
1521    <music21.roman.RomanNumeral vi in C major>
1522
1523
1524    The I64 chord can also be specified as Cad64, which
1525    simply parses as I64:
1526
1527    >>> r = roman.RomanNumeral('Cad64', key.Key('C'))
1528    >>> r
1529    <music21.roman.RomanNumeral Cad64 in C major>
1530    >>> cp(r)
1531    ['G4', 'C5', 'E5']
1532
1533    >>> r = roman.RomanNumeral('Cad64', key.Key('c'))
1534    >>> r
1535    <music21.roman.RomanNumeral Cad64 in c minor>
1536    >>> cp(r)
1537    ['G4', 'C5', 'E-5']
1538
1539    Works also for secondary romans:
1540
1541    >>> r = roman.RomanNumeral('Cad64/V', key.Key('c'))
1542    >>> r
1543    <music21.roman.RomanNumeral Cad64/V in c minor>
1544    >>> cp(r)
1545    ['D5', 'G5', 'B5']
1546
1547
1548    In a major context, i7 and iv7 and their inversions are treated as minor-7th
1549    chords:
1550
1551    >>> r = roman.RomanNumeral('i7', 'C')
1552    >>> r
1553    <music21.roman.RomanNumeral i7 in C major>
1554    >>> cp(r)
1555    ['C4', 'E-4', 'G4', 'B-4']
1556
1557    >>> r = roman.RomanNumeral('iv42', 'C')
1558    >>> cp(r)
1559    ['E-4', 'F4', 'A-4', 'C5']
1560
1561    For a minor-Major 7th chord in major, write it as i[add7] or i7[#7] or another inversion:
1562
1563    >>> minorMajor = roman.RomanNumeral('i[add7]', 'C')
1564    >>> minorMajor
1565    <music21.roman.RomanNumeral i[add7] in C major>
1566    >>> cp(minorMajor)
1567    ['C4', 'E-4', 'G4', 'B4']
1568    >>> cp(roman.RomanNumeral('i7[#7]', 'C'))
1569    ['C4', 'E-4', 'G4', 'B4']
1570
1571    Note that this is not the same as i#7, which gives a rather unusual chord in major.
1572
1573    >>> cp(roman.RomanNumeral('i#7', 'C'))
1574    ['C4', 'E-4', 'G4', 'B#4']
1575
1576    In minor it's just fine, well, as fine:
1577
1578    >>> cp(roman.RomanNumeral('i#7', 'c'))
1579    ['C4', 'E-4', 'G4', 'B4']
1580
1581
1582    >>> cp(roman.RomanNumeral('i42[#7]', 'C'))
1583    ['B4', 'C5', 'E-5', 'G5']
1584
1585    As noted above, Minor-Major 7th chords in minor have a different form in root position:
1586
1587    >>> cp(roman.RomanNumeral('i#7', 'c'))
1588    ['C4', 'E-4', 'G4', 'B4']
1589
1590    (these are both the same)
1591
1592    >>> cp(roman.RomanNumeral('i#753', 'c'))
1593    ['C4', 'E-4', 'G4', 'B4']
1594    >>> cp(roman.RomanNumeral('i7[#7]', 'c'))
1595    ['C4', 'E-4', 'G4', 'B4']
1596
1597
1598    Other inversions are the same as with major keys:
1599
1600    >>> cp(roman.RomanNumeral('i65[#7]', 'c'))
1601    ['E-4', 'G4', 'B4', 'C5']
1602    >>> cp(roman.RomanNumeral('i43[#7]', 'c'))
1603    ['G4', 'B4', 'C5', 'E-5']
1604
1605
1606
1607    The RomanNumeral constructor accepts a keyword 'updatePitches' which is
1608    passed to harmony.Harmony. By default it
1609    is True, but can be set to False to initialize faster if pitches are not needed.
1610
1611    >>> r = roman.RomanNumeral('vio', em, updatePitches=False)
1612    >>> r.pitches
1613    ()
1614
1615    Equality:
1616
1617    Two RomanNumerals compare equal if their `NotRest` components
1618    (noteheads, beams, expressions, articulations, etc.) are equal
1619    and if their figures and keys are equal:
1620
1621    >>> c1 = chord.Chord('C4 E4 G4 C5')
1622    >>> c2 = chord.Chord('C3 E4 G4')
1623    >>> rn1 = roman.romanNumeralFromChord(c1, 'C')
1624    >>> rn2 = roman.romanNumeralFromChord(c2, 'C')
1625    >>> rn1 == rn2
1626    True
1627    >>> rn1.duration.type = 'half'
1628    >>> rn1 == rn2
1629    False
1630    >>> rn3 = roman.RomanNumeral('I', 'd')
1631    >>> rn2 == rn3
1632    False
1633    >>> rn3.key = key.Key('C')
1634    >>> rn2 == rn3
1635    True
1636    >>> rn4 = roman.RomanNumeral('ii', 'C')
1637    >>> rn2 == rn4
1638    False
1639    >>> rn4.figure = 'I'
1640    >>> rn2 == rn4
1641    True
1642
1643    Changed in v6.5 -- caseMatters is keyword only. It along with sixthMinor and
1644    seventhMinor are now the only allowable keywords to pass in.
1645
1646    Changed in v7 -- RomanNumeral.romanNumeral will always give a "b" for a flattened
1647    degree (i.e., '-II' becomes 'bII') as this is what people expect in looking at
1648    the figure.
1649
1650
1651    OMIT_FROM_DOCS
1652
1653    Things that were giving us trouble:
1654
1655    >>> dminor = key.Key('d')
1656    >>> rn = roman.RomanNumeral('iiø65', dminor)
1657    >>> cp(rn)
1658    ['G4', 'B-4', 'D5', 'E5']
1659
1660    >>> rn.romanNumeral
1661    'ii'
1662
1663    >>> rn3 = roman.RomanNumeral('III', dminor)
1664    >>> cp(rn3)
1665    ['F4', 'A4', 'C5']
1666
1667    Should be the same as above no matter when the key is set:
1668
1669    >>> r = roman.RomanNumeral('VId7', key.Key('B-'))
1670    >>> cp(r)
1671    ['G5', 'B5', 'D6', 'F6']
1672
1673    >>> r.key = key.Key('B-')
1674    >>> cp(r)
1675    ['G5', 'B5', 'D6', 'F6']
1676
1677    This was getting B-flat.
1678
1679    >>> r = roman.RomanNumeral('VId7')
1680    >>> r.key = key.Key('B-')
1681    >>> cp(r)
1682    ['G5', 'B5', 'D6', 'F6']
1683
1684    >>> r = roman.RomanNumeral('vio', em)
1685    >>> cp(r)
1686    ['C#5', 'E5', 'G5']
1687
1688    We can omit an arbitrary number of steps:
1689
1690    >>> r = roman.RomanNumeral('Vd7[no3no5no7]', key.Key('C'))
1691    >>> cp(r)
1692    ['G4']
1693
1694    (NOTE: all this is omitted -- look at OMIT_FROM_DOCS above)
1695    '''
1696    # TODO: document better! what is inherited and what is new?
1697
1698    _alterationRegex = re.compile(r'^(b+|-+|#+)')
1699    _omittedStepsRegex = re.compile(r'(\[(no[1-9]+)+]\s*)+')
1700    _addedStepsRegex = re.compile(r'\[add(b*|-*|#*)(\d+)+]\s*')
1701    _bracketedAlterationRegex = re.compile(r'\[(b+|-+|#+)(\d+)]')
1702    _augmentedSixthRegex = re.compile(r'(It|Ger|Fr|Sw)\+?')
1703    _romanNumeralAloneRegex = re.compile(r'(IV|I{1,3}|VI{0,2}|iv|i{1,3}|vi{0,2}|N)')
1704    _secondarySlashRegex = re.compile(r'(.*?)/([#a-np-zA-NP-Z].*)')
1705    _aug6defaultInversions = {'It': '6', 'Fr': '43', 'Ger': '65', 'Sw': '43'}
1706    _slashedAug6Inv = re.compile(r'(\d)/(\d)')
1707
1708    _DOC_ATTR = {
1709        'addedSteps': '''
1710            Returns a list of the added steps, each as a tuple of
1711            modifier as a string (which might be empty) and a chord factor as an int.
1712
1713            >>> rn = roman.RomanNumeral('V7[addb6]', 'C')
1714            >>> rn.addedSteps
1715            [('-', 6)]
1716            >>> rn.pitches
1717            (<music21.pitch.Pitch G4>,
1718             <music21.pitch.Pitch B4>,
1719             <music21.pitch.Pitch D5>,
1720             <music21.pitch.Pitch E-5>,
1721             <music21.pitch.Pitch F5>)
1722
1723            You can add multiple added steps:
1724
1725            >>> strange = roman.RomanNumeral('V7[addb6][add#6][add-8]')
1726            >>> strange.addedSteps
1727            [('-', 6), ('#', 6), ('-', 8)]
1728            >>> ' '.join([p.nameWithOctave for p in strange.pitches])
1729            'G4 B4 D5 E-5 E#5 F5 G-5'
1730
1731            NOTE: The modifier name is currently changed from 'b' to '-', but
1732            this might change in a future version to match `bracketedAlteration`.
1733            ''',
1734        'bracketedAlterations': '''
1735            Returns a list of the bracketed alterations, each as a tuple of
1736            modifier as a string and a chord factor as an int.
1737
1738            >>> rn = roman.RomanNumeral('V7[b5]')
1739            >>> rn.bracketedAlterations
1740            [('b', 5)]
1741            >>> rn.pitches
1742            (<music21.pitch.Pitch G4>,
1743             <music21.pitch.Pitch B4>,
1744             <music21.pitch.Pitch D-5>,
1745             <music21.pitch.Pitch F5>)
1746
1747            NOTE: The bracketed alteration name is currently left as 'b', but
1748            this might change in a future version to match `addedSteps`.
1749
1750            The difference between a bracketed alteration and just
1751            putting b5 in is that, a bracketed alteration changes
1752            notes already present in a chord and does not imply that
1753            the normally present notes would be missing.  Here, the
1754            presence of 7 and b5 means that no 3rd should appear.
1755
1756            >>> rn2 = roman.RomanNumeral('V7b5')
1757            >>> rn2.bracketedAlterations
1758            []
1759            >>> len(rn2.pitches)
1760            3
1761            >>> [p.name for p in rn2.pitches]
1762            ['G', 'D-', 'F']
1763
1764            Changed in v6.5 -- always returns a list, even if it is empty.
1765            ''',
1766        'caseMatters': '''
1767            Boolean to determine whether the case (upper or lowercase) of the
1768            figure determines whether it is major or minor.  Defaults to True;
1769            not everything has been tested with False yet.
1770
1771            >>> roman.RomanNumeral('viiø7', 'd').caseMatters
1772            True
1773            ''',
1774        'figuresWritten': '''
1775            Returns a string containing any figured-bass figures as passed in:
1776
1777            >>> roman.RomanNumeral('V65').figuresWritten
1778            '65'
1779            >>> roman.RomanNumeral('V').figuresWritten
1780            ''
1781            >>> roman.RomanNumeral('Fr43', 'c').figuresWritten
1782            '43'
1783            >>> roman.RomanNumeral('I7#5b3').figuresWritten
1784            '7#5b3'
1785
1786            Note that the `o` and `ø` symbols are quality designations and not
1787            figures:
1788
1789            >>> roman.RomanNumeral('viio6').figuresWritten
1790            '6'
1791            >>> roman.RomanNumeral('viiø7').figuresWritten
1792            '7'
1793            ''',
1794        'figuresNotationObj': '''
1795            Returns a :class:`~music21.figuredBass.notation.Notation` object
1796            that represents the figures in a RomanNumeral
1797
1798            >>> rn = roman.RomanNumeral('V65')
1799            >>> notationObj = rn.figuresNotationObj
1800            >>> notationObj
1801            <music21.figuredBass.notation.Notation 6,5>
1802            >>> notationObj.numbers
1803            (6, 5, 3)
1804
1805            >>> rn = roman.RomanNumeral('Ib75#3')
1806            >>> notationObj = rn.figuresNotationObj
1807            >>> notationObj.numbers
1808            (7, 5, 3)
1809            >>> notationObj.modifiers
1810            (<music21.figuredBass.notation.Modifier b flat>,
1811             <music21.figuredBass.notation.Modifier None None>,
1812             <music21.figuredBass.notation.Modifier # sharp>)
1813            ''',
1814        'frontAlterationAccidental': '''
1815            An optional :class:`~music21.pitch.Accidental` object
1816            representing the chromatic alteration of a RomanNumeral, if any
1817
1818            >>> roman.RomanNumeral('bII43/vi', 'C').frontAlterationAccidental
1819            <music21.pitch.Accidental flat>
1820
1821            >>> roman.RomanNumeral('##IV').frontAlterationAccidental
1822            <music21.pitch.Accidental double-sharp>
1823
1824            For most roman numerals this will be None:
1825
1826            >>> roman.RomanNumeral('V', 'f#').frontAlterationAccidental
1827
1828            Changing this value will not change existing pitches.
1829
1830            Changed in v6.5 -- always returns a string, never None
1831            ''',
1832        'frontAlterationString': '''
1833            A string representing the chromatic alteration of a RomanNumeral, if any
1834
1835            >>> roman.RomanNumeral('bII43/vi', 'C').frontAlterationString
1836            'b'
1837            >>> roman.RomanNumeral('V', 'f#').frontAlterationString
1838            ''
1839
1840            Changing this value will not change existing pitches.
1841
1842            Changed in v6.5 -- always returns a string, never None
1843            ''',
1844        'frontAlterationTransposeInterval': '''
1845            An optional :class:`~music21.interval.Interval` object
1846            representing the transposition of a chromatically altered chord from
1847            the normal scale degree:
1848
1849            >>> sharpFour = roman.RomanNumeral('#IV', 'C')
1850            >>> sharpFour.frontAlterationTransposeInterval
1851            <music21.interval.Interval A1>
1852            >>> sharpFour.frontAlterationTransposeInterval.niceName
1853            'Augmented Unison'
1854
1855            Flats, as in this Neapolitan (bII6) chord, are given as diminished unisons:
1856
1857            >>> roman.RomanNumeral('N6', 'C').frontAlterationTransposeInterval
1858            <music21.interval.Interval d1>
1859
1860            Most RomanNumerals will have None and not a perfect unison for this value
1861            (this is for the speed of creating objects)
1862
1863            >>> intv = roman.RomanNumeral('V', 'e-').frontAlterationTransposeInterval
1864            >>> intv is None
1865            True
1866
1867            Changing this value will not change existing pitches.
1868            ''',
1869        'impliedQuality': '''
1870            The quality of the chord implied by the figure:
1871
1872            >>> roman.RomanNumeral('V', 'C').impliedQuality
1873            'major'
1874            >>> roman.RomanNumeral('ii65', 'C').impliedQuality
1875            'minor'
1876            >>> roman.RomanNumeral('viio7', 'C').impliedQuality
1877            'diminished'
1878
1879            The impliedQuality can differ from the actual quality
1880            if there are not enough notes to satisfy the implied quality,
1881            as in this half-diminished chord on vii which does not also
1882            have a seventh:
1883
1884            >>> incorrectSeventh = roman.RomanNumeral('vii/o', 'C')
1885            >>> incorrectSeventh.impliedQuality
1886            'half-diminished'
1887            >>> incorrectSeventh.quality
1888            'diminished'
1889
1890            >>> powerChordMinor = roman.RomanNumeral('v[no3]', 'C')
1891            >>> powerChordMinor.impliedQuality
1892            'minor'
1893            >>> powerChordMinor.quality
1894            'other'
1895
1896            If case does not matter then an empty quality is implied:
1897
1898            >>> roman.RomanNumeral('II', 'C', caseMatters=False).impliedQuality
1899            ''
1900
1901            ''',
1902        'impliedScale': '''
1903            If no key or scale is passed in as the second object, then
1904            impliedScale will be set to C major:
1905
1906            >>> roman.RomanNumeral('V').impliedScale
1907            <music21.scale.MajorScale C major>
1908
1909            Otherwise this will be empty:
1910
1911            >>> roman.RomanNumeral('V', key.Key('D')).impliedScale
1912            ''',
1913        'omittedSteps': '''
1914            A list of integers showing chord factors that have been
1915            specifically omitted:
1916
1917            >>> emptyNinth = roman.RomanNumeral('V9[no7][no5]', 'C')
1918            >>> emptyNinth.omittedSteps
1919            [7, 5]
1920            >>> emptyNinth.pitches
1921            (<music21.pitch.Pitch G4>,
1922             <music21.pitch.Pitch B4>,
1923             <music21.pitch.Pitch A5>)
1924
1925            Usually an empty list:
1926
1927            >>> roman.RomanNumeral('IV6').omittedSteps
1928            []
1929            ''',
1930        'pivotChord': '''
1931            Defaults to None; if not None, stores another interpretation of the
1932            same RN in a different key; stores a RomanNumeral object.
1933
1934            While not enforced, for consistency the pivotChord should be
1935            the new interpretation going forward (to the right on the staff)
1936
1937            >>> rn = roman.RomanNumeral('V7/IV', 'C')
1938            >>> rn.pivotChord is None
1939            True
1940            >>> rn.pivotChord = roman.RomanNumeral('V7', 'F')
1941            ''',
1942        'primaryFigure': '''
1943            A string representing everything before the slash
1944            in a RomanNumeral with applied chords.  In other roman numerals
1945            it is the same as `figure`:
1946
1947            >>> rn = roman.RomanNumeral('bII43/vi', 'C')
1948            >>> rn.primaryFigure
1949            'bII43'
1950
1951            >>> rnSimple = roman.RomanNumeral('V6', 'a')
1952            >>> rnSimple.primaryFigure
1953            'V6'
1954
1955            Changing this value will not change existing pitches.
1956            ''',
1957        'romanNumeralAlone': '''
1958            Returns a string of just the roman numeral part (I-VII or i-vii) of
1959            the figure:
1960
1961            >>> roman.RomanNumeral('V6').romanNumeralAlone
1962            'V'
1963
1964            Chromatic alterations and secondary numerals are omitted:
1965
1966            >>> rn = roman.RomanNumeral('#II7/vi', 'C')
1967            >>> rn.romanNumeralAlone
1968            'II'
1969
1970            Neapolitan chords are changed to 'II':
1971
1972            >>> roman.RomanNumeral('N6').romanNumeralAlone
1973            'II'
1974
1975            Currently augmented-sixth chords return the "national" base.  But this
1976            behavior may change in future versions:
1977
1978            >>> roman.RomanNumeral('It6').romanNumeralAlone
1979            'It'
1980            >>> roman.RomanNumeral('Ger65').romanNumeralAlone
1981            'Ger'
1982
1983            This will be controversial in some circles, but it's based on a root in
1984            isolation, and does not imply tonic quality:
1985
1986            >>> roman.RomanNumeral('Cad64').romanNumeralAlone
1987            'I'
1988            ''',
1989        'scaleCardinality': '''
1990            Stores how many notes are in the scale; defaults to 7 for diatonic, obviously.
1991
1992            >>> roman.RomanNumeral('IV', 'a').scaleCardinality
1993            7
1994
1995            Probably you should not need to change this.  And most code is untested
1996            with other cardinalities.  But it is (in theory) possible to create
1997            roman numerals on octatonic scales, etc.
1998
1999            Changing this value will not change existing pitches.
2000            ''',
2001        'scaleDegree': '''
2002            An int representing what degree of the scale the figure
2003            (or primary figure in the case of secondary/applied numerals)
2004            is on.  Discounts any front alterations:
2005
2006            >>> roman.RomanNumeral('vi', 'E').scaleDegree
2007            6
2008
2009            Note that this is 2, not 1.5 or 6 or 6.5 or something like that:
2010
2011            >>> roman.RomanNumeral('bII43/vi', 'C').scaleDegree
2012            2
2013
2014            Empty RomanNumeral objects have the special scaleDegree of 0:
2015
2016            >>> roman.RomanNumeral().scaleDegree
2017            0
2018
2019            Changing this value will not change existing pitches.
2020
2021            Changed in v6.5 -- empty RomanNumeral objects get scaleDegree 0, not None.
2022            ''',
2023        'secondaryRomanNumeral': '''
2024            An optional roman.RomanNumeral object that represents the part
2025            after the slash in a secondary/applied RomanNumeral object.  For instance,
2026            in the roman numeral, `C: V7/vi`, the `secondaryRomanNumeral` would be
2027            the roman numeral `C: vi`.  The key of the `secondaryRomanNumeral`
2028            is the key of the original RomanNumeral.  In cases such as
2029            V/V/V, the `secondaryRomanNumeral` can itself have a
2030            `secondaryRomanNumeral`.
2031
2032            >>> rn = roman.RomanNumeral('V7/vi', 'C')
2033            >>> rn.secondaryRomanNumeral
2034            <music21.roman.RomanNumeral vi in C major>
2035            ''',
2036        'secondaryRomanNumeralKey': '''
2037            An optional key.Key object for secondary/applied RomanNumeral that
2038            represents the key that the part of the figure *before* the slash
2039            will be interpreted in.  For instance in the roman numeral,
2040            `C: V7/vi`, the `secondaryRomanNumeralKey` would be `a minor`, since
2041            the vi (submediant) refers to an a-minor triad, and thus the `V7`
2042            part is to be read as the dominant seventh in `a minor`.
2043
2044            >>> rn = roman.RomanNumeral('V7/vi', 'C')
2045            >>> rn.secondaryRomanNumeralKey
2046            <music21.key.Key of a minor>
2047            ''',
2048        'seventhMinor': '''
2049            How should vii, viio,  and VII be parsed in minor?
2050            Defaults to Minor67Default.QUALITY.
2051
2052            This value should be passed into the constructor initially.
2053            Changing it after construction will not change the pitches.
2054            ''',
2055        'sixthMinor': '''
2056            How should vi, vio and VI be parsed in minor?
2057            Defaults to Minor67Default.QUALITY.
2058
2059            This value should be passed into the constructor initially.
2060            Changing it after construction will not change the pitches.
2061            ''',
2062        'useImpliedScale': '''
2063            A boolean indicating whether an implied scale is being used:
2064
2065            >>> roman.RomanNumeral('V').useImpliedScale
2066            True
2067            >>> roman.RomanNumeral('V', 'A').useImpliedScale
2068            False
2069            ''',
2070    }
2071
2072    # INITIALIZER #
2073
2074    def __init__(
2075        self,
2076        figure: Union[str, int] = '',
2077        keyOrScale=None,
2078        *,
2079        caseMatters=True,
2080        updatePitches=True,
2081        sixthMinor=Minor67Default.QUALITY,
2082        seventhMinor=Minor67Default.QUALITY,
2083    ):
2084        self.primaryFigure: str = ''
2085        self.secondaryRomanNumeral: Optional['RomanNumeral'] = None
2086        self.secondaryRomanNumeralKey: Optional['key.Key'] = None
2087
2088        self.pivotChord: Optional['RomanNumeral'] = None
2089        self.caseMatters: bool = caseMatters
2090        self.scaleCardinality: int = 7
2091
2092        if isinstance(figure, int):
2093            self.caseMatters = False
2094            figure = common.toRoman(figure)
2095
2096        # immediately fix low-preference figures
2097        if isinstance(figure, str):
2098            figure = figure.replace('0', 'o')  # viio7
2099
2100        if isinstance(figure, str):
2101            # /o is just a shorthand for ø -- so it should not be stored.
2102            figure = figure.replace('/o', 'ø')
2103
2104        # end immediate fixes
2105
2106
2107        # Store raw figure before calling setKeyOrScale:
2108        self._figure = figure
2109        # This is set when _setKeyOrScale() is called:
2110        self._scale = None
2111        self.scaleDegree: int = 0
2112        self.frontAlterationString: str = ''
2113        self.frontAlterationTransposeInterval: Optional[interval.Interval] = None
2114        self.frontAlterationAccidental: Optional[pitch.Accidental] = None
2115        self.romanNumeralAlone: str = ''
2116        self.figuresWritten: str = ''
2117        self.figuresNotationObj: fbNotation.Notation = _NOTATION_SINGLETON
2118        if not figure:
2119            self.figuresNotationObj = fbNotation.Notation()  # do not allow changing singleton
2120
2121        self.impliedQuality: str = ''
2122
2123        self.impliedScale: Optional[scale.Scale] = None
2124        self.useImpliedScale: bool = False
2125        self.bracketedAlterations: List[Tuple[str, int]] = []
2126        self.omittedSteps: List[int] = []
2127        self.addedSteps: List[Tuple[str, int]] = []
2128        # do not update pitches.
2129        self._parsingComplete = False
2130        self.key = keyOrScale
2131        self.sixthMinor = sixthMinor
2132        self.seventhMinor = seventhMinor
2133
2134        super().__init__(figure, updatePitches=updatePitches)
2135        self._parsingComplete = True
2136        self._functionalityScore = None
2137        self.editorial.followsKeyChange = False
2138
2139    # SPECIAL METHODS #
2140
2141    def _reprInternal(self):
2142        if hasattr(self.key, 'tonic'):
2143            return str(self.figureAndKey)
2144        else:
2145            return self.figure
2146
2147    def __eq__(self, other: 'RomanNumeral') -> bool:
2148        '''
2149        Compare equality, just based on NotRest and on figure and key
2150        '''
2151        if note.NotRest.__eq__(self, other) is NotImplemented:
2152            return NotImplemented
2153        if not note.NotRest.__eq__(self, other):
2154            return False
2155        if self.key != other.key:
2156            return False
2157        if self.figure != other.figure:
2158            return False
2159        return True
2160
2161    # PRIVATE METHODS #
2162    def _parseFigure(self):
2163        '''
2164        Parse the .figure object into its component parts.
2165
2166        Called from the superclass, Harmony.__init__()
2167        '''
2168        if not isinstance(self._figure, str):  # pragma: no cover
2169            raise RomanException(f'got a non-string figure: {self._figure!r}')
2170
2171        if not self.useImpliedScale:
2172            useScale = self._scale
2173        else:
2174            useScale = self.impliedScale
2175
2176        (workingFigure, useScale) = self._correctForSecondaryRomanNumeral(useScale)
2177
2178        if workingFigure == 'Cad64':
2179            # since useScale can be a scale, it might not have a mode
2180            if hasattr(useScale, 'mode') and useScale.mode == 'minor':
2181                workingFigure = 'i64'
2182            else:
2183                workingFigure = 'I64'
2184
2185        self.primaryFigure = workingFigure
2186
2187        workingFigure = self._parseOmittedSteps(workingFigure)
2188        workingFigure = self._parseAddedSteps(workingFigure)
2189        workingFigure = self._parseBracketedAlterations(workingFigure)
2190
2191        # Replace Neapolitan indication.
2192        workingFigure = re.sub('^N6', 'bII6', workingFigure)
2193        workingFigure = re.sub('^N', 'bII6', workingFigure)
2194
2195        workingFigure = self._parseFrontAlterations(workingFigure)
2196        workingFigure, useScale = self._parseRNAloneAmidstAug6(workingFigure, useScale)
2197        workingFigure = self._setImpliedQualityFromString(workingFigure)
2198        workingFigure = self._adjustMinorVIandVIIByQuality(workingFigure, useScale)
2199
2200        self.figuresWritten = workingFigure
2201        shFig = ','.join(expandShortHand(workingFigure))
2202        self.figuresNotationObj = fbNotation.Notation(shFig)
2203
2204    def _setImpliedQualityFromString(self, workingFigure):
2205        # major, minor, augmented, or diminished (and half-diminished for 7ths)
2206        impliedQuality = ''
2207        # impliedQualitySymbol = ''
2208        if workingFigure.startswith('o') or workingFigure.startswith('°'):
2209            workingFigure = workingFigure[1:]
2210            impliedQuality = 'diminished'
2211            # impliedQualitySymbol = 'o'
2212        elif workingFigure.startswith('/o'):
2213            workingFigure = workingFigure[2:]
2214            impliedQuality = 'half-diminished'
2215            # impliedQualitySymbol = 'ø'
2216        elif workingFigure.startswith('ø'):
2217            workingFigure = workingFigure[1:]
2218            impliedQuality = 'half-diminished'
2219            # impliedQualitySymbol = 'ø'
2220        elif workingFigure.startswith('+'):
2221            workingFigure = workingFigure[1:]
2222            impliedQuality = 'augmented'
2223            # impliedQualitySymbol = '+'
2224        elif workingFigure.endswith('d7'):
2225            # this one is different
2226            # # TODO(msc): what about d65, etc.?
2227            workingFigure = workingFigure[:-2] + '7'
2228            impliedQuality = 'dominant-seventh'
2229            # impliedQualitySymbol = '(dom7)'
2230        elif self.caseMatters and self.romanNumeralAlone.upper() == self.romanNumeralAlone:
2231            impliedQuality = 'major'
2232        elif self.caseMatters and self.romanNumeralAlone.lower() == self.romanNumeralAlone:
2233            impliedQuality = 'minor'
2234        self.impliedQuality = impliedQuality
2235        return workingFigure
2236
2237    def _correctBracketedPitches(self):
2238        # correct bracketed figures
2239        if not self.bracketedAlterations:
2240            return
2241        for (alterNotation, chordStep) in self.bracketedAlterations:
2242            alterNotation = re.sub('b', '-', alterNotation)
2243            try:
2244                alterPitch = self.getChordStep(chordStep)
2245            except chord.ChordException:
2246                continue  # can happen for instance in It6 with updatePitches=False
2247            if alterPitch is not None:
2248                newAccidental = pitch.Accidental(alterNotation)
2249                if alterPitch.accidental is None:
2250                    alterPitch.accidental = newAccidental
2251                else:
2252                    alterPitch.accidental.set(alterPitch.accidental.alter + newAccidental.alter)
2253
2254    def _findSemitoneSizeForQuality(self, impliedQuality):
2255        '''
2256        Given an implied quality, return the number of semitones that should be included.
2257
2258        Relies entirely on impliedQuality. Note that in the case of 'diminished'
2259        it could be either diminished triad or diminished seventh. We return for diminished
2260        seventh since a missing chordStep for the 7th degree doesn't affect the processing.
2261
2262        Returns a tuple of 2 or 3 length showing the number of
2263        semitones for third, fifth, [seventh]
2264        or the empty tuple () if not found.
2265
2266        >>> r = roman.RomanNumeral()
2267        >>> r._findSemitoneSizeForQuality('major')
2268        (4, 7)
2269        >>> r._findSemitoneSizeForQuality('minor')
2270        (3, 7)
2271        >>> r._findSemitoneSizeForQuality('half-diminished')
2272        (3, 6, 10)
2273        >>> r._findSemitoneSizeForQuality('augmented')
2274        (4, 8)
2275        >>> r._findSemitoneSizeForQuality('dominant-seventh')
2276        (4, 7, 10)
2277        >>> r._findSemitoneSizeForQuality('not-a-quality')
2278        ()
2279        >>> r._findSemitoneSizeForQuality('diminished')
2280        (3, 6, 9)
2281
2282        OMIT_FROM_DOCS
2283
2284        This one is not currently used.
2285
2286        >>> r._findSemitoneSizeForQuality('minor-seventh')
2287        (3, 7, 10)
2288        '''
2289        if impliedQuality == 'major':
2290            correctSemitones = (4, 7)
2291        elif impliedQuality == 'minor':
2292            correctSemitones = (3, 7)
2293        elif impliedQuality == 'diminished':
2294            correctSemitones = (3, 6, 9)
2295        elif impliedQuality == 'half-diminished':
2296            correctSemitones = (3, 6, 10)
2297        elif impliedQuality == 'augmented':
2298            correctSemitones = (4, 8)
2299        elif impliedQuality == 'minor-seventh':
2300            correctSemitones = (3, 7, 10)
2301        elif impliedQuality == 'dominant-seventh':
2302            correctSemitones = (4, 7, 10)
2303        else:
2304            correctSemitones = ()
2305
2306        return correctSemitones
2307
2308    def _matchAccidentalsToQuality(self, impliedQuality):
2309        '''
2310        Fixes notes that should be out of the scale
2311        based on what the chord "impliedQuality" (major, minor, augmented,
2312        diminished) by changing their accidental.
2313
2314        An intermediary step in parsing figures.
2315
2316        >>> r = roman.RomanNumeral()
2317        >>> r.pitches = ['C4', 'E4', 'G4']
2318        >>> r._matchAccidentalsToQuality('minor')
2319        >>> ' '.join([p.name for p in r.pitches])
2320        'C E- G'
2321        >>> r._matchAccidentalsToQuality('augmented')
2322        >>> ' '.join([p.name for p in r.pitches])
2323        'C E G#'
2324        >>> r._matchAccidentalsToQuality('diminished')
2325        >>> ' '.join([p.name for p in r.pitches])
2326        'C E- G-'
2327        >>> r.pitches = ['C4', 'E4', 'G4', 'B4']
2328        >>> r._matchAccidentalsToQuality('diminished')
2329        >>> ' '.join([p.name for p in r.pitches])
2330        'C E- G- B--'
2331
2332        This was a problem before:
2333
2334        >>> r.pitches = ['C4', 'E4', 'G4', 'B#4']
2335        >>> r._matchAccidentalsToQuality('diminished')
2336        >>> ' '.join([p.name for p in r.pitches])
2337        'C E- G- B--'
2338        '''
2339        def correctFaultyPitch(faultyPitch, inner_correctedSemis):
2340            if inner_correctedSemis >= 6:
2341                inner_correctedSemis = -1 * (12 - inner_correctedSemis)
2342            elif inner_correctedSemis <= -6:
2343                inner_correctedSemis += 12
2344
2345            if faultyPitch.accidental is None:
2346                faultyPitch.accidental = pitch.Accidental(inner_correctedSemis)
2347            else:
2348                acc = faultyPitch.accidental
2349                inner_correctedSemis += acc.alter
2350                if inner_correctedSemis >= 6:
2351                    inner_correctedSemis = -1 * (12 - inner_correctedSemis)
2352                elif inner_correctedSemis <= -6:
2353                    inner_correctedSemis += 12
2354
2355                acc.set(inner_correctedSemis)
2356
2357        def shouldSkipThisChordStep(chordStep) -> bool:
2358            '''
2359            Skip adjusting chordSteps with explicit accidentals.
2360
2361            For a figure like V7b5, make sure not to correct the b5 back,
2362            even though the implied quality requires a Perfect 5th.
2363            '''
2364            for figure in self.figuresNotationObj.figures:
2365                if (figure.number == chordStep
2366                        and figure.modifier.accidental is not None
2367                        and figure.modifier.accidental.alter != 0):
2368                    return True
2369            return False
2370
2371
2372        correctSemitones = self._findSemitoneSizeForQuality(impliedQuality)
2373        chordStepsToExamine = (3, 5, 7)
2374        # newPitches = []
2375
2376        for i in range(len(correctSemitones)):  # 3, 5, possibly 7
2377            thisChordStep = chordStepsToExamine[i]
2378            if shouldSkipThisChordStep(thisChordStep):
2379                continue
2380            thisCorrect = correctSemitones[i]
2381            thisSemis = self.semitonesFromChordStep(thisChordStep)
2382            if thisSemis is None:  # no chord step
2383                continue
2384            if thisSemis == thisCorrect:  # nothing to do
2385                continue
2386
2387            correctedSemis = thisCorrect - thisSemis
2388            correctFaultyPitch(self.getChordStep(thisChordStep), correctedSemis)
2389
2390        if len(correctSemitones) == 2 and len(self.figuresNotationObj.figures) >= 3:
2391            # special cases for chords whose 7th does not necessarily match the scale.
2392            if self.impliedQuality == 'minor' and self.semitonesFromChordStep(7) == 11:
2393                # i7 or iv7 chord or their inversions, in a major context.
2394                # check first that this isn't on purpose...
2395                if not shouldSkipThisChordStep(7):
2396                    correctFaultyPitch(self.seventh, -1)
2397
2398
2399    def _correctForSecondaryRomanNumeral(self, useScale, figure=None):
2400        '''
2401        Creates .secondaryRomanNumeral object and .secondaryRomanNumeralKey Key object
2402        inside the RomanNumeral object (recursively in case of V/V/V/V etc.) and returns
2403        the figure and scale that should be used instead of figure for further working.
2404
2405        Returns a tuple of (newFigure, newScale).
2406        In case there is no secondary slash, returns the original figure and the original scale.
2407
2408        If figure is None, uses newFigure.
2409
2410        >>> k = key.Key('C')
2411        >>> r = roman.RomanNumeral('I', k)  # will not be used below...
2412        >>> r._correctForSecondaryRomanNumeral(k)  # uses 'I'. nothing should change...
2413        ('I', <music21.key.Key of C major>)
2414        >>> r.secondaryRomanNumeral is None
2415        True
2416        >>> r.secondaryRomanNumeralKey is None
2417        True
2418        >>> r._correctForSecondaryRomanNumeral(k, 'V/V')
2419        ('V', <music21.key.Key of G major>)
2420        >>> r._correctForSecondaryRomanNumeral(k, 'V65/IV')
2421        ('V65', <music21.key.Key of F major>)
2422        >>> r._correctForSecondaryRomanNumeral(k, 'viio/bVII')
2423        ('viio', <music21.key.Key of B- major>)
2424
2425        >>> r._correctForSecondaryRomanNumeral(k, 'V9/vi')
2426        ('V9', <music21.key.Key of a minor>)
2427        >>> r.secondaryRomanNumeral
2428        <music21.roman.RomanNumeral vi in C major>
2429        >>> r.secondaryRomanNumeralKey
2430        <music21.key.Key of a minor>
2431
2432        Recursive...
2433
2434        >>> r._correctForSecondaryRomanNumeral(k, 'V7/V/V')
2435        ('V7', <music21.key.Key of D major>)
2436        >>> r.secondaryRomanNumeral
2437        <music21.roman.RomanNumeral V/V in C major>
2438        >>> r.secondaryRomanNumeralKey
2439        <music21.key.Key of D major>
2440        >>> r.secondaryRomanNumeral.secondaryRomanNumeral
2441        <music21.roman.RomanNumeral V in C major>
2442        >>> r.secondaryRomanNumeral.secondaryRomanNumeralKey
2443        <music21.key.Key of G major>
2444        '''
2445        if figure is None:
2446            figure = self._figure
2447        match = self._secondarySlashRegex.match(figure)
2448        if match:
2449            primaryFigure = match.group(1)
2450            secondaryFigure = match.group(2)
2451            secondaryRomanNumeral = RomanNumeral(
2452                secondaryFigure,
2453                useScale,
2454                caseMatters=self.caseMatters,
2455            )
2456            self.secondaryRomanNumeral = secondaryRomanNumeral
2457            if secondaryRomanNumeral.quality == 'minor':
2458                secondaryMode = 'minor'
2459            elif secondaryRomanNumeral.quality == 'major':
2460                secondaryMode = 'major'
2461            elif secondaryRomanNumeral.semitonesFromChordStep(3) == 3:
2462                secondaryMode = 'minor'
2463            else:
2464                secondaryMode = 'major'
2465
2466            # TODO: this should use a KeyCache...
2467            # but lower priority since secondaries are relatively rare
2468            self.secondaryRomanNumeralKey = key.Key(
2469                secondaryRomanNumeral.root().name,
2470                secondaryMode,
2471            )
2472            useScale = self.secondaryRomanNumeralKey
2473            workingFigure = primaryFigure
2474        else:
2475            workingFigure = figure
2476
2477        return (workingFigure, useScale)
2478
2479    def _parseOmittedSteps(self, workingFigure):
2480        '''
2481        Remove omitted steps from a working figure and return the remaining figure,
2482        setting self.omittedSteps to the omitted parts
2483
2484        >>> rn = roman.RomanNumeral()
2485        >>> rn._parseOmittedSteps('7[no5][no3]')
2486        '7'
2487        >>> rn.omittedSteps
2488        [5, 3]
2489
2490        All omitted are mod 7:
2491
2492        >>> rn = roman.RomanNumeral()
2493        >>> rn._parseOmittedSteps('13[no11][no9][no7]b3')
2494        '13b3'
2495        >>> rn.omittedSteps
2496        [4, 2, 7]
2497
2498        '''
2499        omittedSteps = []
2500        match = self._omittedStepsRegex.search(workingFigure)
2501        if match:
2502            group = match.group()
2503            group = group.replace(' ', '')
2504            group = group.replace('][', '')
2505            omittedSteps = [(int(x) % 7 or 7) for x in group[1:-1].split('no') if x]
2506            # environLocal.printDebug(self.figure + ' omitting: ' + str(omittedSteps))
2507            workingFigure = self._omittedStepsRegex.sub('', workingFigure)
2508        self.omittedSteps = omittedSteps
2509        return workingFigure
2510
2511    def _parseAddedSteps(self, workingFigure):
2512        '''
2513        Remove added steps from a working figure and return the remaining figure,
2514        setting self.addedSteps to a list of tuples of alteration and number
2515
2516        >>> rn = roman.RomanNumeral()
2517        >>> rn._parseAddedSteps('7[add6][add#2]')
2518        '7'
2519        >>> rn.addedSteps
2520        [('', 6), ('#', 2)]
2521
2522        All added are not mod 7.  Flat "b" becomes "-"
2523
2524        >>> rn = roman.RomanNumeral()
2525        >>> rn._parseAddedSteps('13[addbb11]b3')
2526        '13b3'
2527        >>> rn.addedSteps
2528        [('--', 11)]
2529        '''
2530        addedSteps = []
2531        matches = self._addedStepsRegex.finditer(workingFigure)
2532        for m in matches:
2533            matchAlteration = m.group(1).replace('b', '-')
2534            matchDegree = m.group(2)
2535            addTuple = (matchAlteration, int(matchDegree))
2536            addedSteps.append(addTuple)
2537            # environLocal.printDebug(self.figure + ' omitting: ' + str(omittedSteps))
2538        workingFigure = self._addedStepsRegex.sub('', workingFigure)
2539        self.addedSteps = addedSteps
2540        return workingFigure
2541
2542    def _parseBracketedAlterations(self, workingFigure):
2543        '''
2544        remove bracketed alterations from a figure and store them in `.bracketedAlterations`
2545
2546        >>> rn = roman.RomanNumeral()
2547        >>> rn._parseBracketedAlterations('7[#5][b3]')
2548        '7'
2549        >>> rn.bracketedAlterations
2550        [('#', 5), ('b', 3)]
2551
2552        '''
2553        matches = self._bracketedAlterationRegex.finditer(workingFigure)
2554        for m in matches:
2555            matchAlteration = m.group(1)
2556            matchDegree = int(m.group(2))
2557            newTuple = (matchAlteration, matchDegree)
2558            self.bracketedAlterations.append(newTuple)
2559        workingFigure = self._bracketedAlterationRegex.sub('', workingFigure)
2560        return workingFigure
2561
2562    def _parseFrontAlterations(self, workingFigure):
2563        '''
2564        removes front alterations from a workingFigure and sets
2565        `.frontAlterationString`, `.frontAlterationTransposeInterval`
2566        and `.frontAlterationAccidental`.
2567
2568        >>> rn = roman.RomanNumeral()
2569        >>> print(rn.frontAlterationTransposeInterval)
2570        None
2571
2572        >>> rn._parseFrontAlterations('bVI')
2573        'VI'
2574        >>> rn.frontAlterationString
2575        'b'
2576        >>> rn.frontAlterationTransposeInterval
2577        <music21.interval.Interval d1>
2578        >>> rn.frontAlterationAccidental
2579        <music21.pitch.Accidental flat>
2580        '''
2581        frontAlterationString = ''  # the b in bVI, or the '#' in #vii
2582        frontAlterationTransposeInterval = None
2583        frontAlterationAccidental = None
2584        match = self._alterationRegex.match(workingFigure)
2585        if match:
2586            group = match.group()
2587            alteration = len(group)
2588            if group[0] in ('b', '-'):
2589                alteration *= -1  # else sharp...
2590            frontAlterationTransposeInterval = interval.intervalFromGenericAndChromatic(
2591                interval.GenericInterval(1),
2592                interval.ChromaticInterval(alteration),
2593            )
2594            frontAlterationAccidental = pitch.Accidental(alteration)
2595            frontAlterationString = group
2596            workingFigure = self._alterationRegex.sub('', workingFigure)
2597        self.frontAlterationString = frontAlterationString
2598        self.frontAlterationTransposeInterval = frontAlterationTransposeInterval
2599        self.frontAlterationAccidental = frontAlterationAccidental
2600
2601        return workingFigure
2602
2603    def _parseRNAloneAmidstAug6(self, workingFigure, useScale):
2604        # noinspection PyShadowingNames
2605        '''
2606        Sets and removes from workingFigure the roman numeral alone, possibly
2607        changing the useScale in the case of augmented sixths.
2608
2609        Returns the remains of the figure alone with the scale to be used
2610
2611        >>> useScale = key.Key('C')
2612        >>> rn = roman.RomanNumeral()
2613        >>> workingFig, outScale = rn._parseRNAloneAmidstAug6('V7', useScale)
2614        >>> workingFig
2615        '7'
2616        >>> outScale is useScale
2617        True
2618        >>> rn.romanNumeralAlone
2619        'V'
2620
2621        >>> rn = roman.RomanNumeral()
2622        >>> workingFig, outScale = rn._parseRNAloneAmidstAug6('Ger65', useScale)
2623        >>> workingFig
2624        '65'
2625        >>> rn.scaleDegreeWithAlteration
2626        (4, <music21.pitch.Accidental sharp>)
2627
2628
2629        Working figures might be changed to defaults:
2630
2631        >>> rn = roman.RomanNumeral()
2632        >>> workingFig, outScale = rn._parseRNAloneAmidstAug6('Fr+6', useScale)
2633        >>> workingFig
2634        '43'
2635        >>> outScale
2636        <music21.key.Key of c minor>
2637        >>> rn.scaleDegree
2638        2
2639
2640        >>> rn = roman.RomanNumeral()
2641        >>> workingFig, outScale = rn._parseRNAloneAmidstAug6('It6', scale.MajorScale('C'))
2642        >>> outScale
2643        <music21.key.Key of c minor>
2644        '''
2645        romanNormalMatch = self._romanNumeralAloneRegex.match(workingFigure)
2646        aug6Match = self._augmentedSixthRegex.match(workingFigure)  # 250ns not worth short-circuit
2647
2648        if not romanNormalMatch and not aug6Match:
2649            raise RomanException(f'No roman numeral found in {workingFigure!r}')  # pragma: no cover
2650
2651        if aug6Match:
2652            # NB -- could be Key or Scale
2653            if ((isinstance(useScale, key.Key) and useScale.mode == 'major')
2654                    or ('DiatonicScale' in useScale.classes and useScale.type == 'major')):
2655                useScale = key.Key(useScale.tonic, 'minor')
2656                self.impliedScale = useScale
2657                self.useImpliedScale = True
2658
2659                # Set secondary key, if any, to minor
2660                if self.secondaryRomanNumeralKey is not None:
2661                    secondary_tonic = self.secondaryRomanNumeralKey.tonic
2662                    self.secondaryRomanNumeralKey = key.Key(secondary_tonic, 'minor')
2663
2664            # when Python 3.7 support is removed
2665            # aug6type: Literal['It', 'Ger', 'Fr', 'Sw'] = aug6Match.group(1)
2666            aug6type = aug6Match.group(1)
2667
2668            if aug6type in ('It', 'Ger'):
2669                self.scaleDegree = 4
2670                self.frontAlterationAccidental = pitch.Accidental('sharp')
2671            elif aug6type == 'Fr':
2672                self.scaleDegree = 2
2673            elif aug6type == 'Sw':
2674                self.scaleDegree = 2
2675                self.frontAlterationAccidental = pitch.Accidental('sharp')
2676
2677            workingFigure = self._augmentedSixthRegex.sub('', workingFigure)
2678            workingFigure = self._slashedAug6Inv.sub(r'\1\2', workingFigure)
2679
2680            if not workingFigure or not workingFigure[0].isdigit():
2681                # Ger was passed in instead of Ger65, etc.
2682                workingFigure = self._aug6defaultInversions[aug6type] + workingFigure
2683            elif (workingFigure
2684                  and aug6type != 'It'
2685                  and workingFigure[0] == '6'
2686                  and (len(workingFigure) < 2
2687                        or not workingFigure[1].isdigit())):
2688                # Fr6 => Fr43
2689                workingFigure = self._aug6defaultInversions[aug6type] + workingFigure[1:]
2690
2691            self.romanNumeralAlone = aug6type
2692            if aug6type != 'Fr':
2693                fixTuple = ('#', 1)
2694                self.bracketedAlterations.append(fixTuple)
2695            if aug6type in ('Fr', 'Sw'):
2696                fixTuple = ('#', 3)
2697                self.bracketedAlterations.append(fixTuple)
2698        else:
2699            romanNumeralAlone = romanNormalMatch.group(1)
2700            self.scaleDegree = common.fromRoman(romanNumeralAlone)
2701            workingFigure = self._romanNumeralAloneRegex.sub('', workingFigure)
2702            self.romanNumeralAlone = romanNumeralAlone
2703
2704        return workingFigure, useScale
2705
2706    def adjustMinorVIandVIIByQuality(self, useScale):
2707        '''
2708        Fix minor vi and vii to always be #vi and #vii if `.caseMatters`.
2709
2710        >>> rn = roman.RomanNumeral()
2711        >>> rn.scaleDegree = 6
2712        >>> rn.impliedQuality = 'minor'
2713        >>> rn.adjustMinorVIandVIIByQuality(key.Key('c'))
2714        >>> rn.frontAlterationTransposeInterval
2715        <music21.interval.Interval A1>
2716
2717        >>> rn.frontAlterationAccidental
2718        <music21.pitch.Accidental sharp>
2719
2720
2721        >>> rn = roman.RomanNumeral()
2722        >>> rn.scaleDegree = 6
2723        >>> rn.impliedQuality = 'major'
2724        >>> rn.adjustMinorVIandVIIByQuality(key.Key('c'))
2725        >>> rn.frontAlterationTransposeInterval is None
2726        True
2727        >>> rn.frontAlterationAccidental is None
2728        True
2729
2730        Changed in v.6.4: public function became hook to private function having the actual guts
2731        '''
2732        unused_workingFigure = self._adjustMinorVIandVIIByQuality('', useScale)
2733
2734    def _adjustMinorVIandVIIByQuality(self, workingFigure, useScale) -> str:
2735        '''
2736        Fix minor vi and vii to always be #vi and #vii if `.caseMatters`.
2737
2738        Made private in v.6.4 when `workingFigure` was added to the signature
2739        and returned.
2740
2741        Altering `workingFigure` became necessary to handle these chromatic figures:
2742        https://github.com/cuthbertLab/music21/issues/437
2743
2744        >>> rn = roman.RomanNumeral('viio#6', 'a')
2745        >>> ' '.join([p.name for p in rn.pitches])
2746        'B D G#'
2747        >>> rn = roman.RomanNumeral('viio6#4', 'a')
2748        >>> ' '.join([p.name for p in rn.pitches])
2749        'D G# B'
2750        >>> rn = roman.RomanNumeral('viio4#2', 'a')
2751        >>> ' '.join([p.name for p in rn.pitches])
2752        'F G# B D'
2753        >>> rn = roman.RomanNumeral('viio#853', 'a')
2754        >>> ' '.join([p.name for p in rn.pitches])
2755        'G# B D'
2756        >>> rn = roman.RomanNumeral('viio##853', 'a')
2757        >>> ' '.join([p.name for p in rn.pitches])
2758        'G# B D G##'
2759        '''
2760        def sharpen(wFig):
2761            changeFrontAlteration(interval.Interval('A1'), 1)
2762            # If root is in the figure, lower the root to avoid double-sharpening
2763            if '##' in wFig:
2764                wFig = wFig.replace('##8', '#8')
2765            elif '#2' in wFig:
2766                wFig = wFig.replace('#2', '2')
2767            elif '#4' in wFig:
2768                wFig = wFig.replace('#4', '4')
2769            elif '#6' in wFig:
2770                wFig = wFig.replace('#6', '6')
2771            else:
2772                wFig = wFig.replace('#8', '')
2773            return wFig
2774
2775        # def flatten():
2776        #    changeFrontAlteration(interval.Interval('-A1'), -1)
2777
2778        def changeFrontAlteration(intV, alter):
2779            # fati = front alteration transpose interval
2780            fati = self.frontAlterationTransposeInterval
2781            if fati:
2782                newFati = interval.add([fati, intV])
2783                self.frontAlterationTransposeInterval = newFati
2784                self.frontAlterationAccidental.alter = self.frontAlterationAccidental.alter + alter
2785                if self.frontAlterationAccidental.alter == 0:
2786                    self.frontAlterationTransposeInterval = None
2787                    self.frontAlterationAccidental = None
2788            else:
2789                self.frontAlterationTransposeInterval = intV
2790                self.frontAlterationAccidental = pitch.Accidental(alter)
2791
2792        # Make vii always #vii and vi always #vi.
2793        if getattr(useScale, 'mode', None) != 'minor':
2794            return workingFigure
2795        if self.scaleDegree not in (6, 7):
2796            return workingFigure
2797        if not self.caseMatters:
2798            return workingFigure
2799
2800        # THIS IS WHERE sixthMinor and seventhMinor goes...
2801        if self.scaleDegree == 6:
2802            minorDefault = self.sixthMinor
2803        else:
2804            minorDefault = self.seventhMinor
2805
2806        if minorDefault == Minor67Default.FLAT:
2807            # default of flat does not need anything.
2808            return workingFigure
2809
2810        normallyRaised = self.impliedQuality in ('minor', 'diminished', 'half-diminished')
2811
2812        if minorDefault == Minor67Default.SHARP:
2813            return sharpen(workingFigure)
2814        elif minorDefault == Minor67Default.QUALITY:
2815            if not normallyRaised:
2816                return workingFigure
2817            else:
2818                return sharpen(workingFigure)
2819        else:  # CAUTIONARY
2820            if not self.frontAlterationAccidental or self.frontAlterationAccidental.alter == 0:
2821                # same as QUALITY in this case
2822                if not normallyRaised:
2823                    return workingFigure
2824                else:
2825                    return sharpen(workingFigure)
2826
2827            # adjust accidentals for CAUTIONARY status
2828            frontAlter = self.frontAlterationAccidental.alter
2829            if frontAlter >= 1 and normallyRaised:
2830                # CAUTIONARY accidental that is needed for parsing.
2831                return workingFigure
2832            elif frontAlter <= -1:
2833                return sharpen(workingFigure)
2834            else:
2835                return workingFigure
2836
2837    def _updatePitches(self):
2838        '''
2839        Utility function to update the pitches to the new figure etc.
2840        '''
2841        if self.secondaryRomanNumeralKey is not None:
2842            useScale = self.secondaryRomanNumeralKey
2843        elif not self.useImpliedScale:
2844            useScale = self.key
2845        else:
2846            useScale = self.impliedScale
2847
2848        # should be 7 but hey, octatonic scales, etc.
2849        # self.scaleCardinality = len(useScale.pitches) - 1
2850        if 'DiatonicScale' in useScale.classes:  # speed up simple case
2851            self.scaleCardinality = 7
2852        else:
2853            self.scaleCardinality = useScale.getDegreeMaxUnique()
2854
2855        bassScaleDegree = self.bassScaleDegreeFromNotation(self.figuresNotationObj)
2856        bassPitch = useScale.pitchFromDegree(bassScaleDegree, direction=scale.DIRECTION_ASCENDING)
2857        pitches = [bassPitch]
2858        lastPitch = bassPitch
2859        numberNotes = len(self.figuresNotationObj.numbers)
2860
2861        for j in range(numberNotes):
2862            i = numberNotes - j - 1
2863            thisScaleDegree = (bassScaleDegree
2864                                + self.figuresNotationObj.numbers[i]
2865                                - 1)
2866            newPitch = useScale.pitchFromDegree(thisScaleDegree,
2867                                                direction=scale.DIRECTION_ASCENDING)
2868            pitchName = self.figuresNotationObj.modifiers[i].modifyPitchName(newPitch.name)
2869            newNewPitch = pitch.Pitch(pitchName)
2870            newNewPitch.octave = newPitch.octave
2871            if newNewPitch.ps < lastPitch.ps:
2872                newNewPitch.octave += 1
2873            pitches.append(newNewPitch)
2874            lastPitch = newNewPitch
2875
2876        if self.frontAlterationTransposeInterval:
2877            newPitches = []
2878            for thisPitch in pitches:
2879                newPitch = thisPitch.transpose(self.frontAlterationTransposeInterval)
2880                newPitches.append(newPitch)
2881            self.pitches = newPitches
2882        else:
2883            self.pitches = pitches
2884
2885        self._matchAccidentalsToQuality(self.impliedQuality)
2886
2887        # run this before omittedSteps and added steps so that
2888        # they don't change the sense of root.
2889        self._correctBracketedPitches()
2890        if self.omittedSteps or self.addedSteps:
2891            # set the root manually so that these alterations don't change the root.
2892            self.root(self.root())
2893
2894        if self.omittedSteps:
2895            omittedPitches = []
2896            for thisCS in self.omittedSteps:
2897                # getChordStep may return False
2898                p = self.getChordStep(thisCS)
2899                if p not in (False, None):
2900                    omittedPitches.append(p.name)
2901
2902            newPitches = []
2903            for thisPitch in self.pitches:
2904                if thisPitch.name not in omittedPitches:
2905                    newPitches.append(thisPitch)
2906            self.pitches = newPitches
2907
2908        if self.addedSteps:
2909            for addAccidental, stepNumber in self.addedSteps:
2910                if '-' in addAccidental:
2911                    alteration = addAccidental.count('-') * -1
2912                else:
2913                    alteration = addAccidental.count('#')
2914                thisScaleDegree = (self.scaleDegree + stepNumber - 1)
2915                addedPitch = useScale.pitchFromDegree(thisScaleDegree,
2916                                                      direction=scale.DIRECTION_ASCENDING)
2917                if addedPitch.accidental is not None:
2918                    addedPitch.accidental.alter += alteration
2919                else:
2920                    addedPitch.accidental = pitch.Accidental(alteration)
2921
2922                while addedPitch.ps < bassPitch.ps:
2923                    addedPitch.octave += 1
2924
2925                if addedPitch not in self.pitches:
2926                    self.add(addedPitch)
2927
2928        if not self.pitches:
2929            raise RomanNumeralException(
2930                f'_updatePitches() was unable to derive pitches from the figure: {self.figure!r}'
2931            )  # pragma: no cover
2932
2933
2934    # PUBLIC PROPERTIES #
2935
2936    @property
2937    def romanNumeral(self):
2938        '''
2939        Read-only property that returns either the romanNumeralAlone (e.g. just
2940        II) or the frontAlterationAccidental.modifier (with 'b' for '-') + romanNumeralAlone
2941        (e.g. #II, bII)
2942
2943        >>> from music21 import roman
2944        >>> rn = roman.RomanNumeral('#II7')
2945        >>> rn.romanNumeral
2946        '#II'
2947
2948        >>> rn = roman.RomanNumeral('Ger+6')
2949        >>> rn.romanNumeral
2950        'Ger'
2951
2952        >>> rn = roman.RomanNumeral('bbII/V')
2953        >>> rn.romanNumeral
2954        'bbII'
2955        >>> rn = roman.RomanNumeral('--II/V')
2956        >>> rn.romanNumeral
2957        'bbII'
2958        '''
2959        if self.romanNumeralAlone in ('Ger', 'Sw', 'It', 'Fr'):
2960            return self.romanNumeralAlone
2961        if self.frontAlterationAccidental is None:
2962            return self.romanNumeralAlone
2963
2964        return (self.frontAlterationAccidental.modifier.replace('-', 'b')
2965                + self.romanNumeralAlone)
2966
2967    @property
2968    def figure(self):
2969        '''
2970        Gets or sets the entire figure (the whole enchilada).
2971        '''
2972        return self._figure
2973
2974    @figure.setter
2975    def figure(self, newFigure):
2976        self._figure = newFigure
2977        if self._parsingComplete:
2978            self._parseFigure()
2979            self._updatePitches()
2980
2981    @property
2982    def figureAndKey(self):
2983        '''
2984        Returns the figure and the key and mode as a string
2985
2986        >>> from music21 import roman
2987        >>> rn = roman.RomanNumeral('V65/V', 'e')
2988        >>> rn.figureAndKey
2989        'V65/V in e minor'
2990
2991        Without a key, it is the same as figure:
2992
2993        >>> roman.RomanNumeral('V7').figureAndKey
2994        'V7'
2995        '''
2996        if self.key is None:
2997            return self.figure
2998
2999        mode = ''
3000        tonic = self.key.tonic
3001
3002        if hasattr(tonic, 'name'):
3003            tonic = tonic.name
3004        if hasattr(self.key, 'mode'):
3005            mode = ' ' + self.key.mode
3006        elif self.key.__class__.__name__ == 'MajorScale':
3007            mode = ' major'
3008        elif self.key.__class__.__name__ == 'MinorScale':
3009            mode = ' minor'
3010
3011        if mode == ' minor':
3012            tonic = tonic.lower()
3013        elif mode == ' major':
3014            tonic = tonic.upper()
3015        return f'{self.figure} in {tonic}{mode}'
3016
3017    @property
3018    def key(self):
3019        '''
3020        Gets or Sets the current Key (or Scale object) for a given
3021        RomanNumeral object.
3022
3023        If a new key is set, then the pitches will probably change:
3024
3025        >>> from music21 import roman
3026        >>> r1 = roman.RomanNumeral('V')
3027
3028        (No key means an implicit C-major)
3029
3030        >>> r1.key is None
3031        True
3032
3033        >>> [str(p) for p in r1.pitches]
3034        ['G4', 'B4', 'D5']
3035
3036        Change to A major
3037
3038        >>> r1.key = key.Key('A')
3039        >>> [str(p) for p in r1.pitches]
3040        ['E5', 'G#5', 'B5']
3041
3042        >>> r1
3043        <music21.roman.RomanNumeral V in A major>
3044
3045        >>> r1.key
3046        <music21.key.Key of A major>
3047
3048        >>> r1.key = key.Key('e')
3049        >>> [str(p) for p in r1.pitches]
3050        ['B4', 'D#5', 'F#5']
3051
3052        >>> r1
3053        <music21.roman.RomanNumeral V in e minor>
3054        '''
3055        return self._scale
3056
3057    @key.setter
3058    def key(self, keyOrScale):
3059        '''
3060        Provide a new key or scale, and re-configure the RN with the
3061        existing figure.
3062        '''
3063        if keyOrScale == self._scale and keyOrScale is not None:
3064            return  # skip...
3065
3066        # try to get Scale or Key object from cache: this will offer
3067        # performance boost as Scale stores cached pitch segments
3068        if isinstance(keyOrScale, str):
3069            keyOrScale = _getKeyFromCache(keyOrScale)
3070        elif keyOrScale is not None:
3071            # environLocal.printDebug(['got keyOrScale', keyOrScale])
3072            try:
3073                keyClasses = keyOrScale.classes
3074            except:  # pragma: no cover
3075                raise RomanNumeralException(
3076                    'Cannot call classes on object {0!r}, send only Key '
3077                    'or Scale Music21Objects'.format(keyOrScale))
3078            if 'Key' in keyClasses:
3079                # good to go...
3080                if keyOrScale.tonicPitchNameWithCase not in _keyCache:
3081                    # store for later
3082                    _keyCache[keyOrScale.tonicPitchNameWithCase] = keyOrScale
3083            elif 'Scale' in keyClasses:
3084                if keyOrScale.name in _scaleCache:
3085                    # use stored scale as already has cache
3086                    keyOrScale = _scaleCache[keyOrScale.name]
3087                else:
3088                    _scaleCache[keyOrScale.name] = keyOrScale
3089            else:
3090                raise RomanNumeralException(
3091                    f'Cannot get a key from this object {keyOrScale!r}, send only '
3092                    + 'Key or Scale objects')  # pragma: no cover
3093
3094        else:
3095            pass  # None
3096            # cache object if passed directly
3097        self._scale = keyOrScale
3098        if (keyOrScale is None
3099                or (hasattr(keyOrScale, 'isConcrete')
3100                    and not keyOrScale.isConcrete)):
3101            self.useImpliedScale = True
3102            if self._scale is not None:
3103                self.impliedScale = self._scale.derive(1, 'C')
3104            else:
3105                self.impliedScale = scale.MajorScale('C')
3106        else:
3107            self.useImpliedScale = False
3108        # need to permit object creation with no arguments, thus
3109        # self._figure can be None
3110        if self._parsingComplete:
3111            self._updatePitches()
3112            # environLocal.printDebug([
3113            #     'Roman.setKeyOrScale:',
3114            #     'called w/ scale', self.key,
3115            #     'figure', self.figure,
3116            #     'pitches', self.pitches,
3117            #     ])
3118
3119    @property
3120    def scaleDegreeWithAlteration(self):
3121        '''
3122        Returns a two element tuple of the scale degree and the
3123        accidental that alters the scale degree for things such as #ii or
3124        bV.
3125
3126        Note that vi and vii in minor have a frontAlterationAccidental of
3127        <sharp> even if it is not preceded by a `#` sign.
3128
3129        Has the same effect as setting .scaleDegree and
3130        .frontAlterationAccidental separately
3131
3132        >>> v = roman.RomanNumeral('V', 'C')
3133        >>> v.scaleDegreeWithAlteration
3134        (5, None)
3135
3136        >>> neapolitan = roman.RomanNumeral('N6', 'c#')
3137        >>> neapolitan.scaleDegreeWithAlteration
3138        (2, <music21.pitch.Accidental flat>)
3139        '''
3140        return self.scaleDegree, self.frontAlterationAccidental
3141
3142    def bassScaleDegreeFromNotation(self, notationObject=None):
3143        '''
3144        Given a notationObject from
3145        :class:`music21.figuredBass.notation.Notation`
3146        return the scaleDegree of the bass.
3147
3148        >>> from music21 import figuredBass, roman
3149        >>> fbn = figuredBass.notation.Notation('6,3')
3150        >>> V = roman.RomanNumeral('V')
3151        >>> V.bassScaleDegreeFromNotation(fbn)
3152        7
3153
3154        >>> fbn2 = figuredBass.notation.Notation('#6,4')
3155        >>> vi = roman.RomanNumeral('vi')
3156        >>> vi.bassScaleDegreeFromNotation(fbn2)
3157        3
3158
3159        Can figure it out directly from an existing RomanNumeral:
3160
3161        >>> ii65 = roman.RomanNumeral('ii65', 'C')
3162        >>> ii65.bassScaleDegreeFromNotation()
3163        4
3164
3165        Simple test:
3166
3167        >>> I = roman.RomanNumeral('I')
3168        >>> I.bassScaleDegreeFromNotation()
3169        1
3170
3171
3172        A bit slow (6 seconds for 1000 operations, but not the bottleneck)
3173        '''
3174        if notationObject is None:
3175            notationObject = self.figuresNotationObj
3176        c = pitch.Pitch('C3')
3177        cDNN = 22  # cDNN = c.diatonicNoteNum  # always 22
3178        pitches = [c]
3179        for i in notationObject.numbers:
3180            distanceToMove = i - 1
3181            newDiatonicNumber = (cDNN + distanceToMove)
3182
3183            newStep, newOctave = interval.convertDiatonicNumberToStep(
3184                newDiatonicNumber)
3185            newPitch = pitch.Pitch(f'{newStep}{newOctave}')
3186            pitches.append(newPitch)
3187
3188        tempChord = chord.Chord(pitches)
3189        rootDNN = tempChord.root().diatonicNoteNum
3190        staffDistanceFromBassToRoot = rootDNN - cDNN
3191        bassSD = ((self.scaleDegree - staffDistanceFromBassToRoot) %
3192                  self.scaleCardinality)
3193        if bassSD == 0:
3194            bassSD = 7
3195        return bassSD
3196
3197    @property
3198    def functionalityScore(self):
3199        '''
3200        Return or set a number from 1 to 100 representing the relative
3201        functionality of this RN.figure (possibly given the mode, etc.).
3202
3203        Numbers are ordinal, not cardinal.
3204
3205        >>> from music21 import roman
3206        >>> rn1 = roman.RomanNumeral('V7')
3207        >>> rn1.functionalityScore
3208        80
3209
3210        >>> rn2 = roman.RomanNumeral('vi6')
3211        >>> rn2.functionalityScore
3212        10
3213
3214        >>> rn2.functionalityScore = 99
3215        >>> rn2.functionalityScore
3216        99
3217
3218        For secondary dominants, the functionality scores are multiplied, reducing
3219        all but the first by 1/100th:
3220
3221        >>> rn3 = roman.RomanNumeral('V')
3222        >>> rn3.functionalityScore
3223        70
3224
3225        >>> rn4 = roman.RomanNumeral('vi')
3226        >>> rn4.functionalityScore
3227        40
3228
3229        >>> rn5 = roman.RomanNumeral('V/vi')
3230        >>> rn5.functionalityScore
3231        28
3232        '''
3233        if self._functionalityScore is not None:
3234            return self._functionalityScore
3235
3236        if self.secondaryRomanNumeral:
3237            figures = self.figure.split('/')  # error for half-diminished in secondary...
3238            score = 100
3239            for f in figures:
3240                try:
3241                    scorePart = functionalityScores[f] / 100
3242                except KeyError:
3243                    scorePart = 0
3244                score *= scorePart
3245            return int(score)
3246
3247        try:
3248            score = functionalityScores[self.figure]
3249        except KeyError:
3250            score = 0
3251        return score
3252
3253    @functionalityScore.setter
3254    def functionalityScore(self, value):
3255        self._functionalityScore = value
3256
3257    def isNeapolitan(self,
3258                     require1stInversion: bool = True):
3259        '''
3260        Music21's Chord class contains methods for identifying chords of a particular type,
3261        such as :meth:`~music21.chord.Chord.isAugmentedSixth`.
3262
3263        Some similar chord types are defined not only by the structure of a chord but
3264        by its relation to a key.
3265        The Neapolitan sixth is a notable example.
3266        A chord is a Neapolitan sixth if it is a major triad, in first inversion, and
3267        (here's the key-dependent part) rooted on the flattened second scale degree.
3268
3269        >>> chd = chord.Chord(['F4', 'Ab4', 'Db5'])
3270        >>> rn = roman.romanNumeralFromChord(chd, 'C')
3271        >>> rn.isNeapolitan()
3272        True
3273
3274        As this is key-dependent, changing the key changes the outcome.
3275
3276        >>> rn = roman.romanNumeralFromChord(chd, 'Db')
3277        >>> rn.isNeapolitan()
3278        False
3279
3280        The 'N6' shorthand is accepted.
3281
3282        >>> rn = roman.RomanNumeral('N6')
3283        >>> rn.isNeapolitan()
3284        True
3285
3286        Requiring first inversion is optional.
3287
3288        >>> rn = roman.RomanNumeral('bII')
3289        >>> rn.isNeapolitan(require1stInversion=False)
3290        True
3291        '''
3292        if self.scaleDegree != 2:
3293            return False
3294        if not self.frontAlterationAccidental:
3295            return False
3296        if self.frontAlterationAccidental.name != 'flat':
3297            return False
3298        if self.quality != 'major':
3299            return False
3300        if require1stInversion and self.inversion() != 1:
3301            return False
3302        return True
3303
3304    def isMixture(self,
3305                  evaluateSecondaryNumeral: bool = False):
3306        '''
3307        Checks if a RomanNumeral is an instance of 'modal mixture' in which the chord is
3308        not diatonic in the key specified, but
3309        would be would be in the parallel (German: variant) major / minor
3310        and can therefore be thought of as a 'mixture' of major and minor modes, or
3311        as a 'borrowing' from the one to the other.
3312
3313        Examples include i in major or I in minor (*sic*).
3314
3315        Specifically, this method returns True for all and only the following cases in any
3316        inversion:
3317
3318        Major context (example of C major):
3319
3320        * scale degree 1 and triad quality minor (minor tonic chord, c);
3321
3322        * scale degree 2 and triad quality diminished (covers both iio and iiø7);
3323
3324        * scale degree b3 and triad quality major (Eb);
3325
3326        * scale degree 4 and triad quality minor (f);
3327
3328        * scale degree 5 and triad quality minor (g, NB: potentially controversial);
3329
3330        * scale degree b6 and triad quality major (Ab);
3331
3332        * scale degree b7 and triad quality major (Bb); and
3333
3334        * scale degree 7 and it's a diminished seventh specifically (b-d-f-ab).
3335
3336        Minor context (example of c minor):
3337
3338        * scale degree 1 and triad quality major (major tonic chord, C);
3339
3340        * scale degree 2 and triad quality minor (d, not diminished);
3341
3342        * scale degree #3 and triad quality minor (e);
3343
3344        * scale degree 4 and triad quality major (F);
3345
3346        * scale degree #6 and triad quality minor (a); and
3347
3348        * scale degree 7 and it's a half diminished seventh specifically (b-d-f-a).
3349
3350        This list is broadly consistent with (and limited to) borrowing between the major and
3351        natural minor, except for excluding V (G-B-D) and viio (B-D-F) in minor.
3352        There are several borderline caes and this in-/exclusion is all open to debate, of course.
3353        The choices here reflect this method's primarily goal to aid anthologizing and
3354        pointing to clear cases of mixture in common practice Classical music.
3355        At least in that context, V and viio are not generally regarded as mixure.
3356
3357        By way of example usage, here are both major and minor versions of the
3358        tonic and subdominant triads in the major context.
3359
3360        >>> roman.RomanNumeral('I', 'D-').isMixture()
3361        False
3362
3363        >>> roman.RomanNumeral('i', 'D-').isMixture()
3364        True
3365
3366        >>> roman.RomanNumeral('IV', 'F').isMixture()
3367        False
3368
3369        >>> roman.RomanNumeral('iv', 'F').isMixture()
3370        True
3371
3372        For any cases extending beyond triad/seventh chords, major/minor keys,
3373        and the like, this method simply returns False.
3374
3375        So when the mode is not major or minor (including when it's undefined), that's False.
3376
3377        >>> rn = roman.RomanNumeral('iv', 'C')
3378        >>> rn.key.mode
3379        'major'
3380
3381        >>> rn.isMixture()
3382        True
3383
3384        >>> rn.key.mode = 'hypomixolydian'
3385        >>> rn.isMixture()
3386        False
3387
3388        A scale for a key never returns True for mixture.
3389
3390        >>> rn = roman.RomanNumeral('i', scale.MajorScale('D'))  # mode undefined
3391        >>> rn.isMixture()
3392        False
3393
3394        Likewise, anything that's not a triad or seventh will return False:
3395
3396        >>> rn = roman.romanNumeralFromChord(chord.Chord("C D E"))
3397        >>> rn.isMixture()
3398        False
3399
3400        Note that Augmented sixth chords do count as sevenths but never indicate modal mixture
3401        (not least because those augmented sixths are the same in both major and minor).
3402
3403        >>> rn = roman.RomanNumeral('Ger65')
3404        >>> rn.isSeventh()
3405        True
3406
3407        >>> rn.isMixture()
3408        False
3409
3410        False is also returned for any case in which the triad quality is not
3411        diminished, minor, or major:
3412
3413        >>> rn = roman.RomanNumeral('bIII+')
3414        >>> rn.quality
3415        'augmented'
3416
3417        >>> rn.isMixture()
3418        False
3419
3420        (That specific example of bIII+ in major is a borderline case that
3421        arguably ought to be included and may be added in future.)
3422
3423        Naturally, really extended usages such as scale degrees beyond 7 (in the
3424        Octatonic mode, for instance) also return False.
3425
3426        The evaluateSecondaryNumeral parameter allows users to chose whether to consider
3427        secondary Roman numerals (like V/vi) or to ignore them.
3428        When considered, exactly the same rules apply but recasting the comparison on
3429        the secondaryRomanNumeral.
3430        This is an extended usage that is open to debate and liable to change.
3431
3432        >>> roman.RomanNumeral('V/bVI', 'E-').isMixture()
3433        False
3434
3435        >>> roman.RomanNumeral('V/bVI', 'E-').isMixture(evaluateSecondaryNumeral=True)
3436        True
3437
3438        In case of secondary numeral chains, read the last one for mixture.
3439
3440        >>> roman.RomanNumeral('V/V/bVI', 'E-').isMixture(evaluateSecondaryNumeral=True)
3441        True
3442
3443        OMIT_FROM_DOCS
3444
3445        Test that the 'C' key has not had its mode permanently changed with the
3446        hypomixolydian change above:
3447
3448        >>> roman.RomanNumeral('iv', 'C').key.mode
3449        'major'
3450
3451        '''
3452
3453        if evaluateSecondaryNumeral and self.secondaryRomanNumeral:
3454            return self.secondaryRomanNumeral.isMixture(evaluateSecondaryNumeral=True)
3455
3456        if (not self.isTriad) and (not self.isSeventh):
3457            return False
3458
3459        if not self.key or not isinstance(self.key, key.Key):
3460            return False
3461
3462        mode = self.key.mode
3463        if mode not in ('major', 'minor'):
3464            return False
3465
3466        scaleDegree = self.scaleDegree
3467        if scaleDegree not in range(1, 8):
3468            return False
3469
3470        quality = self.quality
3471        if quality not in ('diminished', 'minor', 'major'):
3472            return False
3473
3474        if self.frontAlterationAccidental:
3475            frontAccidentalName = self.frontAlterationAccidental.name
3476        else:
3477            frontAccidentalName = 'natural'
3478
3479        majorKeyMixtures = {
3480            (1, 'minor', 'natural'),
3481            (2, 'diminished', 'natural'),
3482            (3, 'major', 'flat'),
3483            # (3, 'augmented', 'flat'),  # Potential candidate
3484            (4, 'minor', 'natural'),
3485            (5, 'minor', 'natural'),  # Potentially controversial
3486            (6, 'major', 'flat'),
3487            (7, 'major', 'flat'),  # Note diminished 7th handled separately
3488        }
3489
3490        minorKeyMixtures = {
3491            (1, 'major', 'natural'),
3492            (2, 'minor', 'natural'),
3493            (3, 'minor', 'sharp'),
3494            (4, 'major', 'natural'),
3495            # 5 N/A
3496            (6, 'minor', 'sharp'),
3497            # (6, 'diminished', 'sharp'),  # Potential candidate
3498            # 7 half-diminished handled separately
3499        }
3500
3501        if mode == 'major':
3502            if (scaleDegree, quality, frontAccidentalName) in majorKeyMixtures:
3503                return True
3504            elif (scaleDegree == 7) and (self.isDiminishedSeventh()):
3505                return True
3506        elif mode == 'minor':
3507            if (scaleDegree, quality, frontAccidentalName) in minorKeyMixtures:
3508                return True
3509            elif (scaleDegree == 7) and (self.isHalfDiminishedSeventh()):
3510                return True
3511
3512        return False
3513
3514
3515# Override the documentation for a property
3516RomanNumeral.figure.__doc__ = '''
3517    Gives a string representation of the roman numeral, which
3518    is usually the same as what you passed in as a string:
3519
3520    >>> roman.RomanNumeral('bVII65/V', 'C').figure
3521    'bVII65/V'
3522
3523    There are a few exceptions.  If the RomanNumeral is initialized
3524    with an int, then it is converted to a string:
3525
3526    >>> roman.RomanNumeral(2).figure
3527    'II'
3528
3529    A `0` used for `o` in a diminished seventh chord is converted to `o`,
3530    and the `/o` form of half-diminished is converted to `ø`:
3531
3532    >>> roman.RomanNumeral('vii07').figure
3533    'viio7'
3534    >>> roman.RomanNumeral('vii/o7').figure
3535    'viiø7'
3536
3537    Changing this value will not change existing pitches.
3538
3539    Changed in v6.5 -- empty RomanNumerals now have figure of '' not None
3540    '''
3541
3542
3543# -----------------------------------------------------------------------------
3544
3545
3546class Test(unittest.TestCase):
3547
3548    def testCopyAndDeepcopy(self):
3549        '''Test copying all objects defined in this module.
3550        '''
3551        import sys
3552        import types
3553        for part in sys.modules[self.__module__].__dict__:
3554            match = False
3555            for skip in ['_', '__', 'Test', 'Exception']:
3556                if part.startswith(skip) or part.endswith(skip):
3557                    match = True
3558            if match:
3559                continue
3560            name = getattr(sys.modules[self.__module__], part)
3561            # noinspection PyTypeChecker
3562            if callable(name) and not isinstance(name, types.FunctionType):
3563                try:  # see if obj can be made w/ args
3564                    obj = name()
3565                except TypeError:
3566                    continue
3567                i = copy.copy(obj)
3568                j = copy.deepcopy(obj)
3569                self.assertIsNotNone(i)
3570                self.assertIsNotNone(j)
3571
3572    def testFBN(self):
3573        fbn = fbNotation.Notation('6,3')
3574        V = RomanNumeral('V')
3575        sdb = V.bassScaleDegreeFromNotation(fbn)
3576        self.assertEqual(sdb, 7)
3577
3578    def testFigure(self):
3579        r1 = RomanNumeral('V')
3580        self.assertEqual(r1.frontAlterationTransposeInterval, None)
3581        self.assertEqual(r1.pitches, chord.Chord(['G4', 'B4', 'D5']).pitches)
3582        r1 = RomanNumeral('bbVI6')
3583        self.assertEqual(r1.figuresWritten, '6')
3584        self.assertEqual(r1.frontAlterationTransposeInterval.chromatic.semitones, -2)
3585        self.assertEqual(r1.frontAlterationTransposeInterval.diatonic.directedNiceName,
3586                         'Descending Doubly-Diminished Unison')
3587        cM = scale.MajorScale('C')
3588        r2 = RomanNumeral('ii', cM)
3589        self.assertIsNotNone(r2)
3590
3591        dminor = key.Key('d')
3592        rn = RomanNumeral('ii/o65', dminor)
3593        self.assertEqual(
3594            rn.pitches,
3595            chord.Chord(['G4', 'B-4', 'D5', 'E5']).pitches,
3596        )
3597        rnRealSlash = RomanNumeral('iiø65', dminor)
3598        self.assertEqual(rn, rnRealSlash)
3599
3600        rnOmit = RomanNumeral('V[no3]', dminor)
3601        self.assertEqual(rnOmit.pitches, chord.Chord(['A4', 'E5']).pitches)
3602        rnOmit = RomanNumeral('V[no5]', dminor)
3603        self.assertEqual(rnOmit.pitches, chord.Chord(['A4', 'C#5']).pitches)
3604        rnOmit = RomanNumeral('V[no3no5]', dminor)
3605        self.assertEqual(rnOmit.pitches, chord.Chord(['A4']).pitches)
3606        rnOmit = RomanNumeral('V13[no11]', key.Key('C'))
3607        self.assertEqual(rnOmit.pitches, chord.Chord('G4 B4 D5 F5 A5 E5').pitches)
3608
3609    def testBracketedAlterations(self):
3610        r1 = RomanNumeral('V9[b7][b5]')
3611        self.assertEqual(str(r1.bracketedAlterations), "[('b', 7), ('b', 5)]")
3612        self.assertEqual(str(r1.pitches),
3613                         '(<music21.pitch.Pitch G4>, <music21.pitch.Pitch B4>, '
3614                         + '<music21.pitch.Pitch D-5>, '
3615                         + '<music21.pitch.Pitch F-5>, <music21.pitch.Pitch A5>)')
3616
3617
3618    def testYieldRemoveA(self):
3619        from music21 import stream
3620        # s = corpus.parse('madrigal.3.1.rntxt')
3621        m = stream.Measure()
3622        m.append(key.KeySignature(4))
3623        m.append(note.Note())
3624        p = stream.Part()
3625        p.append(m)
3626        s = stream.Score()
3627        s.append(p)
3628        targetCount = 1
3629        self.assertEqual(
3630            len(s.flatten().getElementsByClass('KeySignature')),
3631            targetCount,
3632        )
3633        # through sequential iteration
3634        s1 = copy.deepcopy(s)
3635        for p in s1.parts:
3636            for m in p.getElementsByClass('Measure'):
3637                for e in m.getElementsByClass('KeySignature'):
3638                    m.remove(e)
3639        self.assertEqual(len(s1.flatten().getElementsByClass('KeySignature')), 0)
3640        s2 = copy.deepcopy(s)
3641        self.assertEqual(
3642            len(s2.flatten().getElementsByClass('KeySignature')),
3643            targetCount,
3644        )
3645        for e in s2.flatten().getElementsByClass('KeySignature'):
3646            for site in e.sites.get():
3647                if site is not None:
3648                    site.remove(e)
3649        # s2.show()
3650        # yield elements and containers
3651        s3 = copy.deepcopy(s)
3652        self.assertEqual(
3653            len(s3.flatten().getElementsByClass('KeySignature')),
3654            targetCount,
3655        )
3656        for e in s3.recurse(streamsOnly=True):
3657            if isinstance(e, key.KeySignature):
3658                # all active sites are None because of deep-copying
3659                if e.activeSite is not None:
3660                    e.activeSite.remove(e)
3661        # s3.show()
3662        # yield containers
3663        s4 = copy.deepcopy(s)
3664        self.assertEqual(
3665            len(s4.flatten().getElementsByClass('KeySignature')),
3666            targetCount,
3667        )
3668        # do not remove in iteration.
3669        for c in list(s4.recurse(streamsOnly=False)):
3670            if isinstance(c, stream.Stream):
3671                for e in c.getElementsByClass('KeySignature'):
3672                    c.remove(e)
3673
3674    def testScaleDegreesA(self):
3675        from music21 import roman
3676        k = key.Key('f#')  # 3-sharps minor
3677        rn = roman.RomanNumeral('V', k)
3678        self.assertEqual(str(rn.key), 'f# minor')
3679        self.assertEqual(
3680            str(rn.pitches),
3681            '(<music21.pitch.Pitch C#5>, '
3682            '<music21.pitch.Pitch E#5>, '
3683            '<music21.pitch.Pitch G#5>)',
3684        )
3685        self.assertEqual(
3686            str(rn.scaleDegrees),
3687            '[(5, None), (7, <music21.pitch.Accidental sharp>), (2, None)]',
3688        )
3689
3690    def testNeapolitanAndHalfDiminished(self):
3691        from music21 import roman
3692        alteredChordHalfDim3rdInv = roman.RomanNumeral(
3693            'bii/o42', scale.MajorScale('F'))
3694        self.assertEqual(
3695            [str(p) for p in alteredChordHalfDim3rdInv.pitches],
3696            ['F-4', 'G-4', 'B--4', 'D--5'],
3697        )
3698        iv = alteredChordHalfDim3rdInv.intervalVector
3699        self.assertEqual([0, 1, 2, 1, 1, 1], iv)
3700        cn = alteredChordHalfDim3rdInv.commonName
3701        self.assertEqual(cn, 'half-diminished seventh chord')
3702
3703    def testOmittedFifth(self):
3704        from music21 import roman
3705        c = chord.Chord('A3 E-4 G-4')
3706        k = key.Key('b-')
3707        rnDim7 = roman.romanNumeralFromChord(c, k)
3708        self.assertEqual(rnDim7.figure, 'viio7')
3709
3710    def testAllFormsOfVII(self):
3711        from music21 import roman
3712
3713        def p(c):
3714            return ' '.join([x.nameWithOctave for x in c.pitches])
3715
3716        k = key.Key('c')
3717        rn = roman.RomanNumeral('viio', k)
3718        self.assertEqual(p(rn), 'B4 D5 F5')
3719        rn = roman.RomanNumeral('viio6', k)
3720        self.assertEqual(p(rn), 'D4 F4 B4')
3721        rn = roman.RomanNumeral('viio64', k)
3722        self.assertEqual(p(rn), 'F4 B4 D5')
3723
3724        rn = roman.RomanNumeral('vii', k)
3725        self.assertEqual(p(rn), 'B4 D5 F#5')
3726        rn = roman.RomanNumeral('vii6', k)
3727        self.assertEqual(p(rn), 'D4 F#4 B4')
3728        rn = roman.RomanNumeral('vii64', k)
3729        self.assertEqual(p(rn), 'F#4 B4 D5')
3730
3731        rn = roman.RomanNumeral('viio7', k)
3732        self.assertEqual(p(rn), 'B4 D5 F5 A-5')
3733        rn = roman.RomanNumeral('viio65', k)
3734        self.assertEqual(p(rn), 'D4 F4 A-4 B4')
3735        rn = roman.RomanNumeral('viio43', k)
3736        self.assertEqual(p(rn), 'F4 A-4 B4 D5')
3737        rn = roman.RomanNumeral('viio42', k)
3738        self.assertEqual(p(rn), 'A-4 B4 D5 F5')
3739
3740        rn = roman.RomanNumeral('vii/o7', k)
3741        self.assertEqual(p(rn), 'B4 D5 F5 A5')
3742        # noinspection SpellCheckingInspection
3743        rn = roman.RomanNumeral('viiø65', k)
3744        self.assertEqual(p(rn), 'D4 F4 A4 B4')
3745        # noinspection SpellCheckingInspection
3746        rn = roman.RomanNumeral('viiø43', k)
3747        self.assertEqual(p(rn), 'F4 A4 B4 D5')
3748        rn = roman.RomanNumeral('vii/o42', k)
3749        self.assertEqual(p(rn), 'A4 B4 D5 F5')
3750
3751        rn = roman.RomanNumeral('VII', k)
3752        self.assertEqual(p(rn), 'B-4 D5 F5')
3753        rn = roman.RomanNumeral('VII6', k)
3754        self.assertEqual(p(rn), 'D4 F4 B-4')
3755        rn = roman.RomanNumeral('VII64', k)
3756        self.assertEqual(p(rn), 'F4 B-4 D5')
3757
3758        rn = roman.RomanNumeral('bVII', k)
3759        self.assertEqual(p(rn), 'B--4 D-5 F-5')
3760        rn = roman.RomanNumeral('bVII6', k)
3761        self.assertEqual(p(rn), 'D-4 F-4 B--4')
3762        rn = roman.RomanNumeral('bVII64', k)
3763        self.assertEqual(p(rn), 'F-4 B--4 D-5')
3764
3765        rn = roman.RomanNumeral('bvii', k)
3766        self.assertEqual(p(rn), 'B-4 D-5 F5')
3767        rn = roman.RomanNumeral('bvii6', k)
3768        self.assertEqual(p(rn), 'D-4 F4 B-4')
3769        rn = roman.RomanNumeral('bvii64', k)
3770        self.assertEqual(p(rn), 'F4 B-4 D-5')
3771
3772        rn = roman.RomanNumeral('bviio', k)
3773        self.assertEqual(p(rn), 'B-4 D-5 F-5')
3774        rn = roman.RomanNumeral('bviio6', k)
3775        self.assertEqual(p(rn), 'D-4 F-4 B-4')
3776        rn = roman.RomanNumeral('bviio64', k)
3777        self.assertEqual(p(rn), 'F-4 B-4 D-5')
3778
3779        rn = roman.RomanNumeral('#VII', k)
3780        self.assertEqual(p(rn), 'B4 D#5 F#5')
3781        rn = roman.RomanNumeral('#vii', k)
3782        self.assertEqual(p(rn), 'B#4 D#5 F##5')
3783
3784        rn = roman.RomanNumeral('VII+', k)
3785        self.assertEqual(p(rn), 'B-4 D5 F#5')
3786
3787    def testAllFormsOfVI(self):
3788        from music21 import roman
3789
3790        def p(c):
3791            return ' '.join([x.nameWithOctave for x in c.pitches])
3792
3793        k = key.Key('c')
3794        rn = roman.RomanNumeral('vio', k)
3795        self.assertEqual(p(rn), 'A4 C5 E-5')
3796        rn = roman.RomanNumeral('vio6', k)
3797        self.assertEqual(p(rn), 'C4 E-4 A4')
3798        rn = roman.RomanNumeral('vio64', k)
3799        self.assertEqual(p(rn), 'E-4 A4 C5')
3800
3801        rn = roman.RomanNumeral('vi', k)
3802        self.assertEqual(p(rn), 'A4 C5 E5')
3803        rn = roman.RomanNumeral('vi6', k)
3804        self.assertEqual(p(rn), 'C4 E4 A4')
3805        rn = roman.RomanNumeral('vi64', k)
3806        self.assertEqual(p(rn), 'E4 A4 C5')
3807
3808        rn = roman.RomanNumeral('vio7', k)
3809        self.assertEqual(p(rn), 'A4 C5 E-5 G-5')
3810        rn = roman.RomanNumeral('vio65', k)
3811        self.assertEqual(p(rn), 'C4 E-4 G-4 A4')
3812        rn = roman.RomanNumeral('vio43', k)
3813        self.assertEqual(p(rn), 'E-4 G-4 A4 C5')
3814        rn = roman.RomanNumeral('vio42', k)
3815        self.assertEqual(p(rn), 'G-4 A4 C5 E-5')
3816
3817        rn = roman.RomanNumeral('viø7', k)
3818        self.assertEqual(p(rn), 'A4 C5 E-5 G5')
3819        rn = roman.RomanNumeral('vi/o65', k)
3820        self.assertEqual(p(rn), 'C4 E-4 G4 A4')
3821        rn = roman.RomanNumeral('vi/o43', k)
3822        self.assertEqual(p(rn), 'E-4 G4 A4 C5')
3823        rn = roman.RomanNumeral('viø42', k)
3824        self.assertEqual(p(rn), 'G4 A4 C5 E-5')
3825
3826        rn = roman.RomanNumeral('VI', k)
3827        self.assertEqual(p(rn), 'A-4 C5 E-5')
3828        rn = roman.RomanNumeral('VI6', k)
3829        self.assertEqual(p(rn), 'C4 E-4 A-4')
3830        rn = roman.RomanNumeral('VI64', k)
3831        self.assertEqual(p(rn), 'E-4 A-4 C5')
3832
3833        rn = roman.RomanNumeral('bVI', k)
3834        self.assertEqual(p(rn), 'A--4 C-5 E--5')
3835        rn = roman.RomanNumeral('bVI6', k)
3836        self.assertEqual(p(rn), 'C-4 E--4 A--4')
3837        rn = roman.RomanNumeral('bVI64', k)
3838        self.assertEqual(p(rn), 'E--4 A--4 C-5')
3839
3840        rn = roman.RomanNumeral('bvi', k)
3841        self.assertEqual(p(rn), 'A-4 C-5 E-5')
3842        rn = roman.RomanNumeral('bvi6', k)
3843        self.assertEqual(p(rn), 'C-4 E-4 A-4')
3844        rn = roman.RomanNumeral('bvi64', k)
3845        self.assertEqual(p(rn), 'E-4 A-4 C-5')
3846
3847        rn = roman.RomanNumeral('bvio', k)
3848        self.assertEqual(p(rn), 'A-4 C-5 E--5')
3849        rn = roman.RomanNumeral('bvio6', k)
3850        self.assertEqual(p(rn), 'C-4 E--4 A-4')
3851        rn = roman.RomanNumeral('bvio64', k)
3852        self.assertEqual(p(rn), 'E--4 A-4 C-5')
3853
3854        rn = roman.RomanNumeral('#VI', k)
3855        self.assertEqual(p(rn), 'A4 C#5 E5')
3856        rn = roman.RomanNumeral('#vi', k)
3857        self.assertEqual(p(rn), 'A#4 C#5 E#5')
3858
3859        rn = roman.RomanNumeral('VI+', k)
3860        self.assertEqual(p(rn), 'A-4 C5 E5')
3861
3862    def testAugmented(self):
3863        from music21 import roman
3864
3865        def p(c):
3866            return ' '.join([x.nameWithOctave for x in c.pitches])
3867
3868        def test_numeral(country, figure_list, result, key_in='a'):
3869            for figure in figure_list:
3870                for with_plus in ('', '+'):
3871                    for kStr in (key_in, key_in.upper()):
3872                        key_obj = key.Key(kStr)
3873                        rn_str = country + with_plus + figure
3874                        rn = roman.RomanNumeral(rn_str, key_obj)
3875                        self.assertEqual(p(rn), result)
3876
3877
3878        test_numeral('It', ['6', ''], 'F5 A5 D#6')
3879        test_numeral('Ger', ['', '6', '65', '6/5'], 'F5 A5 C6 D#6')
3880        test_numeral('Fr', ['', '6', '43', '4/3'], 'F5 A5 B5 D#6')
3881        test_numeral('Sw', ['', '6', '43', '4/3'], 'F5 A5 B#5 D#6')
3882
3883        # these I worked out in C, not A ...  :-)
3884        test_numeral('It', ['53'], 'F#4 A-4 C5', 'C')
3885        test_numeral('It', ['64'], 'C4 F#4 A-4', 'C')
3886        test_numeral('Ger', ['7'], 'F#4 A-4 C5 E-5', 'C')
3887        test_numeral('Ger', ['43'], 'C4 E-4 F#4 A-4', 'C')
3888        test_numeral('Ger', ['42'], 'E-4 F#4 A-4 C5', 'C')
3889        test_numeral('Fr', ['7'], 'D4 F#4 A-4 C5', 'C')
3890        test_numeral('Fr', ['65'], 'F#4 A-4 C5 D5', 'C')
3891        test_numeral('Fr', ['42'], 'C4 D4 F#4 A-4', 'C')
3892        test_numeral('Sw', ['7'], 'D#4 F#4 A-4 C5', 'C')
3893        test_numeral('Sw', ['65'], 'F#4 A-4 C5 D#5', 'C')
3894        test_numeral('Sw', ['42'], 'C4 D#4 F#4 A-4', 'C')
3895
3896    def test_augmented_round_trip(self):
3897        # only testing properly spelled forms for input, since output will
3898        # always be properly spelled
3899        augTests = [
3900            'It6', 'It64', 'It53',
3901            'Ger65', 'Ger43', 'Ger42', 'Ger7',
3902            'Fr43', 'Fr7', 'Fr42', 'Fr65',
3903            'Sw43', 'Sw7', 'Sw42', 'Sw65',
3904        ]
3905
3906        c_minor = key.Key('c')
3907        c_major = key.Key('C')
3908
3909        for aug6 in augTests:
3910            rn = RomanNumeral(aug6, c_minor)
3911            ch = chord.Chord(rn.pitches)
3912            # without key...
3913            rn_out = romanNumeralFromChord(ch)
3914            if aug6 not in ('Ger7', 'Fr7'):
3915                # TODO(msc): fix these -- currently giving iø7 and Iø7 respectively
3916                self.assertEqual(rn.figure, rn_out.figure, f'{aug6}: {rn_out}')
3917                self.assertEqual(rn_out.key.tonicPitchNameWithCase, 'c')
3918
3919            # with key
3920            rn_out = romanNumeralFromChord(ch, c_minor)
3921            self.assertEqual(rn.figure, rn_out.figure, f'{aug6}: {rn_out}')
3922
3923            rn_out = romanNumeralFromChord(ch, c_major)
3924            self.assertEqual(rn.figure, rn_out.figure, f'{aug6}: {rn_out}')
3925
3926
3927    def testZeroForDiminished(self):
3928        from music21 import roman
3929        rn = roman.RomanNumeral('vii07', 'c')
3930        self.assertEqual([p.name for p in rn.pitches], ['B', 'D', 'F', 'A-'])
3931        rn = roman.RomanNumeral('vii/07', 'c')
3932        self.assertEqual([p.name for p in rn.pitches], ['B', 'D', 'F', 'A'])
3933
3934    def testIII7(self):
3935        c = chord.Chord(['E4', 'G4', 'B4', 'D5'])
3936        k = key.Key('C')
3937        rn = romanNumeralFromChord(c, k)
3938        self.assertEqual(rn.figure, 'iii7')
3939
3940    def testHalfDimMinor(self):
3941        c = chord.Chord(['A3', 'C4', 'E-4', 'G4'])
3942        k = key.Key('c')
3943        rn = romanNumeralFromChord(c, k)
3944        self.assertEqual(rn.figure, 'viø7')
3945
3946    def testHalfDimIII(self):
3947        c = chord.Chord(['F#3', 'A3', 'E4', 'C5'])
3948        k = key.Key('d')
3949        rn = romanNumeralFromChord(c, k)
3950        self.assertEqual(rn.figure, '#iiiø7')
3951
3952    def testAugmentedOctave(self):
3953        c = chord.Chord(['C4', 'E5', 'G5', 'C#6'])
3954        k = key.Key('C')
3955        f = postFigureFromChordAndKey(c, k)
3956        self.assertEqual(f, '#853')
3957
3958        rn = romanNumeralFromChord(c, k)
3959        self.assertEqual(rn.figure, 'I#853')
3960
3961    def testSecondaryAugmentedSixth(self):
3962        rn = RomanNumeral('Ger65/IV', 'C')
3963        self.assertEqual([p.name for p in rn.pitches], ['D-', 'F', 'A-', 'B'])
3964
3965    def testV7b5(self):
3966        rn = RomanNumeral('V7b5', 'C')
3967        self.assertEqual([p.name for p in rn.pitches], ['G', 'D-', 'F'])
3968
3969    def testNo5(self):
3970        rn = RomanNumeral('viio[no5]', 'a')
3971        self.assertEqual([p.name for p in rn.pitches], ['G#', 'B'])
3972
3973        rn = RomanNumeral('vii[no5]', 'a')
3974        self.assertEqual([p.name for p in rn.pitches], ['G#', 'B'])
3975
3976    def testNeapolitan(self):
3977        # False:
3978        rn = RomanNumeral('III', 'a')  # Not II
3979        self.assertFalse(rn.isNeapolitan())
3980        rn = RomanNumeral('II', 'a')  # II but not bII (no frontAlterationAccidental)
3981        self.assertFalse(rn.isNeapolitan())
3982        rn = RomanNumeral('#II', 'a')  # rn.frontAlterationAccidental != flat
3983        self.assertFalse(rn.isNeapolitan())
3984        rn = RomanNumeral('bII', 'a')  # bII but not bII6 and default requires first inv
3985        self.assertFalse(rn.isNeapolitan())
3986        rn = RomanNumeral('bii6', 'a')  # quality != major
3987        self.assertFalse(rn.isNeapolitan())
3988        rn = RomanNumeral('#I', 'a')  # Enharmonics do not count
3989        self.assertFalse(rn.isNeapolitan())
3990
3991        # True:
3992        rn = RomanNumeral('bII', 'a')  # bII but not bII6 and set requirement for first inv
3993        self.assertTrue(rn.isNeapolitan(require1stInversion=False))
3994        rn = RomanNumeral('bII6', 'a')
3995        self.assertTrue(rn.isNeapolitan())
3996
3997    def testMixture(self):
3998        for fig in ['i', 'iio', 'bIII', 'iv', 'v', 'bVI', 'bVII', 'viio7']:
3999            # True, major key:
4000            self.assertTrue(RomanNumeral(fig, 'A').isMixture())
4001            # False, minor key:
4002            self.assertFalse(RomanNumeral(fig, 'a').isMixture())
4003
4004        for fig in ['I', 'ii', '#iii', 'IV', 'vi', 'viiø7']:  # NB not #vi
4005            # False, major key:
4006            self.assertFalse(RomanNumeral(fig, 'A').isMixture())
4007            # True, minor key:
4008            self.assertTrue(RomanNumeral(fig, 'a').isMixture())
4009
4010    def testMinorTonic7InMajor(self):
4011        rn = RomanNumeral('i7', 'C')
4012        pitchStrings = [p.name for p in rn.pitches]
4013        self.assertEqual(pitchStrings, ['C', 'E-', 'G', 'B-'])
4014        for k in (key.Key('C'), key.Key('c')):
4015            ch1 = chord.Chord('C4 E-4 G4 B-4')
4016            rn2 = romanNumeralFromChord(ch1, k)
4017            self.assertEqual(rn2.figure, 'i7')
4018            ch = chord.Chord('E-4 G4 B-4 C5')
4019            rn = romanNumeralFromChord(ch, k)
4020            self.assertEqual(rn.figure, 'i65')
4021
4022        for k in (key.Key('G'), key.Key('g')):
4023            ch = chord.Chord('G4 B-4 C5 E-5')
4024            rn = romanNumeralFromChord(ch, k)
4025            self.assertEqual(rn.figure, 'iv43')
4026            ch = chord.Chord('B-4 C5 E-5 G5')
4027            rn = romanNumeralFromChord(ch, k)
4028            self.assertEqual(rn.figure, 'iv42')
4029
4030    def testMinorMajor7InMajor(self):
4031        def new_fig_equals_old_figure(rn_in, k='C'):
4032            p_old = [p.name for p in rn_in.pitches]
4033            rn_new = RomanNumeral(rn_in.figure, k)
4034            p_new = [p.name for p in rn_new.pitches]
4035            # order matters, octave does not
4036            self.assertEqual(p_old, p_new, f'{p_old} not equal {p_new} for {rn_in}')
4037
4038        rn = RomanNumeral('i7[#7]', 'C')
4039        pitchStrings = [p.name for p in rn.pitches]
4040        self.assertEqual(pitchStrings, ['C', 'E-', 'G', 'B'])
4041        ch1 = chord.Chord('C4 E-4 G4 B4')
4042        rn1 = romanNumeralFromChord(ch1, 'C')
4043        self.assertEqual(rn1.figure, 'i7[#7]')
4044        new_fig_equals_old_figure(rn1)
4045        ch2 = chord.Chord('E-4 G4 B4 C5')
4046        rn2 = romanNumeralFromChord(ch2, 'C')
4047        self.assertEqual(rn2.figure, 'i65[#7]')
4048        new_fig_equals_old_figure(rn2)
4049
4050        ch3 = chord.Chord('G4 B4 C5 E-5')
4051        rn3 = romanNumeralFromChord(ch3, 'G')
4052        self.assertEqual(rn3.figure, 'iv43[#7]')
4053        new_fig_equals_old_figure(rn3, 'G')
4054        ch4 = chord.Chord('B4 C5 E-5 G5')
4055        rn4 = romanNumeralFromChord(ch4, 'G')
4056        self.assertEqual(rn4.figure, 'iv42[#7]')
4057        new_fig_equals_old_figure(rn4, 'G')
4058
4059        # in minor these are more normal #7:
4060        rn1 = romanNumeralFromChord(ch1, 'c')
4061        self.assertEqual(rn1.figure, 'i#7')
4062        new_fig_equals_old_figure(rn1, 'c')
4063        rn2 = romanNumeralFromChord(ch2, 'c')
4064        self.assertEqual(rn2.figure, 'i65[#7]')
4065        new_fig_equals_old_figure(rn2, 'c')
4066
4067        ch3 = chord.Chord('G4 B4 C5 E-5')
4068        rn3 = romanNumeralFromChord(ch3, 'g')
4069        self.assertEqual(rn3.figure, 'iv43[#7]')
4070        new_fig_equals_old_figure(rn3, 'g')
4071        # except third-inversion...
4072        ch4 = chord.Chord('B4 C5 E-5 G5')
4073        rn4 = romanNumeralFromChord(ch4, 'g')
4074        self.assertEqual(rn4.figure, 'iv42[#7]')
4075        new_fig_equals_old_figure(rn4, 'g')
4076
4077
4078
4079class TestExternal(unittest.TestCase):
4080    show = True
4081
4082    def testFromChordify(self):
4083        from music21 import corpus
4084        b = corpus.parse('bwv103.6')
4085        c = b.chordify()
4086        cKey = b.analyze('key')
4087        figuresCache = {}
4088        for x in c.recurse():
4089            if isinstance(x, chord.Chord):
4090                rnc = romanNumeralFromChord(x, cKey)
4091                figure = rnc.figure
4092                if figure not in figuresCache:
4093                    figuresCache[figure] = 1
4094                else:
4095                    figuresCache[figure] += 1
4096                x.lyric = figure
4097
4098        if self.show:
4099            sortedList = sorted(figuresCache, key=figuresCache.get, reverse=True)
4100            for thisFigure in sortedList:
4101                print(thisFigure, figuresCache[thisFigure])
4102
4103        b.insert(0, c)
4104        if self.show:
4105            b.show()
4106
4107
4108# -----------------------------------------------------------------------------
4109_DOC_ORDER = [
4110    RomanNumeral,
4111]
4112
4113
4114if __name__ == '__main__':
4115    import music21
4116    music21.mainTest(Test)  # , runTest='testV7b5')
4117