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
687 stars 185 forks source link

Support for @head.color and @headfill.color #393

Closed craigsapp closed 7 years ago

craigsapp commented 7 years ago

When adding @color to a note, it will also affect any verses attached to the note. Not necessarily a problem, but in some cases also coloring the verses attached to a note will be less desirable.

Example:

http://verovio.humdrum.org/?file=jrp:Ock1010d&filter=myank%20-m1-4|dissonant%20-cetext

screen shot 2016-12-24 at 18 25 59
<?xml version="1.0" encoding="UTF-8"?>
<?xml-model href="http://music-encoding.org/schema/3.0.0/mei-all.rng" type="application/xml" schematypens="http://relaxng.org/ns/structure/1.0"?>
<?xml-model href="http://music-encoding.org/schema/3.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="3.0.0">
    <meiHead>
        <fileDesc>
            <titleStmt>
                <title>Sanctus</title>
                <respStmt>
                    <persName role="Composer">Ockeghem, Johannes</persName>
                    <persName role="digital editor">Jesse Rodin</persName>
                </respStmt>
            </titleStmt>
            <pubStmt>
                <date>2016-12-24 18:19:27</date>
            </pubStmt>
        </fileDesc>
        <encodingDesc>
            <projectDesc>
                <p>Transcoded from Humdrum with Verovio version 0.9.13-dev-ebcf862-dirty</p>
                <p>Encoded by: Will Watson 30/01/2012</p>
            </projectDesc>
        </encodingDesc>
    </meiHead>
    <music>
        <body>
            <mdiv>
                <score>
                    <scoreDef xml:id="scoredef-000000113756425">
                        <staffGrp xml:id="m-000000168107546" symbol="bracket">
                            <staffDef xml:id="staffdef-000000064355913" clef.shape="G" clef.line="2" key.sig="0" meter.count="3" meter.unit="1" n="1" lines="5" />
                            <staffDef xml:id="staffdef-000000015199249" clef.shape="G" clef.line="2" clef.dis="8" clef.dis.place="below" key.sig="0" meter.count="3" meter.unit="1" n="2" lines="5" />
                            <staffDef xml:id="staffdef-000000168963069" clef.shape="G" clef.line="2" clef.dis="8" clef.dis.place="below" key.sig="0" meter.count="3" meter.unit="1" n="3" lines="5" />
                            <staffDef xml:id="staffdef-000000070652140" clef.shape="F" clef.line="4" key.sig="0" meter.count="3" meter.unit="1" n="4" lines="5" />
                        </staffGrp>
                    </scoreDef>
                    <section xml:id="section-000000129908205">
                        <measure xml:id="measure-L16" n="1">
                            <staff xml:id="staff-L16F7" n="1">
                                <layer xml:id="layer-L16F7" n="1">
                                    <note xml:id="note-L19F7" dots="1" dur="breve" oct="4" pname="e" accid.ges="n" />
                                </layer>
                            </staff>
                            <staff xml:id="staff-L16F5" n="2">
                                <layer xml:id="layer-L16F5" n="1">
                                    <note xml:id="note-L19F5" dots="1" dur="1" oct="3" pname="b" accid.ges="n" />
                                    <note xml:id="note-L21F5" dur="2" oct="3" pname="a" accid.ges="n" color="#33bb00" stem.dir="up">
                                        <verse xml:id="verse-L21F6" n="1">
                                            <syl xml:id="syl-L21F6">ed</syl>
                                        </verse>
                                    </note>
                                    <note xml:id="note-L23F5" dur="1" oct="4" pname="c" accid.ges="n" />
                                </layer>
                            </staff>
                            <staff xml:id="staff-L16F3" n="3">
                                <layer xml:id="layer-L16F3" n="1">
                                    <note xml:id="note-L19F3" dur="1" oct="3" pname="e" accid.ges="n" />
                                    <note xml:id="note-L20F3" dots="1" dur="2" oct="3" pname="g" accid.ges="n" stem.dir="up" />
                                    <note xml:id="note-L22F3" dur="4" oct="3" pname="f" accid.ges="n" color="#33bb00" stem.dir="up">
                                        <verse xml:id="verse-L22F4" n="1">
                                            <syl xml:id="syl-L22F4">ed</syl>
                                        </verse>
                                    </note>
                                    <note xml:id="note-L23F3" dur="1" oct="3" pname="a" accid.ges="n" />
                                </layer>
                            </staff>
                            <staff xml:id="staff-L16F1" n="4">
                                <layer xml:id="layer-L16F1" n="1">
                                    <note xml:id="note-L19F1" dots="1" dur="1" oct="3" pname="e" accid.ges="n" />
                                    <note xml:id="note-L21F1" dur="2" oct="2" pname="a" accid.ges="n" color="#33bb00" stem.dir="up">
                                        <verse xml:id="verse-L21F2" n="1">
                                            <syl xml:id="syl-L21F2">d7</syl>
                                        </verse>
                                    </note>
                                    <note xml:id="note-L23F1" dur="1" oct="2" pname="a" accid.ges="n" />
                                </layer>
                            </staff>
                            <tempo xml:id="tempo-000000174768550" staff="1" tstamp="1.000000">Sanctus</tempo>
                            <tie xml:id="tie-000000071444851" startid="#note-L19F7" endid="#note-L25F7" />
                            <tie xml:id="tie-000000032735889" startid="#note-L23F5" endid="#note-L25F5" />
                        </measure>
                        <measure xml:id="measure-L24" n="2">
                            <staff xml:id="staff-L24F7" n="1">
                                <layer xml:id="layer-L24F7" n="1">
                                    <note xml:id="note-L25F7" dur="1" oct="4" pname="e" accid.ges="n" />
                                    <note xml:id="note-L26F7" dots="1" dur="2" oct="4" pname="d" accid.ges="n" stem.dir="up" />
                                    <note xml:id="note-L27F7" dur="4" oct="4" pname="c" accid.ges="n" color="#33bb00" stem.dir="up">
                                        <verse xml:id="verse-L27F8" n="1">
                                            <syl xml:id="syl-L27F8">scd</syl>
                                        </verse>
                                    </note>
                                    <note xml:id="note-L28F7" dur="1" oct="3" pname="a" accid.ges="n" />
                                </layer>
                            </staff>
                            <staff xml:id="staff-L24F5" n="2">
                                <layer xml:id="layer-L24F5" n="1">
                                    <note xml:id="note-L25F5" dur="1" oct="4" pname="c" accid.ges="n" />
                                    <note xml:id="note-L26F5" dur="breve" oct="4" pname="d" accid.ges="n" />
                                </layer>
                            </staff>
                            <staff xml:id="staff-L24F3" n="3">
                                <layer xml:id="layer-L24F3" n="1">
                                    <note xml:id="note-L25F3" dur="1" oct="3" pname="g" accid.ges="n" />
                                    <note xml:id="note-L26F3" dur="breve" oct="3" pname="d" accid.ges="n" />
                                </layer>
                            </staff>
                            <staff xml:id="staff-L24F1" n="4">
                                <layer xml:id="layer-L24F1" n="1">
                                    <note xml:id="note-L25F1" dur="1" oct="3" pname="e" accid.ges="n" />
                                    <note xml:id="note-L26F1" dur="breve" oct="3" pname="f" accid.ges="n" />
                                </layer>
                            </staff>
                        </measure>
                        <measure xml:id="measure-L29" n="3">
                            <staff xml:id="staff-L29F7" n="1">
                                <layer xml:id="layer-L29F7" n="1">
                                    <note xml:id="note-L30F7" dur="1" oct="3" pname="g" accid.ges="n" />
                                    <rest xml:id="rest-L31F7" dur="1" />
                                    <note xml:id="note-L32F7" dur="1" oct="4" pname="g" accid.ges="n" />
                                </layer>
                            </staff>
                            <staff xml:id="staff-L29F5" n="2">
                                <layer xml:id="layer-L29F5" n="1">
                                    <note xml:id="note-L30F5" dots="1" dur="breve" oct="4" pname="e" accid.ges="n" />
                                </layer>
                            </staff>
                            <staff xml:id="staff-L29F3" n="3">
                                <layer xml:id="layer-L29F3" n="1">
                                    <note xml:id="note-L30F3" dur="breve" oct="3" pname="b" accid.ges="n" />
                                    <note xml:id="note-L32F3" dur="1" oct="4" pname="c" accid.ges="n" />
                                </layer>
                            </staff>
                            <staff xml:id="staff-L29F1" n="4">
                                <layer xml:id="layer-L29F1" n="1">
                                    <note xml:id="note-L30F1" dur="breve" oct="3" pname="e" accid.ges="n" />
                                    <note xml:id="note-L32F1" dur="1" oct="3" pname="c" accid.ges="n" />
                                </layer>
                            </staff>
                            <tie xml:id="tie-000000163157393" startid="#note-L30F5" endid="#note-L34F5" />
                        </measure>
                        <measure xml:id="measure-L33" n="4">
                            <staff xml:id="staff-L33F7" n="1">
                                <layer xml:id="layer-L33F7" n="1">
                                    <note xml:id="note-L34F7" dur="1" oct="4" pname="g" accid.ges="n" />
                                    <note xml:id="note-L35F7" dots="1" dur="1" oct="4" pname="a" accid.ges="n" />
                                    <note xml:id="note-L39F7" dur="2" oct="4" pname="g" accid.ges="n" stem.dir="up" />
                                </layer>
                            </staff>
                            <staff xml:id="staff-L33F5" n="2">
                                <layer xml:id="layer-L33F5" n="1">
                                    <note xml:id="note-L34F5" dur="1" oct="4" pname="e" accid.ges="n" />
                                    <note xml:id="note-L35F5" dots="1" dur="2" oct="4" pname="d" accid.ges="n" stem.dir="down" />
                                    <note xml:id="note-L37F5" dur="4" oct="4" pname="c" accid.ges="n" stem.dir="down" />
                                    <note xml:id="note-L38F5" dur="1" oct="3" pname="a" accid.ges="n" />
                                </layer>
                            </staff>
                            <staff xml:id="staff-L33F3" n="3">
                                <layer xml:id="layer-L33F3" n="1">
                                    <note xml:id="note-L34F3" dur="1" oct="3" pname="b" accid.ges="n" />
                                    <note xml:id="note-L35F3" dur="2" oct="3" pname="a" accid.ges="n" stem.dir="up" />
                                    <note xml:id="note-L36F3" dur="1" oct="4" pname="c" accid.ges="n" color="#33bb00">
                                        <verse xml:id="verse-L36F4" n="1">
                                            <syl xml:id="syl-L36F4">d2</syl>
                                        </verse>
                                    </note>
                                    <note xml:id="note-L39F3" dur="4" oct="3" pname="b" accid.ges="n" color="#33bb00" stem.dir="down">
                                        <verse xml:id="verse-L39F4" n="1">
                                            <syl xml:id="syl-L39F4">d2</syl>
                                        </verse>
                                    </note>
                                    <note xml:id="note-L40F3" dur="4" oct="3" pname="a" accid.ges="n" stem.dir="up" />
                                </layer>
                            </staff>
                            <staff xml:id="staff-L33F1" n="4">
                                <layer xml:id="layer-L33F1" n="1">
                                    <note xml:id="note-L34F1" dur="1" oct="3" pname="e" accid.ges="n" />
                                    <note xml:id="note-L35F1" dur="breve" oct="3" pname="f" accid.ges="n" />
                                </layer>
                            </staff>
                        </measure>
                    </section>
                </score>
            </mdiv>
        </body>
    </music>
</mei>
craigsapp commented 7 years ago

Also, somewhat related: @color on a note in MEI results in @fill and @stroke in the SVG. However, the highlighting system used with MIDI playback in the MEI Viewer is also writing to @fill and @stroke which causes the original color to be removed after the note is played with the MIDI player.

Here is the code for highlighting in the Verovio MEI viewer:

                ids.forEach(function(noteid) {
                    if ($.inArray(noteid, elementsattime.notes) == -1) {
                        $("#" + noteid ).attr("fill", "#000");
                        $("#" + noteid ).attr("stroke", "#000");
                        //$("#" + noteid ).removeClassSVG("highlighted");
                    }
                });
                ids = elementsattime.notes;
                ids.forEach(function(noteid) {
                    if ($.inArray(noteid, elementsattime.notes) != -1) {
                    //console.log(noteid);
                        $("#" + noteid ).attr("fill", "#c00");
                        $("#" + noteid ).attr("stroke", "#c00");;
                        //$("#" + noteid ).addClassSVG("highlighted");
                    }
                });

I tried .addClassSVG and it wasn't working (maybe that is why you commented it out). Perhaps that is some plugin for jQuery? In any case I changed the parallel code for VHV to use CSS highlighting using raw JavaScript:

                    //$("#" + noteid ).attr("fill", "#000");
                    //$("#" + noteid ).attr("stroke", "#000"); 
                    // $("#" + noteid ).removeClassSVG("highlighted"); 

                    var element = document.querySelector("#" + noteid);
                    if (element) {
                        var classes = element.getAttribute("class");
                        var classlist = classes.split(" ");
                        var outclass = "";
                        for (var i=0; i<classlist.length; i++) {
                            if (classlist[i] == "highlight") {
                                continue;
                            }
                            outclass += " " + classlist[i];
                        }
                        element.setAttribute("class", outclass);
                    }

and turn on highlighting:

                    // $("#" + noteid ).attr("fill", "#c00");
                    // $("#" + noteid ).attr("stroke", "#c00");; 
                    // $("#" + noteid ).addClassSVG("highlighted"); 

                    var element = document.querySelector("#" + noteid);
                    if (element) {
                        var classes = element.getAttribute("class");
                        var classlist = classes.split(" ");
                        var outclass = "";
                        for (var i=0; i<classlist.length; i++) {
                            if (classlist[i] == "highlight") {
                                continue;
                            }
                            outclass += " " + classlist[i];
                        }
                        outclass += " highlight";
                        element.setAttribute("class", outclass);
                                           }

Here is the CSS entry for highlighting:

.highlight {
    color: #c00;
    fill: #c00;
    stroke: #c00;
}

Example implementation:

http://verovio.humdrum.org/?file=jrp:Ort3001&filter=dissonant%20-cemxhm

Note the red highlighting of the currently playing MIDI notes, with the green highlighting for the dissonant notes preserved after the notes have played.

screen shot 2016-12-24 at 19 09 43
lpugin commented 7 years ago

This is the expected behaviour. When an element has a @color in MEI it is applied to all its children unless redefined. For the player, this need to be solved in the highlighting process. One option is to "cache" the original color in order to restore afterwards.

craigsapp commented 7 years ago

For the player, this need to be solved in the highlighting process. One option is to "cache" the original color in order to restore afterwards.

Using CSS in the MIDI player solves the original color problem (without caching).

craigsapp commented 7 years ago

A ha, here are two note attributes which can be used to color the notehead (and not the lyrics, accidentals or articulations):

http://music-encoding.org/documentation/3.0.0/note

@head.color (optional) Captures the overall color of a notehead. Value conforms to data.COLOR . att.noteheads @head.fillcolor (optional) Captures the fill color of a notehead if different from the overall note color. Value conforms to data.COLOR . att.noteheads

[@pe-ro: what are the differences between these two attributes?]

So it would be nice to allow for these attributes to be processed in the SVG rendering.

Test MEI data:

<?xml version="1.0" encoding="UTF-8"?>
<?xml-model href="http://music-encoding.org/schema/3.0.0/mei-all.rng" type="application/xml" schematypens="http://relaxng.org/ns/structure/1.0"?>
<?xml-model href="http://music-encoding.org/schema/3.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="3.0.0">
    <meiHead>
        <fileDesc>
            <titleStmt>
                <title />
            </titleStmt>
            <pubStmt>
                <date>2016-12-26 21:20:41</date>
            </pubStmt>
        </fileDesc>
        <encodingDesc>
            <projectDesc>
                <p>Transcoded from Humdrum with Verovio version 0.9.13-dev-a3ff6a7-dirty</p>
            </projectDesc>
        </encodingDesc>
    </meiHead>
    <music>
        <body>
            <mdiv>
                <score>
                    <scoreDef xml:id="scoredef-000000073652939">
                        <staffGrp xml:id="m-000000191112967">
                            <staffDef xml:id="staffdef-000000058631028" clef.shape="G" clef.line="2" meter.count="3" meter.unit="4" n="1" lines="5" />
                        </staffGrp>
                    </scoreDef>
                    <section xml:id="section-000000008879125">
                        <measure xml:id="measure-L3" n="1" right="end">
                            <staff xml:id="staff-L3F1" n="1">
                                <layer xml:id="layer-L3F1" n="1">
                                    <note xml:id="note-L4F1" dur="4" oct="4" pname="c" accid.ges="n" head.color="red" />
                                    <note xml:id="note-L5F1" dur="4" oct="4" pname="d" accid.ges="n" color="orange" />
                                    <note xml:id="note-L6F1" dur="4" oct="4" pname="e" accid.ges="n" head.fillcolor="blue" />
                                </layer>
                            </staff>
                        </measure>
                    </section>
                </score>
            </mdiv>
        </body>
    </music>
</mei>

Currently only note@color is processed:

screen shot 2016-12-26 at 21 27 18
lpugin commented 7 years ago

At some point I would like to have a separate SVG <g> for the note head. Then we will be able to easily implement this.

pe-ro commented 7 years ago

@craigsapp --

@head.color (optional) Captures the overall color of a notehead. Value conforms to data.COLOR . att.noteheads @head.fillcolor (optional) Captures the fill color of a notehead if different from the overall note color. Value conforms to data.COLOR . att.noteheads [@pe-ro: what are the differences between these two attributes?]

These attributes address the situation (admittedly rare) where the note head is stroked in one color but filled in another. When only @note.color is encoded, then the stroke and fill colors are the same. In other words, @head.fillcolor defaults to @head.color in most cases.

There's also a @head.fill attribute that captures the fill "pattern"; that is, if the note is partially filled. If @head.fillcolor is present, but @head.fill isn't, then @head.fill defaults to "solid". Other values are intended to capture partially colored notes found in mensural notation.

craigsapp commented 7 years ago

These attributes address the situation (admittedly rare) where the note head is stroked in one color but filled in another.

Like this?:

screen shot 2016-12-27 at 12 47 11 pm

This is not possible currently with verovio, as the font glyphs (such as for the noteheads) are pure outlines, and stroking would thicken the notehead and perhaps causing problems with the stems. SVG 2 should allow for strokes inside of a fill boundary: http://stackoverflow.com/questions/7241393/can-you-control-how-an-svgs-stroke-width-is-drawn

Here is a case where I filled differently than the stroke color:

screen shot 2016-12-27 at 1 33 04 pm

This was visually better than not having the dark stroke around the noteheads, since otherwise lighter colored noteheads would be more difficult to see (particularly yellow).

pe-ro commented 7 years ago

Like this?:

Yep.

This is not possible currently with verovio, as the font glyphs (such as for the noteheads) are pure outlines, ...

Ok, then how about if I rephrase -- Can the font glyphs/outlines be in one color, but filled with a different color?

This was visually better than not having the dark stroke around the noteheads, since otherwise lighter colored noteheads would be more difficult to see (particularly yellow).

That's what I was trying to support.

craigsapp commented 7 years ago

Ok, then how about if I rephrase -- Can the font glyphs/outlines be in one color, but filled with a different color?

SCORE's font system uses strokes and fills, coming from the days it was implemented for output to a pen plotter. Here is a SCORE treble clef split into its individual components:

screen shot 2016-12-27 at 4 23 10 pm

When you print from SCORE, you can select the stroke width:

Normally 4 pixel wide stroke:

screen shot 2016-12-27 at 4 28 44 pm

Here is a 20 pixel wide stroke:

screen shot 2016-12-27 at 4 27 33 pm

(The text is the standard Adobe font syle, where there is no stroking of the outline)

Regular Adobe-type fonts are filled regions only with no stroke at the edges. The problem is if you stroke the edge, half of the stroke will be outside the fill area, and half will be inside the fill area. This will make a font glyph bolder. And most likely for noteheads, this would cause a bulge on the other side of a stem that was otherwise flush with the notehead (unless you also thicken the stem at the same time).

Ok, then how about if I rephrase -- Can the font glyphs/outlines be in one color, but filled with a different color?

Not currently practical, but SVG version 2 will/does solves this problem by allowing a stroke to only be visible in the fill area with the @stroke-alignment="inner".