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