smashub / choco

ChoCo: the Chord Corpus
Other
56 stars 6 forks source link

More granular within-measure onsets for iReal charts #20

Open jonnybluesman opened 2 years ago

jonnybluesman commented 2 years ago

The library we are currently using for parsing iReal charts (pyRealParser) does no provide granular onset information when multiple chords occur within the same measure. However, if enough context is given in the original charts, this can be improved.

Code-Omega commented 1 year ago

The original pyRealParser code removes all occurances of 'l's and occurances of 's's not part of a sus chord, as well as 'f' for fermata. It may be possible to retain these annotations and use them to extract more accurate onset and duration information.

By referencing the rendering on the iReal Pro App, all chords are initially large while chords following an 's' will be render in a condensed form that seems to have half the duration. All following chords will be rendered short/small until an 'l' occures in the chord string. So instead of the current equal-weight approach, better onset might be obtained via a few different approaches:

Let chords between a pair of 's' and 'l' be S (small chords) and the rest be L (large chords)

  1. A weighted approach where S takes half weight, assming 's' and 'l' denote relative weights. This way [S,S,L] in 4/4 will give [1,1,2] as durations. The pro is that there shouldn't be edge cases as all combinations of s and l chords will still fit nicely in a measure, but the con is the parsing of some measures still seem to not make sense (e.g. a measure of [S,L] in 4/4 still generates fractional durations [1.3,2.6] and is it debatable if that makes musical sense.
  2. A fixed approach where S takes one beat, and other chords takes the rest of the measure. In 4/4, [S,S,L] -> [1,1,2]; [S,L] -> [1,3] It is uncertain if there will be any edge cases. Whether it makes more sense will probably require some expert opinions.

Additionally, a fermata's extended duration can be applied to its following chords.

I've wrote up a potential adjustment to the current extraction process using the weighted approach here, though I'm leaning a bit towards using approach 2.

E.g. Parsing Round Midnight following extract_annotations_from_tune gives the following result compaired to the original. Related measures are 7, 15, and 41

([[1, 0, 4.0, 'Ah7'], [2, 0, 4.0, 'D7#9'], [3, 0, 4.0, 'Gh7'], [4, 0, 4.0, 'C7#9'], [5, 0, 4.0, 'Fh7'], [6, 0, 4.0, 'Bb7#9'], [7, 0, 2.0, 'Eb-7'], [7, 2.0, 1.0, 'Ab7'], [7, 3.0, 1.0, 'Db7'], [8, 0, 1.0, 'Gb7'], [8, 1.0, 1.0, 'B7'], [8, 2.0, 2.0, 'Bb7#5'], [9, 0, 2.0, 'Eb-7'], [9, 2.0, 2.0, 'Eb-7/Db'], [10, 0, 2.0, 'Co7'], [10, 2.0, 1.0, 'Ab-7'], [10, 3.0, 1.0, 'Db7'], [11, 0, 4.0, 'Eb-6'], [12, 0, 1.0, 'B-7'], [12, 1.0, 1.0, 'E7'], [12, 2.0, 1.0, 'Bb-7'], [12, 3.0, 1.0, 'Eb7'], [13, 0, 2.0, 'Ab-7'], [13, 2.0, 2.0, 'Db7'], [14, 0, 2.0, 'Eb-7'], [14, 2.0, 2.0, 'Ab7'], [15, 0, 1.3333333333333333, 'Ch'], [15, 1.3333333333333333, 2.6666666666666665, 'B7#11'], [16, 0, 4.0, 'Bb7'], [17, 0, 2.0, 'Eb-7'], [17, 2.0, 2.0, 'Eb-7/Db'], [18, 0, 2.0, 'Co7'], [18, 2.0, 1.0, 'Ab-7'], [18, 3.0, 1.0, 'Db7'], [19, 0, 4.0, 'Eb-6'], [20, 0, 1.0, 'B-7'], [20, 1.0, 1.0, 'E7'], [20, 2.0, 1.0, 'Bb-7'], [20, 3.0, 1.0, 'Eb7'], [21, 0, 2.0, 'Ab-7'], [21, 2.0, 2.0, 'Db7'], [22, 0, 2.0, 'Eb-7'], [22, 2.0, 2.0, 'Ab7'], [23, 0, 1.0, 'Ch'], [23, 1.0, 1.0, 'B7'], [23, 2.0, 2.0, 'Bb7sus'], [24, 0, 4.0, 'Eb6'], [25, 0, 2.0, 'Ch7'], [25, 2.0, 2.0, 'B7#11'], [26, 0, 4.0, 'Bb7#11'], [27, 0, 2.0, 'Ch7'], [27, 2.0, 2.0, 'B7#11'], [28, 0, 4.0, 'Bb7#11'], [29, 0, 2.0, 'Ab-7'], [29, 2.0, 1.0, 'F-7'], [29, 3.0, 1.0, 'Bb7'], [30, 0, 2.0, 'Ch7'], [30, 2.0, 2.0, 'F7'], [31, 0, 2.0, 'Db9'], [31, 2.0, 2.0, 'B9'], [32, 0, 2.0, 'Ab-7'], [32, 2.0, 1.0, 'F-7'], [32, 3.0, 1.0, 'Bb7'], [33, 0, 2.0, 'Ch7'], [33, 2.0, 2.0, 'F7'], [34, 0, 2.0, 'Ch7'], [34, 2.0, 2.0, 'F7'], [35, 0, 2.0, 'F#-7'], [35, 2.0, 2.0, 'B7'], [36, 0, 2.0, 'F#-7'], [36, 2.0, 2.0, 'B7'], [37, 0, 2.0, 'B-7'], [37, 2.0, 2.0, 'E7'], [38, 0, 2.0, 'F-7'], [38, 2.0, 2.0, 'Bb7'], [39, 0, 1.0, 'Eb'], [39, 1.0, 1.0, 'Ab'], [39, 2.0, 1.0, 'Db'], [39, 3.0, 1.0, 'Gb'], [40, 0, 4.0, 'Bb7b9b5'], [41, 0, 4.0, 'Eb^7#11']], [[1, 1, 164, 'Eb-']], [[1, 1, 164, '4/4']])

jonnybluesman commented 1 year ago

Thanks @Code-Omega for looking into this and for your preliminary investigation. Both these approaches are reasonable, and it would be nice to dig deeper into the implications and corner cases.

Regarding both approaches, please note that having fractional beat onsets and durations is currently possible in ChoCo, because we use music21 to obtain the "beat count" for each measure, depending on the current time signature (see this for more info). This may also produce some weird beat counts when there are ambiguous time signatures (see the example of 6/8 in the same page). Therefore, it may be worth looking at cases where we may not be able to assign a whole beat to an S marker (Approach 2).

In sum, I agree with you: Approach 1 is practical and flexible (and can still occur in other annotations in ChoCo) although raises concerns on its musical plausibility. Instead, Approach 2 is more sound but may introduce complications if we don't look at corner cases depending on the beat count of a measure.

Finally, it may be worth looking at how ireal-musicxml handles onsets and durations. We are currently in contact with the maintainer of this project as we are considering to integrate this contribution within ChoCo via a Python wrapper. This will allow us to reuse ChoCo's music21 parser to produce JAMS from MusicXML files. However, this also comes at the cost of importing additional libraries (it is a web-based project); therefore, having better native support in Python by improving the ireal_parser.py would also provide complementary features to both projects.

Code-Omega commented 1 year ago

No problem! @jonnybluesman Thanks for the suggesting ireal-musicxml, it is impressive work. I looked into the code and the discussion between its maintainer and the creator of ireal as documented here. I tried a few tricky pieces (odd time signatures and complex combination of S and L chords) and the results were pretty satisfactory.

According to the discussion, by nature of this particular encoding, chord duration determination for iReal will involve some approximations. Furthermore, the current Python parser's clean-up procedure removes critical information needed to recreate the chord duration as interperted by iReal Pro. The parser would certain require further modification if ChoCo's goal is to replicate the official interpertation (or maybe find some better approach along the way).

A wrapper over ireal-musicxml is certainly a good idea in the sense any future maintance will be synced up and leaves no room for inconsistencies. I suppose translating the js parser into Python should also be doable if scrutinizing the original Python parser is too much hassle.