rism-digital / verovio

🎵 Music notation engraving library for MEI with MusicXML and Humdrum support and various toolkits (JavaScript, Python)
https://www.verovio.org
GNU Lesser General Public License v3.0
660 stars 181 forks source link

Harm is incorrect placed after dotted notes #990

Closed peterkorgaard closed 5 years ago

peterkorgaard commented 5 years ago

This issue is identified using version 2.0.0.

The example below has added harm on each beat in both measures - tstamp 1, 2, 3 and 4. When harm follows a dotted note it displays on an incorrect location.

image

<?xml version="1.0" encoding="UTF-8"?>
<?xml-model href="http://music-encoding.org/schema/4.0.0/mei-all.rng" type="application/xml" schematypens="http://relaxng.org/ns/structure/1.0"?>
<mei xmlns="http://www.music-encoding.org/ns/mei" meiversion="4.0.0">
    <meiHead></meiHead>
    <music>
        <body>
            <mdiv>
                <score>
                    <scoreDef xml:id="14c9c719-63b7-4fef-8572-7e8ffbe8f2be" key.sig="0" meter.count="4" meter.unit="4">
                        <staffGrp xml:id="2b7bea05-c601-4270-b372-ae08112dfdb6">
                            <staffDef xml:id="b6cb97ff-523f-469c-a808-b6488d40fd52" clef.shape="G" clef.line="2" n="1" lines="5"/>
                        </staffGrp>
                    </scoreDef>
                    <section>
                        <section xml:id="78706929-575a-4699-b609-3c04055e76b4">
                            <measure xml:id="a06baf34-4fde-4f4a-9529-1b34e547d24a" right="single">
                                <staff xml:id="407bd65a-d86d-49fb-8c53-3ee65c8b21b3" n="1">
                                    <layer xml:id="3015a629-ab40-42b9-a157-fced592f25d5" n="1">
                                        <note xml:id="60efb493-c459-4fe9-a0eb-a10bb43fc2bf" dur="4" oct="4" pname="b"></note>
                                        <beam xml:id="a49800bb-7cd9-4278-8c02-67b403050b8e">
                                            <note xml:id="f8bd1f2e-7518-4d4b-b33f-3e2a8dbeee39" dur="16" oct="4" pname="b"></note>
                                            <note xml:id="3aa113ad-26a4-4a55-8ec7-155965fc3c14" dur="16" oct="4" pname="b"></note>
                                            <note xml:id="6c0e6742-6e3e-41b0-b903-b69de162d318" dur="16" oct="4" pname="b"></note>
                                            <note xml:id="43c92bde-141d-4fda-9aed-e513037916e2" dur="16" oct="4" pname="b"></note>
                                        </beam>
                                        <beam xml:id="61d56641-8328-40e2-a71c-7454f467e9d1">
                                            <note xml:id="6bfabcfc-1e9f-49af-a1f2-7cc445ee4fac" dots="1" dur="4" oct="4" pname="b"></note>
                                            <note xml:id="bee9e929-bd4a-40b5-934a-87d58fa865f7" dur="16" oct="4" pname="b"></note>
                                        </beam>
                                        <note xml:id="7da2d638-6896-42c0-b698-0a03edcc4400" dur="4" oct="4" pname="b"></note>
                                    </layer>
                                </staff>
                                <harm place="above" staff="1" tstamp="1">
                                    <rend fontsize="small">1</rend>
                                </harm>
                                <harm place="above" staff="1" tstamp="2">
                                    <rend fontsize="small">2</rend>
                                </harm>
                                <harm place="above" staff="1" tstamp="3">
                                    <rend fontsize="small">3</rend>
                                </harm>
                                <harm place="above" staff="1" tstamp="4">
                                    <rend fontsize="small">4</rend>
                                </harm>
                            </measure>
                            <measure xml:id="3f40752d-7d58-44ce-886f-d5df5d870be3" right="dbl">
                                <staff xml:id="18876fa0-90b9-46b0-b1e0-36aa097220fd" n="1">
                                    <layer xml:id="afe9aea7-f9c2-4c35-afc9-434159cbb358" n="1">
                                        <beam xml:id="5cf8fa71-e867-42ba-bf3f-bbbbf2850b46">
                                            <note xml:id="dfc13a7e-bdf1-4fd0-b6ed-427ef562c52b" dots="1" dur="4" oct="4" pname="b"></note>
                                            <note xml:id="baf4284f-7474-43cb-8156-88f868f6c778" dur="16" oct="4" pname="b"></note>
                                        </beam>
                                        <rest xml:id="c263352c-baac-4cb2-9c28-d9a7b7633aef" dur="4"/>
                                        <note xml:id="6d8660eb-12ff-4cb6-8e0f-ab6c88fd8183" dur="2" oct="4" pname="b"></note>
                                    </layer>
                                </staff>
                                <harm place="above" staff="1" tstamp="1">
                                    <rend fontsize="small">1</rend>
                                </harm>
                                <harm place="above" staff="1" tstamp="2">
                                    <rend fontsize="small">2</rend>
                                </harm>
                                <harm place="above" staff="1" tstamp="3">
                                    <rend fontsize="small">3</rend>
                                </harm>
                                <harm place="above" staff="1" tstamp="4">
                                    <rend fontsize="small">4</rend>
                                </harm>
                            </measure>
                        </section>
                    </section>
                </score>
            </mdiv>
        </body>
    </music>
</mei>
craigsapp commented 5 years ago

Strange. When I encode it, the placement is correct:

screen shot 2019-01-30 at 11 52 01 am

I am getting the same problem as you when trying to render your encoding.

My encoding is below, which looks similar to yours (and changing things do not break it so far, such as moving meter.units to scoreDef, or removing the rend on the harm data):

<?xml version="1.0" encoding="UTF-8"?>
<?xml-model href="http://music-encoding.org/schema/4.0.0/mei-all.rng" type="application/xml" schematypens="http://relaxng.org/ns/structure/1.0"?>
<?xml-model href="http://music-encoding.org/schema/4.0.0/mei-all.rng" type="application/xml" schematypens="http://purl.oclc.org/dsdl/schematron"?>
<mei xmlns="http://www.music-encoding.org/ns/mei" meiversion="4.0.0">
    <meiHead>
        <fileDesc>
            <titleStmt>
                <title />
            </titleStmt>
            <pubStmt />
        </fileDesc>
        <encodingDesc>
            <appInfo>
                <application isodate="2019-01-30T11:51:00" version="2.1.0-dev-56b5535-dirty">
                    <name>Verovio</name>
                    <p>Transcoded from Humdrum</p>
                </application>
            </appInfo>
        </encodingDesc>
        <workList>
            <work>
                <title />
            </work>
        </workList>
    </meiHead>
    <music>
        <body>
            <mdiv xml:id="mdiv-0000000518650985">
                <score xml:id="score-0000000375403176">
                    <scoreDef xml:id="scoredef-0000000310725454">
                        <staffGrp xml:id="staffgrp-0000000941074021">
                            <staffDef xml:id="staffdef-0000000677658184" clef.shape="G" clef.line="2" meter.count="4" meter.unit="4" n="1" lines="5">
                                <label xml:id="label-0000001077812367" />
                            </staffDef>
                        </staffGrp>
                    </scoreDef>
                    <section xml:id="section-L1F1">
                        <measure xml:id="measure-L4">
                            <staff xml:id="staff-L4F1N1" n="1">
                                <layer xml:id="layer-L4F1N1" n="1">
                                    <note xml:id="note-L5F1" dur="4" oct="4" pname="b" accid.ges="n" />
                                    <beam xml:id="beam-L6F1-L9F1">
                                        <note xml:id="note-L6F1" dur="16" oct="4" pname="b" accid.ges="n" />
                                        <note xml:id="note-L7F1" dur="16" oct="4" pname="b" accid.ges="n" />
                                        <note xml:id="note-L8F1" dur="16" oct="4" pname="b" accid.ges="n" />
                                        <note xml:id="note-L9F1" dur="16" oct="4" pname="b" accid.ges="n" />
                                    </beam>
                                    <beam xml:id="beam-L10F1-L11F1">
                                        <note xml:id="note-L10F1" dots="1" dur="8" oct="4" pname="b" accid.ges="n" />
                                        <note xml:id="note-L11F1" dur="16" oct="4" pname="b" accid.ges="n" />
                                    </beam>
                                    <note xml:id="note-L12F1" dur="4" oct="4" pname="b" accid.ges="n" />
                                </layer>
                            </staff>
                            <harm xml:id="harm-L5F2" staff="1" tstamp="1.000000">1</harm>
                            <harm xml:id="harm-L6F2" staff="1" tstamp="2.000000">2</harm>
                            <harm xml:id="harm-L10F2" staff="1" tstamp="3.000000">3</harm>
                            <harm xml:id="harm-L12F2" staff="1" tstamp="4.000000">4</harm>
                        </measure>
                        <measure xml:id="measure-L13" right="dbl">
                            <staff xml:id="staff-L13F1N1" n="1">
                                <layer xml:id="layer-L13F1N1" n="1">
                                    <beam xml:id="beam-L14F1-L15F1">
                                        <note xml:id="note-L14F1" dots="1" dur="8" oct="4" pname="b" accid.ges="n" />
                                        <note xml:id="note-L15F1" dur="16" oct="4" pname="b" accid.ges="n" />
                                    </beam>
                                    <rest xml:id="rest-L16F1" dur="4" />
                                    <note xml:id="note-L17F1" dur="2" oct="4" pname="b" accid.ges="n" />
                                </layer>
                            </staff>
                            <harm xml:id="harm-L14F2" staff="1" tstamp="1.000000">1</harm>
                            <harm xml:id="harm-L16F2" staff="1" tstamp="2.000000">2</harm>
                            <harm xml:id="harm-L17F2" staff="1" tstamp="3.000000">3</harm>
                            <harm xml:id="harm-L18F2" staff="1" tstamp="4.000000">4</harm>
                        </measure>
                    </section>
                </score>
            </mdiv>
        </body>
    </music>
</mei>
peterkorgaard commented 5 years ago

I rendered your code and everything looks fine for me too. So, it looks like I need to find the key difference between the two encodings. It is a little peculiar, though. I have never experienced this before.

Thanks. I'll give an update when I'm wiser.

musicEnfanthen commented 5 years ago

@peterkorgaard The duration of your first note in the second measure is "4" instead of "8".

<note xml:id="dfc13a7e-bdf1-4fd0-b6ed-427ef562c52b" dots="1" dur="4" oct="4" pname="b"></note>

Changing it to "8" should do the trick.

peterkorgaard commented 5 years ago

You're absolutely right. It's an obvious mistake, but I didn't see it because all the notes somehow rendered correctly. Thanks a lot. I'm very grateful.

musicEnfanthen commented 5 years ago

No worries, such things can happen a lot.

@lpugin Is there any mechanism to detect metrical incongruency automatically and then display, e.g., a warning?

craigsapp commented 5 years ago

Is there any mechanism to detect metrical incongruency automatically

That would be a useful feature, but is not yet available in verovio.

In verovio it should be implemented as some sort of warning that all <staff> elements of a <measure> do not have the same duration (and in addition, maybe check that all <layer> in a staff have the same duration). Here is an example of a measure that has an unequal duration between the staves (because I deleted one note in the bottom staff):

screen shot 2019-01-30 at 7 28 31 pm

Verovio will not print a warning message in such cases, but it would probably be useful to report that the measure is either under or over full (and it should be something that verovio is already aware of). If someone wanted the above rendering, then they should add a <space> (invisible rest) to indicate that is what they really want (and not a data error).

The problem is that there will be pickup measures and repeats that split full measures of music, so reporting an inconsistency with the prevailing meter would be beyond the scope of verovio. So in the case of a single staff (as in this case), it would be harder to detect the error, and beyond the scope of typesetting the music.

This would be more of a schematron feature for comparing the duration of the measure to the one expected by the time signature.

This can also be done currently by converting the MEI data into Humdrum and then summing the durations of each measure:

mei2hum file.mei | beat -s 

which produces this output:

**beatsum
*M4/4
4.75
=1
4.75
=
*-

or to see along with the original data:

mei2hum file.mei | beat -as 
**kern  **beatsum
*part1  *
*staff1 *
*clefG2 *
*M4/4   *M4/4
4b  4.75
16bL    .
16b .
16b .
16bJ    .
4.b .
16b .
4b  .
=1  =1
4.b 4.75
16b .
4r  .
2b  .
=   =
*-  *-

In this case both measures are 4.75 beats long instead of the expected 4.

Test data for the above rendering:

<?xml version="1.0" encoding="UTF-8"?>
<?xml-model href="http://music-encoding.org/schema/4.0.0/mei-all.rng" type="application/xml" schematypens="http://relaxng.org/ns/structure/1.0"?>
<?xml-model href="http://music-encoding.org/schema/4.0.0/mei-all.rng" type="application/xml" schematypens="http://purl.oclc.org/dsdl/schematron"?>
<mei xmlns="http://www.music-encoding.org/ns/mei" meiversion="4.0.0">
    <meiHead>
        <fileDesc>
            <titleStmt>
                <title />
            </titleStmt>
            <pubStmt />
        </fileDesc>
        <encodingDesc>
            <appInfo>
                <application isodate="2019-01-30T19:27:57" version="2.0.0-dev-bd2039c">
                    <name>Verovio</name>
                    <p>Transcoded from Humdrum</p>
                </application>
            </appInfo>
        </encodingDesc>
        <workDesc>
            <work>
                <titleStmt>
                    <title />
                </titleStmt>
            </work>
        </workDesc>
    </meiHead>
    <music>
        <body>
            <mdiv xml:id="mdiv-0000001190257153">
                <score xml:id="score-0000000841798666">
                    <scoreDef xml:id="scoredef-0000000454304434">
                        <staffGrp xml:id="staffgrp-0000001122750840" symbol="brace" bar.thru="true">
                            <label xml:id="label-0000001045322484" />
                            <staffDef xml:id="staffdef-0000001261663736" clef.shape="G" clef.line="2" meter.count="4" meter.unit="4" n="1" lines="5" />
                            <staffDef xml:id="staffdef-0000000540853942" clef.shape="F" clef.line="4" meter.count="4" meter.unit="4" n="2" lines="5" />
                        </staffGrp>
                    </scoreDef>
                    <section xml:id="section-L1F1">
                        <measure xml:id="measure-L3" n="1">
                            <staff xml:id="staff-L3F2N1" n="1">
                                <layer xml:id="layer-L3F2N1" n="1">
                                    <note xml:id="note-L4F2" dur="4" oct="4" pname="c" accid.ges="n" />
                                    <note xml:id="note-L5F2" dur="4" oct="4" pname="c" accid.ges="n" />
                                    <note xml:id="note-L6F2" dur="4" oct="4" pname="c" accid.ges="n" />
                                    <note xml:id="note-L7F2" dur="4" oct="4" pname="c" accid.ges="n" />
                                </layer>
                            </staff>
                            <staff xml:id="staff-L3F1N1" n="2">
                                <layer xml:id="layer-L3F1N1" n="1">
                                    <note xml:id="note-L4F1" dur="4" oct="3" pname="c" accid.ges="n" />
                                    <note xml:id="note-L5F1" dur="4" oct="3" pname="c" accid.ges="n" />
                                    <note xml:id="note-L6F1" dur="4" oct="3" pname="c" accid.ges="n" />
                                </layer>
                            </staff>
                        </measure>
                    </section>
                </score>
            </mdiv>
        </body>
    </music>
</mei>
lpugin commented 5 years ago

@musicEnfanthen not in Verovio. But maybe it can be done with Schematron as @craigsapp is suggesting it?

craigsapp commented 5 years ago

And/or you can write an XSLT to do it 😄

The basic algorithm is:

(1) use xpath to extra list of scoreDefs and measures (presuming measured music)

(2) use xpath to extract layers from the measures

(3) iterate through the layers accumulating durations from children elements

(4) compare the duration of all layers to each other and issue a warning if they do not all match. A variation would be to find the max duration for each staff and then compare them, if you want to allow underfull layers on a staff that is otherwise complete.

number 3 is the most complicated, having these complications:

(1) use @dur.ges when present instead of @dur. Use @dots.ges when present instead of @dots. (2) when in a tuplet, apply the tuplet@num and tuplet@numbase to scale the visual duration of the durational element inside the tuplet. Tuplets can be nested, so if you use xpath to select durational items in a layer (notes, rests, mRests, multiRests, spaces), you would need to trace back in the ancestors of the durational element until you hit the layer level to apply all tuplet modications to the duration. (3) mRest and multiRest take their duration from the prevailing time signature (so you can ignore them if you are searching for over/under filled measures). (4) the biggest complication for a generalized tool would be tupletSpan. These function like tuplets but behave like slurs, in that they are outside of the layer/staff and are floating elements at the measure level. In theory these should be rare, but they are still quite common in the sample encoding dataset (16 out of 86 files):

https://github.com/music-encoding/sample-encodings/tree/master/MEI_4.0/Music/Complete_examples

Beethoven_op.18.mei
Borodin_StringTrio_g.mei
Brahms_StringQuartet_Op51_No1.mei
Chopin_Etude_op.10_no.9.mei
Chopin_Mazurka.mei
Debussy_Golliwogg'sCakewalk.mei
Hummel_Concerto_for_trumpet.mei
Hummel_op.67_No.11.mei
Liszt_Four_little_pieces.mei
Mahler_Song.mei
Mozart_Quintett.mei
Mozart_Quintett_2013.mei
Schubert_Lindenbaum.mei
Schumann_Op.41.mei
Telemann_Concert.mei
Weber_Arie.mei

tupletSpan should only be needed (like beamSpan) when a tuplet group crosses the element hierarchy (such as across measures), so most (probably all) uses of the tupletSpan in the above files is not necessary. There are also numerical errors related to tupletspans in the above data caused by the musicxml2mei converting XSLT program. Having such a tool would help identify the locations of those errors for hand-correcting. (ideally the musicxml2mei converter would be fixed, and then re-run on the source encoding, but the source encodings are not part of the repository).