cuthbertLab / music21

music21 is a Toolkit for Computational Musicology
https://www.music21.org/
Other
2.09k stars 397 forks source link

Change Spelling of Aug6 inversions in some cases #529

Closed mscuthbert closed 3 years ago

mscuthbert commented 4 years ago

Thinking through Music21's handling of Aug6th RomanNumerals, here’s my thought of refining it (in c minor):

Any of these can have a + added after the country name.

The “proper” spelling OR the country name alone OR the country name with a 6 afterwards becomes the standard inversion of the chord:

Ger, Ger6, Ger65 — all = standard German 6th. Ab in bass — changed to Ger65 internally Ger43 = C in bass Ger42 = Eb in bass Ger7 = F# in bass (n.b., this is not Ger7, not Ger43)

Fr, Fr6, Fr43 — all = standard French 6th. Ab in bass — changed to Fr43 internally Fr42 = C in bass Fr7 = D in bass Fr65 = F# in bass

Sw/Swiss/English/whatever you call sharp-2 — behaves exactly as French.

Italian is tricker, because there are two things that “It” alone might mean, so we will require:

It, It6 = standard Italian 6th. Ab in bass. Changed to It6 internally It64 = C in bass It53 = F# in bass — this is the ONLY case where we require 53 to be specified.

For Ger7 possibly allow Gero3 or Gerdim3

Current music21 output:

In [14]: for c in ('Ger', 'Ger6', 'Ger65', 'Ger43', 'Ger42', 'Ger7', 'Fr', 'Fr6', 'Fr43', 'Fr42', 'Fr7', 'Fr65', 
    ...: 'Sw', 'Sw6', 'Sw43', 'Sw42', 'Sw7', 'Sw65', 'It', 'It6', 'It64', 'It53', 'It7'): 
    ...:     try: 
    ...:         r = roman.RomanNumeral(c, 'C') 
    ...:         print(r, '\t',  
    ...:             ' '.join(p.nameWithOctave for p in r.pitches)) 
    ...:     except: 
    ...:         print(f'No dice for {r}') 

with stars next to ones that need updating:

* <music21.roman.RomanNumeral Ger in C major>    F#4 A-4 C5
* <music21.roman.RomanNumeral Ger6 in C major>   A-4 C5 F#5
  <music21.roman.RomanNumeral Ger65 in C major>  A-4 C5 E-5 F#5
  <music21.roman.RomanNumeral Ger43 in C major>  C4 E-4 F#4 A-4
  <music21.roman.RomanNumeral Ger42 in C major>  E-4 F#4 A-4 C5
  <music21.roman.RomanNumeral Ger7 in C major>   F#4 A-4 C5 E-5
* <music21.roman.RomanNumeral Fr in C major>     D4 F#4 A-4
* <music21.roman.RomanNumeral Fr6 in C major>    F#4 A-4 D5
  <music21.roman.RomanNumeral Fr43 in C major>   A-4 C5 D5 F#5
  <music21.roman.RomanNumeral Fr42 in C major>   C4 D4 F#4 A-4
  <music21.roman.RomanNumeral Fr7 in C major>    D4 F#4 A-4 C5
  <music21.roman.RomanNumeral Fr65 in C major>   F#4 A-4 C5 D5
* <music21.roman.RomanNumeral Sw in C major>     D#4 F#4 A-4
* <music21.roman.RomanNumeral Sw6 in C major>    F#4 A-4 D#5
  <music21.roman.RomanNumeral Sw43 in C major>   A-4 C5 D#5 F#5
  <music21.roman.RomanNumeral Sw42 in C major>   C4 D#4 F#4 A-4
  <music21.roman.RomanNumeral Sw7 in C major>    D#4 F#4 A-4 C5
  <music21.roman.RomanNumeral Sw65 in C major>   F#4 A-4 C5 D#5
* <music21.roman.RomanNumeral It in C major>     F#4 A-4 C5
  <music21.roman.RomanNumeral It6 in C major>    A-4 C5 F#5
  <music21.roman.RomanNumeral It64 in C major>   C4 F#4 A-4
  <music21.roman.RomanNumeral It53 in C major>   F#4 A-4 C5
* <music21.roman.RomanNumeral It7 in C major>    F#4 A-4 C5 E-5
napulen commented 3 years ago

Also, related to this: Any inversion of an Augmented Sixth chord should pass augsixth.isAugmentedSixth() == True.

Currently, only the inversions that showcase an (actual) A6 interval will pass this, which I found confusing at times. That is, a German Augmented Sixth in root position (showcasing a D3 between the F# and Ab) is still an augmented sixth chord.

Of course, unless this has been designed this way on purpose. In that case, I'd still say that it is not what I expect; I'd want any inversion to be an Augmented Sixth regardless of the presence of an A6 interval. (A dominant "seventh" in any inversion other than root position doesn't showcase any actual seventh, but it is still a seventh chord.)

Here is an example on the latest music21 release:

In [1]: import music21                                                                                                                                                                                             

In [2]: augsixth = music21.roman.RomanNumeral("Ger7", "c")                                                                                                                                                         

In [3]: augsixth.isAugmentedSixth()                                                                                                                                                                                
Out[3]: False

In [4]: augsixth = music21.roman.RomanNumeral("Ger65", "c")                                                                                                                                                        

In [5]: augsixth.isAugmentedSixth()                                                                                                                                                                                
Out[5]: True

In [6]: music21.__version__                                                                                                                                                                                        
Out[6]: '6.7.1'
jacobtylerwalls commented 3 years ago

Yeah, as currently designed, all the is augmented sixth methods depend on the canonical inversion.

returns True if the chord is an Augmented 6th chord in first inversion. (N.B. a French/Swiss sixth technically needs to be in second inversion)

This seems like a separate feature proposal to me. I see why it was done this way -- in the universe of common practice harmony, not every sonority is a "chord", so augmented-sixths aren't really "chords", they're voice-leading phenomena, but we need Roman numerals for them -- but I see your point that it would be nice to have a way to get at this information.

Maybe a keyword permitAnyInversion?

mscuthbert commented 3 years ago

Yeah -- I think that I would actually like to make isAugmentedSixth() by default return True on any inversion, so maybe the keyword is standardInversionOnly=False which people can change to True if they want it?

But I agree that this is a separate PR because the code for determining isAugmentedSixth() is distributed and convoluted, so it won't just be a line or two to change. It's very different code base than the parsing of RomanNumeral.

Correction to all this: the docs are pretty clear "in first inversion" so we will leave that as default.

I'm also working on the first part of the PR and realizing that if we want chords to round-trip (i.e., if rn = roman.RomanNumeral('It53') implies rn.figure == romanNumeralFromChord(rn).figure) then this needs to be changed as well.