w3c / mnx

Music Notation CG next-generation music markup proposal.
179 stars 18 forks source link

Unified encoding for rhythmic position, handling grace notes #314

Closed adrianholovaty closed 1 year ago

adrianholovaty commented 1 year ago

At the most recent co-chairs meeting, @dspreadbury and I dove into the specific issue of how do we encode a particular position into a bar, accounting for the fact that grace notes don't have a distinct rhythmic position?

This came about due to issue #313 (inner-bar clef changes). Each clef change needs to know its rhythmic position — and the encoding needs to handle rhythmic positions within a run of grace notes.

Concretely, we need to support a clef change at the start of a grace note run, vs. a clef change within a grace note run, vs. a clef change immediately after a grace note run. In each of these situations, the rhythmic offset is the same but the position with respect to the grace notes is different.

Though it's tempting to come up with an ad hoc solution for clef changes, it would be nice to solve this in a unified, holistic way — because there are other parts of MNX that would benefit from it, such as placement of hairpins.

One solution would be to rely on IDs for this — for example, "this clef change comes immediately before event ID xyz" — but in our estimation this is a bit too fragile. More importantly, this fails in the case of a clef change that applies to multiple voices in multivoice music (multiple sequences in MNX parlance).

Daniel and I came to agree that a two-dimensional encoding could be the right approach. "Two-dimensional" means two pieces of information: (1) the rhythmic offset into the bar and (2) a way to identify a specific grace note relative to that rhythmic offset. Daniel says Dorico has been using this approach successfully in its own internal encoding.

Here are two options we came up with.

grace-note-example

Option 1: Encode grace note index

The green grace note's position in the bar would be encoded as [[1, 4], 2]. The two elements of this array are:

Option 2: Encode grace note rhythmic offset

The green grace note's position in the bar would be encoded as [[1, 4], [1, 8]. The two elements of this array are:

The advantage of option 1 is: you don't need to worry about the grace notes' notated rhythms (i.e., how many beams they have). You merely count backward from the right. It also avoids dealing with fractions; the index is always an integer.

The advantage of option 2 is: it handles the case of multiple voices in which each voice has its own grace note run with different grace-note beaming for each voice (as long as the different beaming values still added up to an equal grace note rhythmic duration in both voices). This is admittedly an obscure edge case.

Another edge case is grace notes that appear at the end of a bar. Daniel and I discussed this and reasoned that they should be encoded as if they were in the subsequent bar (i.e., attached to their target note), so that they're always located with respect to their target note. We'd then use a simple boolean flag along the lines of isActuallyDisplayedInPreviousBar=true. But we don't need to work out the details on this particular edge case just yet.

Thoughts or other ideas?

adrianholovaty commented 1 year ago

Here is a refined proposal based on today's co-chair chat with @dspreadbury and @mscuthbert:

{
  "position": [1, 4],
  "graceIndex": 2
}

This is based on the previous option 1, with the differences being:

For the extreme edge case of a clef change that affects multiple voices, where each voice has grace notes and the clef change is positioned at a specific grace note position, we could have a "graceIndexSequence" key:

{
  "position": [1, 4],
  "graceIndex": 2,
  "graceIndexSequence": "voice1"
}

This would be the ID of the sequence that the "graceIndex" refers to. But, again, this is an extreme edge case and I'd suggest putting it aside for now (with the knowledge that we can always add it later in a backwards-compatible way).

adrianholovaty commented 1 year ago

This is now done, via the new "rhythmic position" object:

https://w3c.github.io/mnx/docs/mnx-reference/objects/rhythmic-position/

To see it in action, see the new clef changes example document. The inner-bar clef change is encoded with that rhythmic position:

https://w3c.github.io/mnx/docs/mnx-reference/examples/clef-changes/

This uses the new concept of a "positioned clef", documented here:

https://w3c.github.io/mnx/docs/mnx-reference/objects/positioned-clef/