cuthbertLab / music21

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

Harmony: map between function and figure #1103

Closed MarkGotham closed 2 years ago

MarkGotham commented 3 years ago

Motivation music21 supports Roman numeral analysis in many ways, but currently has nothing on function (T, S, D ... ).

Feature summary Provide a simple mapping between figure and function.

Proposed implementation Store pairs of corresponding figure and function labels on the roman.py module:

functionFigureTuples = (
    # Scale degrees (major)
    # 1:
    ('T', 'I'), 
    ('t', 'i'),
    # b2:
    ('sL', 'bII'),
    # 2:
    ('Sp', 'ii'),
    # b3:
    ('dL', 'III'),
    ('tP', 'III'),  # Note last: tP generally preferred over dL
    # 3:
    ('Tl', 'iii'),
    ('Dp', 'iii'),  # Note last: Dp generally preferred over tL
    # 4:
    ('S', 'IV'),
    ('s', 'iv'),
    # 5:
    ('D', 'V'),
    ('D7', 'V7'),
    ('d', 'v'),
    # b6:
    ('tL', 'VI'),
    ('sP', 'VI'),  # Note last: sP generally preferred over tL
    # 6:
    ('Sl', 'vi'),
    ('Tp', 'vi'),  # Note last: Tp generally preferred over Sl
    # b7:
    ('dP', 'VII'),
    # 7:
    ('Đ7', 'viio'),
    ('Dl', '#VII'),
    )

Either read directly from that, or store this in two dicts functiontoFigureDict and figureToFunctionDict. In any case, offer two functions for mapping functiontoFigure() and figureToFunction(). Probably static functions rather than methods on the roman.RomanNumeral class, because of the asymmetry: both accept str; the figureToFunction() probably also accepts a full roman.RomanNumeral object (retrieving .figureAlone for the equivalent input).

Intent I'm happy to finish up an implement of this, integrating any comments on the above, and/or any preference for where in the roman.py module it should go.

jacobtylerwalls commented 3 years ago

Cool idea. Spitballing for a second... what about a property?

>>> rn = roman.RomanNumeral('ii65', 'd')
>>> rn.function
<Function.PREDOMINANT>

Then folks have choices to either override the logic or edit the tables OR just manually set them:

>>> rn = roman.RomanNumeral('vi', 'C')
>>> rn.function
<Function.PREDOMINANT>
>>> rn.function = 'tonic'  # or 'tonic substitute'?? 
>>> rn.function
<Function.TONIC>

Maybe we add it to Harmony for now so that it can be manually set/gotten anywhere, including ChordSymbols, but then do the table lookup in the property getter on RomanNumeral. Opinions @mscuthbert ?

jacobtylerwalls commented 3 years ago

I could go either way on putting it on Harmony or RomanNumeral. Obviously function requires a tonal context. But I could imagine walking a lead sheet and annotating the chord symbols with their functions once the key is determined (manually or algorithmically).

mscuthbert commented 3 years ago

definitely a .function that returns an enum would be best.

It should probably also be a Flag Enum so that it could be both <harmonicFunction.PREDOMINANT|SEVENTH> to make for fewer things to check.

Note: harmonicFunction not function -- function means too many things in programming.

I think it should be overridable so that someone can change what it means. Important for I64. :-)

I do think that having each one be unique to a particular chord defeats the purpose though -- how is Đ7 easier to remember than viio7?

needs tests that it works on applied/secondary chords.

The other possibility is to put this in the analysis tools as harmonicFunction.py?

MarkGotham commented 3 years ago

Thanks @jacobtylerwalls and @mscuthbert for the ideas and comments.

While function can be about reducing several Roman numerals (V, vii, ...) to one function (D), the 1:1 mapping I set out here speaks more to it as an alternative ways of describing harmonies: for instance, most of my students in Germany have learned these functional label not in addition to, but instead of Roman numerals. So for that kind of (prospective) user, Đ7 is easier to remember than viio7. I don't know how many current music21 users would benefit from this, but offering this 'way in' could only help expand the relevance and reach (notwithstanding how modest this contribution is!).

All told, we have two potential use cases for this and probably two corners of music 21 that would fit it:

1130 is a PR for the latter use case. Note the option for a simplified return on functionToFigure that returns only one of 6 options – could be useful for any implementation on the Roman module.