Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add conversion from mensural to CMN #3852

Open
wants to merge 81 commits into
base: develop
Choose a base branch
from
Open

Conversation

lpugin
Copy link
Contributor

@lpugin lpugin commented Nov 5, 2024

This PR introduces a new converter from mensural to CMN (supported by the CESR) together with some other improvements to the CMME importer and options for handling mensural notation.

Refactoring of the mensural notation cast-off

The cast-off of mensural notation has been re-written and makes Verovio more flexible. Previously, system and page begins would be inserted only at places where a barLine would occur at all voices. This was requiring barLine elements to be inserted, sometimes artificially, with still a limited number of possible break points.

With the implementation of this PR, system and page begins will be inserted wherever all voices align, meaning at any point where all voices have either a note or a rest.

Options

Duration equivalence

The new --duration-equivalence specifies the level that is considered equivalent when aligning different (ternary or binary) mensural divisions and is similar to the integer valor. Previously, the level was fixed to brevis, but it can now be changed to semibrevis or minima. The brevis remains the default level.

One of the purpose of the --duration-equivalence option is to control how mensural notation can be aligned with CMN. With minima, a mensural minima will be considered equivalent to a half-note (a duration of 2 in MEI). With semibrevis, a mensural duration of semibrevis will be considered equivalent to a whole-note (a duration of 1 in MEI). With a tempus imperfectum and a prolatio minor (e.g., all binary divisions), the level of equivalence has makes no difference. However, with ternary divisions (tempus perfectum and/or prolatio major), some tuplets will be needed for aligning whole-notes and semibrevis with a level of minima and for aligning minima and half-notes with a level of semibrevis, respectively

[example will be added to the reference book]

Mensural responsive view

The PR introduces a new option to make rendering of mensural notation more flexible. One issue with the dynamic rendering of mensural data is the cast off of containers, such as ligature or editorial markup. Adding system of page begins within ligature or editorial markup would require some restructuring of the data - keeping some notes of a ligature in one system and others to the next system. That is not the normal way layout is performed in Verovio since the principle is to preserve the MEI tree. The new option --mensural-responsive-view is a way to focus only on the editorial markup version selected and to flatten the ligature in order to allow for system and page begins to be inserted wherever needed within editorial markup or ligature.

Users should be aware that calling GetMEI with the option will return the simplified version of the data, and not the original one.

[example will be added to the reference book]

Adjustments to the CMME importer

The PR adds the conversion of minimal metadata from the CMME GeneralData element to the MEI header. It is:

  • The Title => title
  • The Section => title@type="subordinate"
  • The Composer => composer

The PR as the support for the CMME ColorChange by applying the color (non-black) to note@color and rest@color.

The PR also forces the use of the --duration-equivalence option to minima when importing a CMME file. A warning is shown if the option was previously different - which is the case by default. This could potentially have side effects for subsequent calls since it is not set back to its original value. One way to improve this would be to set the option to minima by default, but that can be done after the merge of the PR and some additional testing with existing mensural data.

Finally the PR corrects the handling of the proportion in the CMME importer. They were previously applied to the data during the conversion, but this was wrong because CMME works the other way around. That is, the proportion is not coded in the note value but applied by the CMME processor - this is also why proportions are also used a tempo changes in some CMME data.

Converter from Mensural to CMN

The conversion is triggered by the --mensural-to-cmn option, which replaces --mensural-to-measure.

Meter signatures

Mensur signs are converted into CMN meter signatures of 3/2, 4/2 or 6/2, with 3/2 for tempus perfectum and prolatio major, and 4/2 for tempus imperfectum and prolatio minor. 6/2 is applied for the other two combinations.

The meter signature is arbitrarily determined by the top voice when voices have conflicting meter signatures. Also, a meter signature change is introduced when all voices have a mensur sign at the position and that the resulting meter signature is actually different from the previous one.

The mensur signs are not preserved as such. However, a MEI <dir> is inserted with a textual transcription, with:

  • A sign (C or O) with r for a reversed orientation
  • A dash (|)
  • A dot (.)
  • A figure (x) or a proportion (x/x) for @num w/o @numbase respectively.

The <dir> is placed with tstamp="0.0" when occurring at the beginning of a measure, or with a @startid of the previous note or rest when occurring within the measure.

Clefs

Clef are all converted to G-2, G-2 with ottava bassa or F-2 clef. When no clef is given, a C-3 is used since this is the most neutral clef ambitus-wise.

Warning

Clef changes are currently ignored.

Proportions

Proportions are converted into tuplets. A tuplet that matches the proportion can be used when notes do not need to be cut across several bars. Otherwise, the tuplet proportion need to be adjusted according to the proportion of the note part that goes in one bar and the other. The result is more or less legible but always mathematically correct.

Mensural MEI file
<?xml version="1.0" encoding="UTF-8"?>
<?xml-model href="https://music-encoding.org/schema/5.0/mei-all.rng" type="application/xml" schematypens="http://relaxng.org/ns/structure/1.0"?>
<?xml-model href="https://music-encoding.org/schema/5.0/mei-all.rng" type="application/xml" schematypens="http://purl.oclc.org/dsdl/schematron"?>
<mei xmlns="http://www.music-encoding.org/ns/mei" meiversion="5.0">
   <meiHead>
      <fileDesc>
         <titleStmt>
            <title>[Proportional example]</title>
            <composer>John Dygon</composer>
         </titleStmt>
         <pubStmt>
            <date isodate="2024-11-07-17:13:01" />
         </pubStmt>
      </fileDesc>
      <encodingDesc>
         <appInfo>
            <application xml:id="verovio" version="4.4.0-dev-3524038-dirty">
               <name>Verovio (4.4.0-dev-3524038-dirty)</name>
            </application>
         </appInfo>
         <projectDesc>
            <p>MEI encoded with Verovio</p>
            <p>Converted from CMME XML</p>
         </projectDesc>
      </encodingDesc>
   </meiHead>
   <music>
      <body>
         <mdiv>
            <score>
               <scoreDef>
                  <staffGrp>
                     <grpSym symbol="bracket" />
                     <staffDef n="1" notationtype="mensural" lines="5">
                        <label>Discantus</label>
                        <mensur modusmaior="2" modusminor="2" prolatio="2" tempus="2" />
                     </staffDef>
                     <staffDef n="2" notationtype="mensural" lines="5">
                        <label>Tenor</label>
                        <mensur modusmaior="2" modusminor="2" prolatio="2" tempus="2" />
                     </staffDef>
                  </staffGrp>
               </scoreDef>
               <section type="MensuralMusic">
                  <staff n="1">
                     <layer n="1">
                        <clef shape="C" line="2" />
                        <mensur modusmaior="2" modusminor="2" prolatio="2" tempus="3" sign="O" loc="7" fontsize="small" />
                        <note dur="semibrevis" num="2" numbase="3" oct="3" pname="g" />
                        <dot />
                        <note dur="minima" oct="3" pname="a" />
                        <accid loc="1" ploc="b" oloc="3" accid="s" />
                        <note dur="minima" oct="3" pname="b" />
                        <note dur="minima" oct="4" pname="c" />
                        <note dur="minima" oct="4" pname="d" />
                        <note dur="semibrevis" oct="4" pname="e" />
                        <note dur="minima" oct="4" pname="c" />
                        <note dur="minima" oct="4" pname="f" />
                        <note dur="semiminima" oct="4" pname="g" />
                        <note dur="semiminima" oct="4" pname="a" />
                        <keySig>
                           <keyAccid oct="4" pname="b" loc="8" accid="f" />
                        </keySig>
                        <note dur="minima" oct="4" pname="b" stem.dir="down" />
                        <note dur="semibrevis" oct="4" pname="a" />
                        <note dur="minima" oct="4" pname="f" />
                        <note dur="minima" oct="4" pname="a" />
                        <note dur="semibrevis" oct="4" pname="g" />
                        <note dur="minima" oct="4" pname="f" />
                        <note dur="minima" oct="4" pname="e" />
                        <note dur="minima" oct="4" pname="d" />
                        <note dur="semibrevis" oct="4" pname="e" />
                        <note dur="semibrevis" oct="4" pname="d" />
                        <proport type="cmme_proportion" num="18" numbase="5" />
                        <ligature form="obliqua">
                           <note dur="semibrevis" oct="4" pname="a" color="red" stem.dir="up" stem.pos="left" />
                           <note dur="semibrevis" oct="4" pname="g" color="red" />
                        </ligature>
                        <ligature form="obliqua">
                           <note dur="semibrevis" oct="4" pname="f" color="red" stem.dir="up" stem.pos="left" />
                           <note dur="semibrevis" oct="4" pname="e" color="red" />
                        </ligature>
                        <ligature form="obliqua">
                           <note dur="semibrevis" oct="4" pname="f" color="red" stem.dir="up" stem.pos="left" />
                           <note dur="semibrevis" oct="4" pname="e" color="red" />
                        </ligature>
                        <note dur="semibrevis" oct="4" pname="c" color="red" />
                        <ligature form="obliqua">
                           <note dur="semibrevis" oct="4" pname="f" color="red" stem.dir="up" stem.pos="left" />
                           <note dur="semibrevis" oct="4" pname="e" color="red" />
                        </ligature>
                        <note dur="brevis" num="3" numbase="2" oct="4" pname="a" color="red" />
                        <note dur="semibrevis" num="2" numbase="3" oct="5" pname="c" color="red" />
                        <dot />
                        <note dur="minima" oct="4" pname="b" color="red" stem.dir="down" />
                        <ligature form="obliqua">
                           <note dur="semibrevis" oct="4" pname="a" color="red" stem.dir="up" stem.pos="left" />
                           <note dur="semibrevis" oct="4" pname="g" color="red" />
                        </ligature>
                        <note dur="semibrevis" oct="4" pname="f" color="red" />
                        <ligature form="obliqua">
                           <note dur="semibrevis" oct="4" pname="g" color="red" stem.dir="up" stem.pos="left" />
                           <note dur="semibrevis" oct="4" pname="f" color="red" />
                        </ligature>
                        <proport type="cmme_proportion" num="5" numbase="18" />
                        <note dur="longa" fermata="above" oct="4" pname="g" stem.dir="down" />
                        <barLine />
                     </layer>
                  </staff>
                  <staff n="2">
                     <layer n="1">
                        <clef shape="C" line="4" />
                        <keySig>
                           <keyAccid oct="3" pname="b" loc="5" accid="f" />
                        </keySig>
                        <mensur modusmaior="2" modusminor="2" prolatio="2" tempus="3" sign="O" loc="7" fontsize="small" />
                        <rest dur="minima" />
                        <note dur="semibrevis" oct="3" pname="g" />
                        <note dur="minima" oct="3" pname="f" />
                        <note dur="minima" oct="3" pname="g" />
                        <note dur="minima" oct="3" pname="a" />
                        <note dur="minima" oct="3" pname="b" />
                        <note dur="minima" oct="4" pname="c" />
                        <note dur="semibrevis" oct="3" pname="a" />
                        <note dur="semibrevis" oct="3" pname="d" />
                        <rest dur="minima" />
                        <note dur="semibrevis" num="2" numbase="3" oct="4" pname="d" />
                        <dot />
                        <note dur="minima" oct="4" pname="c" />
                        <note dur="minima" oct="3" pname="b" />
                        <note dur="semibrevis" oct="3" pname="a" />
                        <ligature form="recta">
                           <note dur="semibrevis" oct="3" pname="g" stem.dir="up" stem.pos="left" />
                           <note dur="semibrevis" oct="4" pname="c" />
                        </ligature>
                        <keySig>
                           <keyAccid oct="3" pname="b" loc="5" accid="f" />
                        </keySig>
                        <dot />
                        <note dur="semibrevis" oct="3" pname="b" />
                        <note dur="longa" num="6" numbase="5" oct="3" pname="a" stem.dir="down" />
                        <note dur="longa" oct="3" pname="g" stem.dir="down" />
                        <barLine />
                     </layer>
                  </staff>
               </section>
            </score>
         </mdiv>
      </body>
   </music>
</mei>
image
Converted MEI (cmn) file
<?xml version="1.0" encoding="UTF-8"?>
<?xml-model href="https://music-encoding.org/schema/5.0/mei-basic.rng" type="application/xml" schematypens="http://relaxng.org/ns/structure/1.0"?>
<?xml-model href="https://music-encoding.org/schema/5.0/mei-basic.rng" type="application/xml" schematypens="http://purl.oclc.org/dsdl/schematron"?>
<mei xmlns="http://www.music-encoding.org/ns/mei" meiversion="5.0+basic">
   <meiHead>
      <fileDesc>
         <titleStmt>
            <title>[Proportional example]</title>
            <composer>John Dygon</composer>
         </titleStmt>
         <pubStmt>
            <date isodate="2024-11-07-17:13:01" />
         </pubStmt>
      </fileDesc>
   </meiHead>
   <music>
      <body>
         <mdiv>
            <score>
               <scoreDef>
                  <staffGrp symbol="bracket">
                     <staffDef n="1" lines="5" meter.count="6" meter.unit="2" clef.shape="G" clef.line="2">
                        <label>Discantus</label>
                     </staffDef>
                     <staffDef n="2" lines="5" meter.count="6" meter.unit="2" clef.shape="G" clef.line="2" clef.dis="8" clef.dis.place="below">
                        <label>Tenor</label>
                     </staffDef>
                  </staffGrp>
               </scoreDef>
               <section type="MensuralMusic">
                  <measure n="1">
                     <staff n="1">
                        <layer n="1">
                           <note dots="1" dur="1" oct="3" pname="g" />
                           <note dur="2" oct="3" pname="a" />
                           <note dur="2" oct="3" pname="b" />
                           <note dur="2" oct="4" pname="c" />
                        </layer>
                     </staff>
                     <staff n="2">
                        <layer n="1">
                           <rest dur="2" />
                           <note dur="1" oct="3" pname="g" />
                           <note dur="2" oct="3" pname="f" />
                           <note dur="2" oct="3" pname="g" />
                           <note dur="2" oct="3" pname="a" />
                        </layer>
                     </staff>
                     <dir type="mscore-staff-text" place="above" staff="1" tstamp="0">O</dir>
                     <dir type="mscore-staff-text" place="above" staff="2" tstamp="0">O</dir>
                  </measure>
                  <measure n="2">
                     <staff n="1">
                        <layer n="1">
                           <note dur="2" oct="4" pname="d" />
                           <note dur="1" oct="4" pname="e" />
                           <note dur="2" oct="4" pname="c" />
                           <note dur="2" oct="4" pname="f" />
                           <note dur="4" oct="4" pname="g" />
                           <note dur="4" oct="4" pname="a" />
                        </layer>
                     </staff>
                     <staff n="2">
                        <layer n="1">
                           <note dur="2" oct="3" pname="b" />
                           <note dur="2" oct="4" pname="c" />
                           <note dur="1" oct="3" pname="a" />
                           <note dur="1" oct="3" pname="d" />
                        </layer>
                     </staff>
                  </measure>
                  <measure n="3">
                     <staff n="1">
                        <layer n="1">
                           <note dur="2" oct="4" pname="b" />
                           <note dur="1" oct="4" pname="a" />
                           <note dur="2" oct="4" pname="f" />
                           <note dur="2" oct="4" pname="a" />
                           <note xml:id="nwvfeb" dur="2" oct="4" pname="g" />
                        </layer>
                     </staff>
                     <staff n="2">
                        <layer n="1">
                           <rest dur="2" />
                           <note dots="1" dur="1" oct="4" pname="d" />
                           <note dur="2" oct="4" pname="c" />
                           <note dur="2" oct="3" pname="b" />
                        </layer>
                     </staff>
                     <tie startid="#nwvfeb" endid="#n1vhrrw1" />
                  </measure>
                  <measure n="4">
                     <staff n="1">
                        <layer n="1">
                           <note xml:id="n1vhrrw1" dur="2" oct="4" pname="g" />
                           <note dur="2" oct="4" pname="f" />
                           <note dur="2" oct="4" pname="e" />
                           <note dur="2" oct="4" pname="d" />
                           <note dur="1" oct="4" pname="e" />
                        </layer>
                     </staff>
                     <staff n="2">
                        <layer n="1">
                           <note dur="1" oct="3" pname="a" />
                           <note xml:id="n17y24y7" dur="1" oct="3" pname="g" />
                           <note xml:id="n1fjwfa1" dur="1" oct="4" pname="c" />
                        </layer>
                     </staff>
                     <bracketSpan startid="#n17y24y7" endid="#n1fjwfa1" func="ligature" lform="solid" />
                  </measure>
                  <measure n="5">
                     <staff n="1">
                        <layer n="1">
                           <note dur="1" oct="4" pname="d" />
                           <tuplet num="18" numbase="5" bracket.visible="true" num.format="ratio">
                              <note xml:id="njqjjix" dur="1" oct="4" pname="a" color="red" />
                              <note xml:id="nmljcz9" dur="1" oct="4" pname="g" color="red" />
                              <note xml:id="n16a3qtu" dur="1" oct="4" pname="f" color="red" />
                              <note xml:id="ngv281" dur="1" oct="4" pname="e" color="red" />
                              <note xml:id="n1fpc4s5" dur="1" oct="4" pname="f" color="red" />
                              <note xml:id="nomptww" dur="1" oct="4" pname="e" color="red" />
                              <note dur="1" oct="4" pname="c" color="red" />
                           </tuplet>
                           <tuplet num="9" numbase="1" bracket.visible="false" num.format="ratio">
                              <note xml:id="n4ua71c" dur="2" oct="4" pname="f" color="red" />
                           </tuplet>
                        </layer>
                     </staff>
                     <staff n="2">
                        <layer n="1">
                           <note dur="1" oct="3" pname="b" />
                           <note xml:id="n1r43vz0" dur="breve" oct="3" pname="a" />
                        </layer>
                     </staff>
                     <bracketSpan startid="#njqjjix" endid="#nmljcz9" func="ligature" lform="solid" />
                     <bracketSpan startid="#n16a3qtu" endid="#ngv281" func="ligature" lform="solid" />
                     <bracketSpan startid="#n1fpc4s5" endid="#nomptww" func="ligature" lform="solid" />
                     <bracketSpan startid="#n4ua71c" endid="#nshczft" func="ligature" lform="solid" />
                     <tie startid="#n4ua71c" endid="#ny07vaa" color="red" />
                     <tie startid="#n1r43vz0" endid="#n1ia5t3e" />
                  </measure>
                  <measure n="6">
                     <staff n="1">
                        <layer n="1">
                           <tuplet num="9" numbase="4" bracket.visible="false" num.format="ratio">
                              <note xml:id="ny07vaa" dur="2" oct="4" pname="f" color="red" />
                           </tuplet>
                           <tuplet num="18" numbase="5" bracket.visible="true" num.format="ratio">
                              <note xml:id="nshczft" dur="1" oct="4" pname="e" color="red" />
                              <note dur="breve" oct="4" pname="a" color="red" />
                              <note dots="1" dur="1" oct="5" pname="c" color="red" />
                              <note dur="2" oct="4" pname="b" color="red" />
                              <note xml:id="n17kk27o" dur="1" oct="4" pname="a" color="red" />
                              <note xml:id="n1aedmca" dur="1" oct="4" pname="g" color="red" />
                              <note dur="1" oct="4" pname="f" color="red" />
                              <note xml:id="n6wkmr4" dur="1" oct="4" pname="g" color="red" />
                              <note xml:id="n1paq3u0" dur="1" oct="4" pname="f" color="red" />
                           </tuplet>
                        </layer>
                     </staff>
                     <staff n="2">
                        <layer n="1">
                           <note xml:id="n1ia5t3e" dots="1" dur="breve" oct="3" pname="a" />
                        </layer>
                     </staff>
                     <bracketSpan startid="#n17kk27o" endid="#n1aedmca" func="ligature" lform="solid" />
                     <bracketSpan startid="#n6wkmr4" endid="#n1paq3u0" func="ligature" lform="solid" />
                  </measure>
                  <measure n="7">
                     <staff n="1">
                        <layer n="1">
                           <note xml:id="n1isjjni" dots="1" dur="breve" oct="4" pname="g" />
                        </layer>
                     </staff>
                     <staff n="2">
                        <layer n="1">
                           <note xml:id="nb9ic71" dots="1" dur="breve" oct="3" pname="g" />
                        </layer>
                     </staff>
                     <tie startid="#n1isjjni" endid="#n1nh7jcw" />
                     <tie startid="#nb9ic71" endid="#n1xki01y" />
                  </measure>
                  <measure right="end" n="8">
                     <staff n="1">
                        <layer n="1">
                           <note xml:id="n1nh7jcw" dots="1" dur="breve" oct="4" pname="g" />
                        </layer>
                     </staff>
                     <staff n="2">
                        <layer n="1">
                           <note xml:id="n1xki01y" dots="1" dur="breve" oct="3" pname="g" />
                        </layer>
                     </staff>
                  </measure>
               </section>
            </score>
         </mdiv>
      </body>
   </music>
</mei>

Warning

The MEI produced with unconventional proportions can be problematic in MuseScore because of the limitation of MuseScore regarding the representation of the tuplets. It is expected for any tuplets to have a duration that can be represented with a group of base durations, which is not always the case with the MEI tuplets produced. As a result, even if the file will load and show properly in MuseScore, or even export properly to MEI, saving it as MuseScore file will corrupt it. This is not likely to be overcome.

Ligatures and coloration

Ligatures and coloration are both converted into bracketSpan, with a @lform="solid" for the ligatures.

Warning

Coloration on chords (used in mensural notation for faux bourdon) is not preserved. Also, when converting to MEI-Basic, the file is currently not valid because bracketSpan are not available in MEI-Basic.

Editorial markup

Editorial markup is ignored and dropped from the conversion. Only the selected reading is converted according to the use of --app-x-path-query or --choice-x-path-query parameters.

Example

The example above rendered with the new cast-off algorithm

image

Others

  • Add a new Object::CopyAttributesTo for copying all MEI (supported and unsupported) attributes from one object to another.
  • Need update to C++23

* Uses newly generated LibMEI module level static methods
* Only documents with only one layer by staff can be treated as mensural only (i.e., for mensural view, mensural cast-off and conversion to CMN)
* Add ObjectFactory::Create(ClassId)
* Drop two string values in MusicXML importer
* Drop two string values in MusicXML importer

(cherry picked from commit fe2d5b1)
@annplaksin
Copy link

Isn't the CMME ColorChange addressing coloration? In that case, using @colored would make more sense instead of @color.
Or are you using @color to display coloration in Verovio?

@lpugin
Copy link
Contributor Author

lpugin commented Nov 13, 2024

Isn't the CMME ColorChange addressing coloration?

Not quite. Coloration is addressed with Colored on Note, and we do use @colored for that. ColorChange is the change of color, e.g., a change of ink. Possible colors are black, red, yellow, green or blue.

@annplaksin
Copy link

Okay, I thought it might be special way to indicate longer sections of coloration in a piece. Then, everything is fine. 😁

@ahankinson
Copy link
Contributor

NB: By merging this Verovio will also be updated to C++23 (nb: @craigsapp for your previous message)

include/vrv/iocmme.h Outdated Show resolved Hide resolved
src/iocmme.cpp Outdated Show resolved Hide resolved
src/iocmme.cpp Outdated Show resolved Hide resolved
@lpugin
Copy link
Contributor Author

lpugin commented Nov 15, 2024

NB: By merging this Verovio will also be updated to C++23 (nb: @craigsapp for your previous message)

This is not the case any more. I dropped the C++23 dependency since it looks like it is a bit early to switch to it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants