w3c / mnx

Music Notation CG next-generation music markup proposal.
176 stars 19 forks source link

Are nested elements within sequences the right approach? #193

Closed adrianholovaty closed 4 years ago

adrianholovaty commented 4 years ago

In my work on the mnxconverter library, I've found that the nested nature of <tuplet> and <beamed> in MNX has been a bit irritating to deal with.

Figuring out the proper nesting — should <tuplet> go inside <beamed> or vice versa? — for a given musical situation is non-trivial, at least in the context of converting MusicXML to MNX. See this commit for a taste of the complexity.

For example, this music...

Screen Shot 2020-08-26 at 1 47 49 PM

...is encoded like so:

<beamed>
  <event value="/8">
    <note pitch="C5"/>
  </event>
  <tuplet inner="3/16" outer="2/16">
    <event value="/16">
      <note pitch="D5"/>
    </event>
    <event value="/16">
      <note pitch="E5"/>
    </event>
    <event value="/16">
      <note pitch="D5"/>
    </event>
  </tuplet>
  <event value="/8">
    <note pitch="C5"/>
  </event>
  <event value="/8">
    <note pitch="E5"/>
  </event>
</beamed>

And this music...

Screen Shot 2020-08-26 at 1 48 19 PM

...is encoded like so:

<tuplet inner="3/8" outer="2/8">
  <event value="/8">
    <rest/>
  </event>
  <beamed>
    <event value="/8">
      <note pitch="C5"/>
    </event>
    <event value="/8">
      <note pitch="E5"/>
    </event>
  </beamed>
</tuplet>

In the first example, the outer element is <beamed>. In the second example, the outer element is <tuplet>. The nesting differs depending on the musical situation.

This nested nature means that there's developer friction when doing the following with MNX:

There are advantages to the current MNX approach:

Another thing I should explicitly say is: music notation is inherently complex, so perhaps this is just something we have to deal with and this is the most elegant option available.

I'd like to hear thoughts from developers of music-notation software. Questions:

I am not necessarily proposing that we change MNX. It's more that I'd like to do a "gut-check" to make sure this is the right approach, based on my practical experience making the mnxconverter.

notator commented 4 years ago

Begin Edit 04.09.2020 Apologies: This posting was a bit hasty, and is superceded by comments in the remainder of this thread. In particular:

End Edit 04.09.2020

My MNXtoSVG application has no problem working with the MNX specification as it stands, since its internal data model for MNX-Common has both beams that can contain triplets and triplets that can contain beams. Beams can exist on both levels of nesting.

You say:

Figuring out the proper nesting — should <tuplet> go inside <beamed> or vice versa? — for a given musical situation is non-trivial, at least in the context of converting MusicXML to MNX.

So maybe your problem has more to do with getting the appropriate data structure out of MusicXML?

I agree with you completely that

music notation is inherently complex, so perhaps this is just something we have to deal with and this is the most elegant option available.

bhamblok commented 4 years ago

Personally I never experienced the friction you mention. In my opinion it is only logical to do the nesting depending on the musical situation. In HTML we can have similar situations where nesting is also very "context-aware". You can nest multiple elements in one way or the other depending of the situation.

mogenslundholm commented 4 years ago

Could it be even worse? image

Wonder if we should only allow the first encoding so that "beamed" should always be outside and "tuplet" always inside. (That would mean that in the second example the "rest" would technically be part of the beam - but look the same) An alternative solution would be to make "beamed" be specified in the same way as "slur". It starts at a certain note where the ID for the end-note is specified.

What does this mean for my program? I wanted to make a program where the symbols were independent of the context and no state of the system was needed. I did this by including the context in the symbol. For example symbols for a note: <?xml><mnx><score><mnx-common><part><measure><sequence><beamed><tuplet><event><note> <?xml><mnx><score><mnx-common><part><measure><sequence><tuplet><beamed><event><note> <?xml><mnx><score><mnx-common><part><measure><sequence><tuplet><event><note> <?xml><mnx><score><mnx-common><part><measure><sequence><beamed><event><note> <?xml><mnx><score><mnx-common><part><measure><sequence><event><note>

These are not legal identifiers in the program, but appears in the program as ID's like:
XmlMnxScoreMnxcommonPartMeasureSequenceTupletBeamedEventNote

My fear is that the number of symbols explode and my approach is impossible - but so far it works OK. But I wanted to test it.

notator commented 4 years ago

Yes, and it gets even worse than that! nestedTriplets @mogenslundholm Thanks for continuing the discussion here. Very useful! In both standard CWMN and in the Draft Spec, tuplets can contain tuplets, so your approach means that you will have to define arbitrarily many <event>types. That can't be right, so I think your fears are very justified!

I've been looking at this again, and now think the Draft Spec should be changed to be more MusicXML-like. Beams should be thought of, not as groupings of event symbols, but as alternative ways of displaying duration symbol flags. They should be defined, as it were, from the bottom up, not top down, so that there is no <beamed> element, and no conflict with (nested) tuplets. My application had no problem translating from the current (weak) MNX to SVG, but I can well imagine that translating to the current MNX from MusicXML was pretty hair-raising.

MusicXML's approach can be found at https://usermanuals.musicxml.com/MusicXML/Content/EL-MusicXML-beam.htm Apropos that file:

  1. The diagram accompanying the Example XML does not match the code. It should be: musicXML2
  2. I think MusicXML's fanned beams belong in an "advanced" file format, not in the basic (ca. 1900) CWMN format, so we don't need to consider them for the current Draft Spec.

The simplest thing to do would be to carry over MusicXML's <note><beam> element into an MNX <event><beam> element. MusicXML's approach is well tested, and I see no obvious way to improve it, so I propose that we start by doing that. (Also see usage in MNX by Example/Beams and MNX by Example/Beams (with hooks)) For example: nestedTriplets2Bars would become:

<mnx-common>
    ...
    <part>
        ...
        <measure>
            ...
            <sequence>
                <event value="/4">
                    <note pitch="C4"/>
                </event>
                <event value="/4">
                    <note pitch="E4"/>
                </event>
                <tuplet inner="3/8" outer="2/8">
                    <event value="/8">
                        <note pitch="G4"/>
                        <beam number="1">begin</beam>
                    </event>
                    <tuplet inner="3/8" outer="2/8"> /* nested tuplet */
                        <event value="/8">
                            <note pitch="C5"/>
                            <beam number="1">end</beam>
                        </event>
                        <event value="/8">
                            <note pitch="C5"/>
                            <beam number="1">begin</beam>
                        </event>
                        <event value="/16">
                            <note pitch="C5"/>
                            <beam number="1">continue</beam>
                            <beam number="2">begin</beam>
                        </event>
                        <event value="/16">
                            <note pitch="C5"/>
                            <beam number="1">continue</beam> /* continue at end of measure! */
                            <beam number="2">end</beam>
                        </event>
                    </tuplet>
                </tuplet>
            </sequence>
        </measure>
        <measure>
            <sequence>
                <tuplet inner="3/16" outer="2/16">
                    <event value="/16">
                        <note pitch="B4"/>
                        <beam number="1">continue</beam> /* start of measure! */
                        <beam number="2">begin</beam>
                    </event>
                    <event value="/16">
                        <note pitch="B4"/>
                        <beam number="1">continue</beam>
                        <beam number="2">continue</beam>
                    </event>
                    <event value="/16">
                        <note pitch="B4"/>
                        <beam number="1">continue</beam>
                        <beam number="2">end</beam>
                    </event>
                </tuplet>
                <event value="/8">
                    <note pitch="B4"/>
                    <beam number="1">end</beam>
                </event>
                <event value="/4">
                    <note pitch="E4"/>
                </event>
                <event value="/4">
                    <note pitch="C4"/>
                </event>
            </sequence>
        </measure>
    </part>
</mnx-common>

I think the above approach is basically correct, but this has been rather off the top of my head. There may well be ways to improve things. Maybe we could abbreviate some of MusicXML's names? Any other suggestions?

TODO: Check that, using the above approach, beamed groups of grace notes can exist inside ordinary beams. I think that's impossible with MNX as it is.

mogenslundholm commented 4 years ago

Yes, and it gets even worse than that! ... @notator ... In both standard CWMN and in the Draft Spec, tuplets can contain tuplets, so your approach means that you will have to define arbitrarily many <event>types. That can't be right, so I think your fears are very justified!

You are right - nested tuplets will be a huge problem for my program. Your beam-solution might give it a change so that symbols with tuplets had 10 symbols containing "<tuplet>", "<tuplet><tuplet>", "<tuplet><tuplet><tuplet>" etc. This in case nested tuplets are limited to 10. Otherwise there is no hope.

A college to me once said: It is never too late to give up.

notator commented 4 years ago

Looking at beamed grace notes inside ordinary beamed notes (with or without tuplets): This would be no problem using the MusicXML approach in MNX-Common, since there are two distinct <event> levels that can be separately parsed. beamedGraceNotes becomes:

<mnx-common>
    ...
    <part>
        ...
        <measure>
            ...
            <sequence>
                <event value="/8">
                    <note pitch="E4"/>
                    <beam number="1">begin</beam>
                </event>
                <grace>
                    <event value="/8">
                        <note pitch="E4"/>
                        <beam number="1">begin</beam>
                    </event>
                    <event value="/8">
                        <note pitch="G4"/>
                        <beam number="1">continue</beam>
                    </event>
                    <event value="/8">
                        <note pitch="A4"/>
                        <beam number="1">end</beam>
                    </event>
                </grace>
                <event value="/8">
                    <note pitch="D5"/>
                    <beam number="1">end</beam>
                </event>
            </sequence>
        </measure>
    </part>
</mnx-common>

Edit 04.09.2020: Note that grace beams, like ordinary beams, can easily cross barlines.

adrianholovaty commented 4 years ago

@notator Let's please keep this thread to the tightly constrained questions that I listed in the issue (under "I'd like to hear thoughts from developers of music-notation software"). I know it's tempting to come up with new ideas and proposals, but that distracts people from the main point of this issue.

notator commented 4 years ago

@adrianholovaty ?? There is obviously a problem with the way the current Draft Spec defines beams as elements nested inside <beamed> elements. There is an irreconcilable conflict between <beamed> and <tuplet> that needs to be resolved. The answer to this issue's question is therefore NO when talking about beams nested within sequences, but YES when talking about tuplets nested within sequences. Note that converting MusicXML to MNX gets MUCH easier if <beamed> elements simply don't exist. You, and others migrating from MusicXML to MNX, don't then have to solve the non-trivial problem of working out whether they have to be written inside or outside the <tuplet>. All you have to do is copy the MusicXML <note><beam> code over to MNX-Common <event><beam> elements. The MusicXML approach is a little more verbose than current MNX-Common, but it is both simple to understand and much more powerful! How, if MusicXML can describe notations that current MNX can't, are you going to convert all MusicXML files to MNX? That question arises directly from the answer to this issue. Shall we open a new issue for it?

mogenslundholm commented 4 years ago

Are there any examples of nested tuplets? Are we talking about something that has never been used?

adrianholovaty commented 4 years ago

@mogenslundholm Yes, nested tuplets definitely appear in real-world music. They're not necessarily supported by all notation software, though.

notator commented 4 years ago

Following @adrianholovaty's comment in https://github.com/w3c/mnx/issues/193#issuecomment-686379403, I have now taken a closer look at my current MNXtoSVG code. Discovered that my original reply in https://github.com/w3c/mnx/issues/193#issuecomment-681755852 was inadequate and even misleading. Here's a revision:

Does your internal data model store tuplets and beams in a nested form similar to the MNX approach?

No. It uses the current MNX <beamed> element to set Event attributes IsBeamStart and IsBeamEnd on the boundary events in each <beamed> group. It would be quite easy to change the code to reflect the more powerful MusicXML approach to beams by adding beam levels and a BeamContinues attribute. (I'm not sure that we really need beam levels, since the beams that stop/start can be inferred from the duration class. Beams stack -- But that's a different issue. (Edit: Correction, we do need MusicXML's beam levels since beam number 2 may or may not continue from a 32nd note.))

Given your internal data model, would it be difficult (or at least annoying) to generate tuplets/beams in MNX?

No.

Given your internal data model, would it be difficult (or at least annoying) to read MNX?

No.

Note that this does not mean that I think the current MNX specification is the best we can do.

bhamblok commented 4 years ago

I think, after reading this thread, @notator has a good point:

There is an irreconcilable conflict between <beamed> and <tuplet> that needs to be resolved. The answer to this issue's question is therefore NO when talking about beams nested within sequences, but YES when talking about tuplets nested within sequences.

So can I already make a conclusion that we'll have to define "if nesting is possible or not" for each different element. Or, maybe I can make a conclusion that elements which affect durations (like <tuplet>) can be used as "container elements" (for other elements as well as for themselves => nesting), while other elements which do not affect durations but which potentially span other elements (like <beam(ed)> or <slur>), can not be used as a "container elements"? As a sidenote: I think, apart from a completely different semantical meaning, <slur> could have been defined very similar as <beam(ed)> and/or visa versa.

I also peeked at the specs of MEI where they also define <beam> as a "container element"... But for the use case of @mogenslundholm in https://github.com/w3c/mnx/issues/193#issuecomment-685084361, MEI also defines <beamSpan>. I'm sorry to say, but I think that this is also not a good approach to define 2 different elements which can potentially be interchangeable. Therefor I think the MusicXML-approach like @notator already mentioned in https://github.com/w3c/mnx/issues/193#issuecomment-685799364, is indeed the best alternative. However, I would definitely not make use of a "number"-attribute, which caused me a lot of developer-friction in the past (especially when dealing with cross-measure-beams in combination with multiple voices and the <backup>- and <forward>-elements), but I would use unique id's instead. Is this something worthwhile to create a new issue for?

mogenslundholm commented 4 years ago

@adrianholovaty : Do you have any thoughts about a slur-like solution for beam? (set at start of beam with ID of last note) PS: Would solve the problem of overlap between tuples and beams

adrianholovaty commented 4 years ago

The way I see it, the current MNX design for tuplets seems right. It allows for nesting and is "just complex enough" to handle any rhythmic situation, given the complexity of music notation.

Beams, on the other hand, are much more presentational in nature (though it's obviously also semantic data and important to preserve).

Consider old-fashioned American choral notation from the 1800s, which did not beam eighth notes. The same music engraved today would use beaming — but the underlying music is rhythmically identical.

That gets to the heart of why this seems a bit awkward. The current MNX design gives tuplets and beams the same "weight," whereas beams are not nearly as important to the music.

ahankinson commented 4 years ago

@bhamblok The use of beamSpan in MEI is typically used to encode beams that span measures -- in that case, you cannot have a hierarchy of elements. This is what we call 'stand-off notation', where the element describing a particular musical idiom does not fit well within a given musical hierarchy. You will notice that there are also bracketSpan and tupletSpan for the same reasons, and that none of the *Span elements can have any child elements.

But 99% of the time, using beam with note children is what is recommended.

bhamblok commented 4 years ago

@ahankinson thank you for this elaboration.

I never thought of the possibility of tuplets spanning measures. :-)

This means that the <tuplet> element cannot be used as a "container element" and thus the current MNX design for tuplets does not meet all use-cases. Unless we take a similar approach as MEI does, creating other <*Span> elements for those "stand-off notation" use-cases.

I'm starting to think that the answer on the title of this issue is (regretfully) "NO".

Should we redefine the current proposal for tuplets as well? Again in a similar way as musicXML does? (which as far as I know does meet all use-cases)

adrianholovaty commented 4 years ago

@bhamblok I'm not particularly concerned about <tuplet> not handling tuplets over barlines. We can come up with a separate way to encode those types of tuplets, given their relative rarity.

notator commented 4 years ago

@bhamblok said:

... I think the MusicXML-approach like @notator already mentioned in #193 (comment), is indeed the best alternative. However, I would definitely not make use of a "number"-attribute, which caused me a lot of developer-friction in the past (especially when dealing with cross-measure-beams in combination with multiple voices and the <backup>- and <forward>-elements), but I would use unique id's instead.

and

Is this something worthwhile to create a new issue for?

First, the issue issue: I think it might be a good idea either to create a new issue (about redefining the way tuplets and beams are coded), or to rename this one. Creating a new issue would interrupt the discussion that is developing, so I'll continue here for the time being.

@bhamblok Using a <beam>id attribute instead of <beam>number is a very interesting idea. (Please correct me if I have misunderstood what you meant. We need to get to the bottom of this.)

I think that ids might have been better in MusicXML, but that they may not be necessary when this approach is used in MNX:

In MNX, we have a <part><measure><sequence><event> hierarchy, with each sequence of <measure><sequence> elements containing a single "voice" in a <part>. That means that there is no ambiguity about the sequence of events across different measures (i.e. in the same voice). So there is no ambiguity about where a beam continues if it continues across a barline. Does that make sense? Notes:

notator commented 4 years ago

Begin Edit 09.09.2020 Apologies: I was a bit disoriented by @bhamblok's reply to this posting, and that lead to several unnecessary edits. The examples here are not entirely correct (e.g. they wrongly assume that the current Draft Spec allows beams to change their voice) but I have finally decided that I was on the right track after all, and to revert this posting to the form it had when the reply came. My reply to the reply is now in the usual sequence (below). End Edit 09.09.2020

beam.number attributes are easy to use and perfectly sufficient if the beam continues in the same voice.

But what happens if the beam continues in a different voice? (See the example below.)

This can be done by adding an optional target attribute to the beam element, linking it to the event where the beam continues. This target attribute would not be available if the beam's type is "end", so I'd like to change the type from being element content to being an attribute. Like this:

<beam number="1" type="begin" target="..." /> or <beam number="1" type="continues" target="..." />

There are (at least) three ways to define the target attribute:

I prefer alternative 3, in which crossVoiceBeam would be encoded (with sequence.voice refactored to sequence.voiceID), as:

<mnx-common>
    ...
    <part>
        ...
        <measure>
            ...
            <sequence staff="1" voiceID="voice1">
                <event value="/8">
                    <note pitch="E4"/>
                    <beam number="1" type="begin" target="voice2:evt2" />
                </event>
                <event value="/8">
                    <rest/>
                </event>
            </sequence>
            <sequence staff="2" voiceID="voice2">
                <event value="/8">
                    <rest/>
                </event>
                <event value="/8" id="evt2">
                    <note pitch="F3"/>
                    <beam number="1" type="end" />
                </event>
            </sequence>
        </measure>
    </part>
</mnx-common>

It might be a good idea to provide a link from the target beam back to the event where the beam came from. Like this: <beam number="1" type="end" source="voice1:evt1" />

Note that this general approach would also work for beams that change <part>s (as in full scores).

Afterthought: In the simple case of 16ths changing voices (stem directions), the beam numbers effectively get reversed. That's going to lead to trouble. And there is an ambiguity that arises when the number of beams changes across the link. Which beam in the target supposed to continue? The answer is that the target attribute also needs to say which beam in the target event it is referring to. Maybe like this: <beam number="1" type="begin" target="voice2:evt2:beamNumber1" /> or <beam number="1" type="begin" target="voice2:evt2:beam1" /> or just <beam number="1" type="begin" target="voice2:evt2:1" />

Begin Edit 07.09.2020 Problem: A beam's number currently seems to depend on the value of the event.orient attribute (i.e. whether the stem is up or down). I'm not sure how MusicXML deals with this, but think it would be better to use the beams' duration class explicitly: Replace number="1" by level="8", number="2" by level="16", number="3" by level="32" etc. For example, to make the beam code independent of the stem direction, replace: <beam number="1" type="begin" target="voice2:evt2:1" /> by <beam level="8" type="begin" target="voice2:evt2:8" /> The source beam's code then does not need to change if the target event's orientation changes. End Edit 07.09.2020

Its pretty amazing that all this wasn't cleared up long ago... But I think we're definitely making progress -- thanks to @adrianholovaty's idea of working on real code examples. :-)

Comments/thoughts welcome, of course. :-)

bhamblok commented 4 years ago

@notator I am not supporting the idea of referencing one element to another by using relative paths. We can simple resolve this issue by using unique ID's. I would like to reference to issue 65: immutable and augmentable UIDs

notator commented 4 years ago

@bhamblok I did indeed rather misunderstand you, when you said (in https://github.com/w3c/mnx/issues/193#issuecomment-687076756)

I would use unique id's instead.

I should have noticed how important the "unique" was. Thanks for mentioning Issue #65, and for making me take a closer look at the Draft Spec!

I now see that the Spec uses scoped "ids": The value of the sequence.staff attribute is only referenced from inside the <part> scope where it is defined. Similarly for the sequence.voice value. These "ids" are scoped to an element that is not as large as the file in which they are defined. So they are not global ids (that have to be unique). For example: "part1:measure10:event3" is not the same <event> as "part1:measure20:event3". These two strings are unique identifiers for different events, but many events can have "event3" as their "id" because the "ids" are used in different scopes. The <part> ids define the top-level scope, so they should be unique. All that #65 means by "unique" is, I think, that the values should transfer properly between files when the files are edited. If <part> ids are the names of instruments (e.g. "Flute1", "Flute2" etc.) that will be the case. So there's no problem. I think that giving every element a non-relative, unique (top-level) id would quickly become illegible and unworkable. @bhamblok Maybe I'm still not really understanding what you mean. If that's the case, perhaps you could elaborate and/or post some demo code.

The following issues follow from the above: New Issue: Each <part> element must have a unique id The globals section of the Draft Spec says that the globals.parts attribute is "an optional set of IDs of <part> elements to which this global content applies", but neither §6.1.9 The part element nor §6.11 Part description content say that <part> has an id attribute. I think that every <part> MUST have a unique id that can be used both in the globals.parts attribute, and to scope other ids.

New Issue: §5.5 Measure location syntax I think a location should consist of a <measure> number, an <event> number (not the <event>'s metric position in the measure) and an optional <note> number. The event.number would be the absolute number of the event in the measure, including events inside <tuplet> and <grace> elements. The note.number would be the number of the <note> in the <event>. (top-bottom order, I think). Note that the handling of grace-notes would be much less complicated, if location did not depend on metrical position...

New Issue: §6.7.2 The slurs section of the Draft Spec needs some attention: It currently says that the slur.location attribute is only used if slur.target (an ID) is not defined. In fact, either thelocation or the target attribute is redundant. My preference would be to call the attribute target.

Slurs can be defined without using complex (unique) global ids, and the spec needs to be changed accordingly.


Back to beams. In my previous posting, I failed to take account of the following in the current Draft Spec:

  1. a <beam> never changes to a different voice
  2. <sequence> has an optional staff attribute, that determines the default staff for all the <sequence>s in the same voice.
  3. <event> has an optional staff attribute, that overrides the voice's default staff.

Consider the following (Bach): crossStaffBeam1 This could be coded as follows using

  1. current Draft Spec elements and attributes, but the MusicXML <beam> number approach.
  2. the <beam> element's type as an attribute rather than as element content.
<mnx-common>
    ...
    <part id="Organ">
       ...
       /* measures are defined in chronological order. The first is number 1 */
       <measure>
           ...
           /* sequences are defined in top-down order. The first is number 1 */
           <sequence staff="1">  /*** default staff number for this voice */
               <event value="/4">
                   <note pitch="C5"/>
               </event>
           </sequence>
           <sequence staff="2">  /*** default staff number for this voice  */
               <event value="/16">
                   <note pitch="Ab3"/>
                   <beam number="1" type="begin" />
                   <beam number="2" type="begin" />
               </event>
               <event value="/16">
                   <note pitch="C4"/>
                   <beam number="1" type="continue" />
                   <beam number="2" type="continue" />
               </event>
               <event value="/16" staff="1">  /*** override this voice's default staff number*/
                   <note pitch="F4"/>
                   <beam number="1" type="continue" />
                   <beam number="2" type="continue" />
               </event>
               <event value="/16" staff="1">  /*** override this voice's default staff number*/
                   <note pitch="E4"/>
                   <beam number="1" type="end" />
                   <beam number="2" type="end" />
               </event>
           </sequence>
       </measure>
   </part>
    /* etc. */
</mnx-common>

Note that, if the stems change direction: crossStaffBeam3 Beam number 1, on events 1 and 2, can become beam number 2 on events 3 and 4. Contrary to my previous posting, I no longer think that matters. Does MusicXML do anything special in situations like this?

I think that beams having type="begin" or type="continue" should be allowed to change voice. As far as I can see, that is not currently allowed by the Draft Spec, but could be achieved in the same way as with slurs -- by using a target attribute that is a scoped identifier.

bhamblok commented 4 years ago

off-track: @notator, "part1:measure20:event3" is not a unique ID for that event, it's more like an "xml-path". Instrument-names as id's for <part>-elements are also useless, we have part-name and/or instrument-sound for that...

Imagine MNX to be used as the native file-format for an editor application. The user could insert an additional event just before your "part1:measure20:event3"-event. ... Or the user changes instrument for a particular part... That would break your id's, or that would imply that this editor application will have to alter all "target"-attributes referencing those elements...

I think that giving every element a non-relative, unique (top-level) id would quickly become illegible and unworkable.

ID-attributes should be optional, so it won't become illegible and unworkable. An element only needs an Id when another element is referring to it (eg: via a target-attribute).

@all could we tag issue #65 back to "active review" and define the spec more accordingly on https://w3c.github.io/mnx/specification/common/#element-locations to have a more specific definition on an "XML ID". We now have the urge of using it, because we are willing to use "target"-attributes, which wasn't the case back then.

For example: on the MNX by example-page, Slurs (targeting specific notes), id's are being used like "event1", "event2" or "note1", "note2"... That could work, it's fine, but actually these are bad examples in the context of #65 or in the imaginary use-case here above in this comment.

mogenslundholm commented 4 years ago

Are beams either or? The Bach-example above specifies two beams, but it seems to me there is only one beam - that becomes to lines because they are 1/16. (I.e. do we need IDs and Numbers?)

notator commented 4 years ago

@bhamblok

"part1:measure20:event3" is not a unique ID for that event, it's more like an "xml-path".

Since all parts, measures, voices, sequences, events and notes are kept strictly in horizontal or vertical order, we don't actually need IDs at all to single out any one of them. We can treat the whole MNX structure as a ragged array, and use indexing. That would make everything much simpler. Its fine if that's what you mean by "xml-path". The string "part1:measure20:event3" actually consists just of the numbers "1:20:3" but with named dimensions. If the part contained more than one voice, the string would have to contain a voice component: "part1:measure20:voice2:event3". If the part can contain multi-note events, the string could either point at "note1" by default, or have a note component: "part1:measure20:voice2:event3:note4". The named dimensions make things easier for humans to read, but I think they could in fact be deduced from the part structures (multi-voice, multi-note) etc. It might be best to avoid errors (like getting the measure and voice values in the wrong order) by defining a compromise: "p1:m20:v2:e3:n4".

Instrument-names as id's for <part>-elements are also useless, we have part-name and/or instrument-sound for that...

In MNX by Example: Parts, MusicXML defines the <part-name> element for a part using the <part>'s id attribute (in the <part-list> at the top of the example). The id attribute and the <part-name> element are not the same thing. The <part-name> element is mentioned, but not described, as part of the Draft Spec §6.11 Part description content. §6.11's equivalent MusicXML definitions can be found in the MusicXML part-name definition. MusicXML's part.id has gone missing in the equivalent MNX code. That's fine, if parts are kept in strict vertical order, but if (as in my previous postings) the id is defined, it would be easier for humans to read the code, if the string looked more like "Flute1", "Flute2" etc. than just "P1", "P2" etc. Its interesting to note (again) that §6.1.9 of the Draft Spec does not define an id attribute for the <part> element -- but that §6.1.8 still mentions them. I think §6.1.8 could get by just using part numbers.

Imagine MNX to be used as the native file-format for an editor application. The user could insert an additional event just before your "part1:measure20:event3"-event. ... Or the user changes instrument for a particular part... That would break your id's, or that would imply that this editor application will have to alter all "target"-attributes referencing those elements...

If, for example, the user of such an application deletes an event that is the target of a slur, then the slur would automatically point at the next event. If that's not what the user meant, she/he can redefine the slur. Similar things happen when adding/deleting parts, measures, notes etc. I see no problem there. A much bigger problem arises if the slur is pointing at an event having a unique id: If the event is deleted, the slur's target becomes completely undefined. In that case, an application would have to maintain a separate list of all slur targets (and all other IDs that are being used as targets) in order to either a) reattach the slur to some nearby event or b) delete the undefined link from the slur definition. If the slur ended up being unattached to anything, the user would have to scroll back to the beginning of the slur in order to reattach it. Quite apart from this scenario, note that applications don't have to use MNX as their native format. All that's necessary is that they can read and write MNX files correctly (at read/write time). The bottom line is that it would be much easier for an application to maintain the element structure as a ragged array or as linked lists, than to have to keep track of unique IDs.

@ALL could we tag issue #65 back to "active review" and define the spec more accordingly on https://w3c.github.io/mnx/specification/common/#element-locations to have a more specific definition on an "XML ID". We now have the urge of using it, because we are willing to use "target"-attributes, which wasn't the case back then.

As I said in https://github.com/w3c/mnx/issues/193#issuecomment-689510641, I also think that §3.2.3. Element locations needs to be reviewed.

For example: on the MNX by example-page, Slurs (targeting specific notes), id's are being used like "event1", "event2" or "note1", "note2"... That could work, it's fine, but actually these are bad examples in the context of #65 or in the imaginary use-case here above in this comment.

As I said, I think MNX can be defined without using IDs at all, and that that would simplify things considerably. So the Slurs examples need to be revised (after updating the Spec everywhere).

notator commented 4 years ago

My position has been changing during the course of this thread, thanks mainly to the discussion with @bhamblok. I think we've really got somewhere, but that the above thread is sometimes confused and/or mistaken. At any rate, its quite difficult to follow. In the hope that it helps, here's a summary of where I'm currently at:

  1. I think this issue's title should be changed to reflect what we actually discussed. Its very good that some issues are allowed to be wider ranging than originally imagined!
  2. The <beamed> element should be removed from the Draft Spec. Instead, MNX should use a <beam> element like MusicXML's, but using a beam.type attribute instead of MusicXML's element content. (That's because <beam ... type="end" /> elements may not have a target attribute.) See the <beam> examples below.
  3. Contrary to previous postings, I no longer think that the slur.location attribute is redundant. It provides the position of the end-point of a <slur> (which can be in a measure different from the one in which the <slur> is defined) when the <slur> is not attached to some target <event> or <note>. The values of slur.location and slur.target are defined differently: slur.location is currently a "measure location", slur.target is a "target string" (see next point).
  4. All IDs in the Draft Spec (the values of target attributes) should be replaced by "target string"s of the form "p1:m1:v1:e1:n1", where each component (between ":" characters) represents the numerical position of an element in an ordered list. The non-numeric characters identify the list: 'p'-><parts>, 'm'-><measures>, 'v'-><voices>, 'e'-><events>, 'n'-><notes>. If a component is missing, its value is either inherited from the source element or it has a default value. For example: if <slur target="e7"> is defined inside the event having the address "p1:m1:v1:e3", it would be a slur in the same part, measure and voice connecting the five events "e3", "e4", "e5", "e6" and "e7". If the 'm' component is defined (<slur target="m3:e2">), the slur connects to an event in that measure . If the 'n' component is defined (<slur target="e7:n2">), the slur would attach to that particular note in the event. If the 'n' component is not defined, slurs attach to the top or bottom <note> in the event to which they are attached, depending on the values of their side or side-end attributes. Note that this subsumes the current slur.end-note attribute into the value of the target attribute, but that the optional slur.start-note attribute can still be defined (using an "n"-string). See the MNX by Example - Slurs (targeting specific notes) example below. Note that event numbers include normal events and the events in both <tuplet> and <grace> elements. This makes MNX much simpler since many currently defined id attributes can be deleted (e.g. in the <event> and <note> elements -- the <part> element does not currently have an id attribute), and the values of target attributes can be simple "target strings" that are relative to the current scope. I don't think things are being made too simple, because we are only concerned here with the content of an MNX file (where the order of elements in their lists is always fixed). We are not concerned with applications' internal data structures, only that the pointers can be connected up correctly when reading the file, and saved correctly when writing it.

Here are the current MNX by Example beams and slurs examples, coded according to these conventions: MNX by Example - Beams: example-beams

<mnx-common>
    ...
    <part> 
        ...
        <measure>
            ...
            <sequence>
                <event value="/8">
                    <note pitch="C5"/>
                    <beam number="1" type="begin" />
                </event>
                <event value="/8">
                    <note pitch="D5"/>
                    <beam number="1" type="continue" />
                </event>
                <event value="/8">
                    <note pitch="E5"/>
                    <beam number="1" type="continue" />
                </event>
                <event value="/8">
                    <note pitch="F5"/>
                    <beam number="1" type="end" />
                </event>
                <event value="/8">
                    <note pitch="D4"/>
                    <beam number="1" type="begin" />
                </event>
                <event value="/8">
                    <note pitch="E4"/>
                    <beam number="1" type="continue" />
                </event>
                <event value="/8">
                    <note pitch="F4"/>
                    <beam number="1" type="continue" />
                </event>
                <event value="/8">
                    <note pitch="G4"/>
                    <beam number="1" type="end" />
                </event>
            </sequence>
        </measure>
        <measure>
            <sequence>
                <event value="/8">
                    <note pitch="G4"/>
                    <beam number="1" type="begin" />
                </event>
                <event value="/8">
                    <rest/>
                </event>
                <event value="/16">
                    <note pitch="D4"/>
                    <beam number="1" type="continue" />
                    <beam number="2" type="begin" />
                </event>
                <event value="/16">
                    <note pitch="E4"/>
                    <beam number="1" type="continue" />                    
                    <beam number="2" type="end" />
                </event>
                <event value="/8">
                    <note pitch="G4"/>
                    <beam number="1" type="end" />
                </event>
                <event value="/8">
                    <note pitch="E4"/>
                    <beam number="1" type="begin" />
                </event>
                <event value="/8">
                    <note pitch="D4"/>
                    <beam number="1" type="end" />
                </event>
                <event value="/4">
                    <note pitch="C4"/>
                </event>
            </sequence>
        </measure>
    </part>
</mnx-common>

MNX by Example - Beams (with hooks): example-beamhooks

<mnx-common>
    ...
    <part>
        ...
        <measure>
            ...
            <sequence>
                <event value="/16">
                    <note pitch="C5"/>
                    <beam number="1" type="begin" />
                    <beam number="2" type="forwardHook" />
                </event>
                <event value="/8">
                    <note pitch="D5"/>
                    <beam number="1" type="continue" />
                </event>
                <event value="/16">
                    <note pitch="C5"/>
                    <beam number="1" type="end" />
                    <beam number="2" type="backwardHook" />
                </event>
                <event value="/16">
                    <note pitch="E5"/>
                    <beam number="1" type="begin" />
                    <beam number="2" type="forwardHook" />
                </event>
                <event value="/8">
                    <note pitch="F5"/>
                    <beam number="1" type="continue" />
                </event>
                <event value="/16">
                    <note pitch="E5"/>
                    <beam number="1" type="end" />
                    <beam number="2" type="backwardHook" />
                </event>
                <event value="/16">
                    <note pitch="G5"/>
                    <beam number="1" type="begin" />
                    <beam number="2" type="forwardHook" />
                </event>
                <event value="/8">
                    <note pitch="A5"/>                    
                    <beam number="1" type="continue" />
                </event>
                <event value="/16">
                    <note pitch="G5"/>
                    <beam number="1" type="end" />
                    <beam number="2" type="backwardHook" />
                </event>
                <event value="/4">
                    <note pitch="C6"/>
                </event>
            </sequence>
        </measure>
    </part>
</mnx-common>

MNX by Example - Slurs: slurs

<mnx-common>
    ...
    <part>
        ...
        <measure>
            ....
            <sequence> /* sequence.voice="1" by default */
                <event value="/4"> /* no event.id attributes */
                    <note pitch="C5"/>
                    <slur side="up" target="e4" /> /* e4 is the 4th event in this this part:measure:voice */
                </event>
                <event value="/4">
                    <note pitch="D5"/>
                </event>
                <event value="/4">
                    <note pitch="E5"/>
                </event>
                <event value="/4">
                    <note pitch="C5"/>
                </event>
            </sequence>
        </measure>
        <measure>
            <sequence>  /* again sequence.voice="1" by default */
                <event value="/4">
                    <note pitch="G4"/>
                    <slur side="down" target="e4" /> /* e4 is the 4th event in this part:measure:voice */
                </event>
                <event value="/4">
                    <note pitch="A4"/>
                </event>
                <event value="/4">
                    <note pitch="G4"/>
                </event>
                <event value="/4">
                    <note pitch="E4"/>
                </event>
            </sequence>
        </measure>
    </part>
</mnx-common>

MNX by Example - Slurs (for chords): slurchords

<mnx-common>
    ...
    <part>
        ...
        <measure>
            ...
            <sequence>
                <event value="/4"> /* no event id attributes */
                    <slur side="up" target="e4" /> /* e4 is the 4th event in this part:measure:voice */
                    /* notes in top-bottom order */
                    <note pitch="E5"/>
                    <note pitch="C5"/>
                </event>
                <event value="/4">
                    <note pitch="F5"/>
                    <note pitch="D5"/>
                </event>
                <event value="/4">
                    <note pitch="G5"/>
                    <note pitch="E5"/>
                </event>
                <event value="/4">
                    <note pitch="A5"/>
                    <note pitch="F5"/>
                </event>
            </sequence>
        </measure>
    </part>
</mnx-common>

MNX by Example - Slurs (targeting specific notes): slurnotes

<mnx-common>
    ...
    <part>
        ...
        <measure>
            ...
            <sequence>
                <event value="/2"> /* no event.id attributes */                
                    /* notes in top-bottom order */
                    <note pitch="G5"/>  /* no note.id attributes */
                    <note pitch="E5"/>
                    <note pitch="C5"/>
                    <slur side="down" start-note="n1" target="e2:n1"/>
                    <slur side="up" start-note="n2" target="e2:n2" />
                    <slur side="up" start-note="n3" target="e2:n3" />
                </event>
                <event value="/2"> /* no event.id attributes */
                    /* notes in top-bottom order */
                    <note pitch="F5" /> /* no note.id attributes */
                    <note pitch="D5" />
                    <note pitch="B4" />
                </event>
            </sequence>
        </measure>
    </part>
</mnx-common>

In addition to the special strings "incoming" and "outgoing", the Draft Spec allows the slur.location attribute to be a "measure location" , so the current MNX by Example - Slurs (incomplete slurs): slurincomplete

<mnx-common>
    ...
    <part>
        ...
        <measure>
            ...
            <sequence>
                <event value="/2">
                    <note pitch="C5"/>
                </event>
                <event value="/2">
                    <note pitch="D5"/>
                    <slur side="up" location="outgoing"/>
                </event>
            </sequence>
        </measure>
    </part>
</mnx-common>

could be complemented by a second example: slurincomplete2

<mnx-common>
    ...
    <part>
        ...
        <measure>
            ...
            <sequence>
                <event value="/2">
                    <note pitch="C5"/>
                    <slur side="up" location="1/4"/>
                </event>
                <event value="/2">
                    <note pitch="D5"/>
                    <slur side="up" location="3/4"/>
                </event>
            </sequence>
        </measure>
    </part>
</mnx-common>
mogenslundholm commented 4 years ago

But isn't slur coupled to a note and should be inside <note> ... </note>?

PS: Is legato and slur the same?

clnoel commented 4 years ago

@mogenslundholm Slurs are usually connected to a "stem", which could be a single note but could also be a chord, which is why slurs are currently considered to belong to an event.

Slurs are usually played back similarly to legato on piano, at least for all the notes under the slur (not counting the last note, which should be played normally). They have other uses for other instruments, including sung phrasing (where to not take a breath), string instrument bowing, and wind instrument tonguing.

adrianholovaty commented 4 years ago

Thanks for all the comments! We've decided to remove <beamed> as a structural element, and pull request #194 implements this.

Eventually we'll need to find a place to encode beaming data, in more of a presentational way, but we're kicking that can down the road for now.

Once we get to that: It would be nice to be able to encode high-level rules, such as "always beam eighth notes together, but never beam across the middle of a bar in 4/4 time." Of course there would also need to be a way to override this for a particular <event>.