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

Keep original Humdrum duration #1796

Open
rettinghaus opened this issue Nov 1, 2020 · 15 comments
Open

Keep original Humdrum duration #1796

rettinghaus opened this issue Nov 1, 2020 · 15 comments

Comments

@rettinghaus
Copy link
Contributor

When importing from Humdrum, **recip values are converted to @dur. It would be nice to keep the original values additionally in @dur.recip (see https://music-encoding.org/guidelines/v4/attribute-classes/att.duration.gestural.html).

Should be an easy task for @craigsapp.

@craigsapp craigsapp self-assigned this Nov 1, 2020
@craigsapp
Copy link
Contributor

craigsapp commented Nov 1, 2020

I don't know if it would be useful to include @dur.recip all of the time, but adding an option, either in verovio or embedded in the Humdrum data, would be a good way to preserve the @dur.recip values. Or if you want to have @dur.recip present always in the conversion that would of course be easiest to implement.

@dur.recip maps closely to @dur.ges (which usually matches @dur), and it also includes @dots.ges (which usually matches @dots). Except in the case of tuplets, where @dur.recip contains the performance duration, resolving any time modifications. For example triplet-eighth notes are @dur.recip="12", meaning 1/12th of a whole note, while in MEI, triplet eighth notes would be @dur="8" with a tuplet time modification of 2/3rds: tuplet@num="3"/tuplet@numbase="2", meaning 3 tuplet notes in the time of 2 regular notes. One possibility would be to add @dur.recip only in cases of notes/rests inside of a tuplet, since @dur.recip is built from @dur.ges (or @dur) + @dots.ges (or @dots) and all @num and @numbase values of tuplets of the element's ancestors.

There will be complications related to multiRest: I could either give the duration of one measure, or the duration of all of the measures summed together. Probably the latter is best, and then the multiRest count could be used to calculate the duration of the full multiRest. There will be other sorts of things to deal with such as two-note tremolos and repeat markers which do not exist in the Humdrum data, but I can generate the @dur.recip for these as they are converted.

There are also rhythms that are otherwise unrepresentable in graphic notation, such as the duration of the mRest in this example:

Screen Shot 2020-11-01 at 5 31 50 AM

The duration of the whole rest is 5 eighth notes, or 5/8th of a whole note, which is represented as @dur.recip="8%5" (i.e., reciprocal of the duration 5/8).

When converting into MIDI data, @dur.recip has the advantage of mapping directly to the durations of notes, which otherwise require all of the durational parameters mentioned above.

@rettinghaus
Copy link
Contributor Author

I think it would be good to have them always present when converting.
As multiRest needs no @dur it would not make too much sense to generate a @dur.recip, does it?

For MIDI output it sound good to prefer the @dur.recip value if available.

@craigsapp
Copy link
Contributor

Also, if I add @dur.recip parameters, the definition of it should be updated in the MEI schema:

[0-9]+(%[0-9]+)?\.*q?

The (%[0-9]+)? is a **recip extension that I added 10 years ago to allow for rhythms such as triplet whole notes or 5 quintuple quarter notes in the time of three regular quarter notes. The q is needed to indicate a grace note (gestural duration of 0).

Or to preserve some indirect rhythmic information:

[0-9]+(%[0-9]+)?\.*q*

or to be more precise if extended regular expressions are allowed:

[0-9]+(%[0-9]+)?\.*q{0,2}

This is because q represents an acciaccatura grace note, and qq represents an appoggiatura grace note. For the later, the graphical duration of the note is 0, but the performance duration is given, and this duration should be subtracted from the following note in performance:

Screen Shot 2020-11-01 at 6 12 05 AM

A complication being that qq actually removes the slash from the grace note, and to encode appoggiaturas correctly P needs to be added to the first note in an appoggiatura, and p on the second note. But in general q will mean acciaccatura, and qq means appoggiatura. This is because appoggiaturas can be resolved into regular notes (as often happens in modern editions).


There are two additional minor complications:

(1) Not a problem in the definition, but a caveat: Breves (double whole notes) are traditionally represented as 0, which is a symbolic number, not a literal number. In the extended **recip representation, breves are 1%2, meaning 2/1sts of a whole note. By extension 00 is an (imperfect) long, or 1%4 and (imperfect) maximas are 000 or 1%8. Double maximas would be 0000 or 1%16 if they exist.

(2) Already mentioned: Since 0 is reserved for a symbolic rhythm, the letter q is used to reprent the numeric 0 duration (i.e., a grace note), so this would need to be added to the current @dur.recip definition. When q is present, that means the duration information shifts from @dur.ges to @dur (i.e., the **recip data is for the graphical display of the zero-duration grace note as demonstrated on the bottom staff).

@craigsapp
Copy link
Contributor

I think it would be good to have them always present when converting.
As multiRest needs no @dur it would not make too much sense to generate a @dur.recip, does it?

OK, I will have it always convert, and an option to suppress it can be added in the future if necessary (or it will be easy to remove by post-processing).

It will depend on the purpose of including @dur.recip and what it will be used for. It will not be interesting for MIDI conversions since rests are not represented directly in MIDI files, although you might need to advance the write time of the next note based on the duration of the multiRest. I can leave it out for now and add it if it is ever needed.

@craigsapp
Copy link
Contributor

Another considertation for the definition of dur.recip would be to include the tie information:

[[]?[0-9]+(%[0-9]+)?\.*q{0,2}[]_]?

This encodes the tie states of notes:

symbol meaning
[ start of a tied note group, usually placed before duration
_ middle note of a tied group, usually placed after duration (and pitch)
] ending note of a tied group, usually placed after duration (and pitch)

This would be useful for MIDI conversion as well as analysis using @dur.recip, since the presence of _ or ] means that the graphic note is not a sounding note attack, and the duration of the note should be added to the performance duration of the first note in the tied group.

Without this information, it is otherwise complicated to match <tie> elements to their matching notes.

How to translate the durations of notes with disjunct ties should be considered further. This will depend on the purpose of @dur.recip. For MIDI and analysis, probably it should be changed from @dur.ges type data into performance durations duration the conversion process:

Screen Shot 2020-11-01 at 6 35 48 AM

In other words, the second 16th note would best be converted into @dur.recip="[8. which matches to the performance duration rather than the @dur.ges equivalent of a 16th note. Or the double [[ could be preserved, although this would still make it hard to figure out the performance duration of the note, since you would not know when the next note in the tied group occurs. So converting the example note as a dotted eighth note seems most useful, allowing @dur.recip to function as a score-time performance duration rather than duplicating @dur.ges.

@rettinghaus
Copy link
Contributor Author

I would leave tie information out, as this is what @tie is for. The idea is to just store the information that comes from humdrum, not to calculate some additional information.

@craigsapp
Copy link
Contributor

OK. That sounds good.

@craigsapp
Copy link
Contributor

Also:

(1) Do you want the complication of the q to be involved, or leave it out?

(2) We could also disallow symbolic values in @dur.recip such as 0 for the breve, and instead require them to be the expanded numeric representation, which would be 1%2. This could then free up 0 to represent grace note durations, removing the need for q.

craigsapp added a commit that referenced this issue Nov 1, 2020
@craigsapp
Copy link
Contributor

Implemented with commit ae12c90

I adjusted the **recip durations based on the previous post in the thread (remove q and convert symbolic rhythms to numeric ones). If that is good, then I can merge into the develop branch.

Test example:

Screen Shot 2020-11-01 at 10 44 01 AM

**kern
*M4/4
4c
8qd
2c
12cL
12d
24.e
48fJ
=
*M2/1
0c
=
3%2c
3%2d
3%2e
=
*M4/1
00c
=
0...c
4d
=
*M3/4
*^
20%3ccL	2.g
20%3dd	.
20%3ee	.
20%3ff	.
20%3ggJ	.
*v	*v
==
*-

MEI transcoding:

<?xml version="1.0" encoding="UTF-8"?>
<?xml-model href="https://music-encoding.org/schema/4.0.0/mei-all.rng" type="application/xml" schematypens="http:https://relaxng.org/ns/structure/1.0"?>
<?xml-model href="https://music-encoding.org/schema/4.0.0/mei-all.rng" type="application/xml" schematypens="http:https://purl.oclc.org/dsdl/schematron"?>
<mei xmlns="http:https://www.music-encoding.org/ns/mei" meiversion="4.0.0">
 <meiHead>
  <fileDesc>
   <titleStmt>
    <title />
   </titleStmt>
   <pubStmt />
  </fileDesc>
  <encodingDesc>
   <appInfo>
    <application isodate="2020-11-01T10:42:04" version="3.1.0-dev-c4f2cd7-dirty">
     <name>Verovio</name>
     <p>Transcoded from Humdrum</p>
    </application>
   </appInfo>
  </encodingDesc>
  <workList>
   <work>
    <title />
   </work>
  </workList>
 </meiHead>
 <music>
  <body>
   <mdiv xml:id="mdiv-0000000612106679">
    <score xml:id="score-0000001230284823">
     <scoreDef xml:id="scoredef-0000001957649138" midi.bpm="400">
      <staffGrp xml:id="staffgrp-0000000900295103">
       <staffDef xml:id="staffdef-0000000866567159" n="1" lines="5">
        <clef xml:id="clef-0000000714128493" shape="G" line="2" />
        <meterSig xml:id="metersig-L2F1" count="4" unit="4" />
       </staffDef>
      </staffGrp>
     </scoreDef>
     <section xml:id="section-L1F1">
      <measure xml:id="measure-L1">
       <staff xml:id="staff-0000000052432288" n="1">
        <layer xml:id="layer-L1F1N1" n="1">
         <note xml:id="note-L3F1" dur.recip="4" dur="4" oct="4" pname="c" accid.ges="n" />
         <note xml:id="note-L4F1" dur.recip="0" dur="8" oct="4" pname="d" grace="unacc" accid.ges="n" />
         <note xml:id="note-L5F1" dur.recip="2" dur="2" oct="4" pname="c" accid.ges="n" />
         <tuplet xml:id="tuplet-L6F1-L9F1" num="3" numbase="2" bracket.visible="false" num.format="count">
          <beam xml:id="beam-L6F1-L9F1">
           <note xml:id="note-L6F1" dur.recip="12" dur="8" oct="4" pname="c" accid.ges="n" />
           <note xml:id="note-L7F1" dur.recip="12" dur="8" oct="4" pname="d" accid.ges="n" />
           <note xml:id="note-L8F1" dots="1" dur.recip="24." dur="16" oct="4" pname="e" accid.ges="n" />
           <note xml:id="note-L9F1" dur.recip="48" dur="32" oct="4" pname="f" accid.ges="n" />
          </beam>
         </tuplet>
        </layer>
       </staff>
      </measure>
      <scoreDef xml:id="scoredef-0000001109176401">
       <meterSig xml:id="msig-0000001769715647" count="2" unit="1" />
      </scoreDef>
      <measure xml:id="measure-L10">
       <staff xml:id="staff-L10F1N1" n="1">
        <layer xml:id="layer-L10F1N1" n="1">
         <note xml:id="note-L12F1" dur.recip="1%2" dur="breve" oct="4" pname="c" accid.ges="n" />
        </layer>
       </staff>
      </measure>
      <measure xml:id="measure-L13">
       <staff xml:id="staff-L13F1N1" n="1">
        <layer xml:id="layer-L13F1N1" n="1">
         <tuplet xml:id="tuplet-L14F1-L16F1" num="3" numbase="2" num.format="count">
          <note xml:id="note-L14F1" dur.recip="3%2" dur="1" oct="4" pname="c" accid.ges="n" />
          <note xml:id="note-L15F1" dur.recip="3%2" dur="1" oct="4" pname="d" accid.ges="n" />
          <note xml:id="note-L16F1" dur.recip="3%2" dur="1" oct="4" pname="e" accid.ges="n" />
         </tuplet>
        </layer>
       </staff>
      </measure>
      <scoreDef xml:id="scoredef-0000002027439410">
       <meterSig xml:id="msig-0000001051136921" count="4" unit="1" />
      </scoreDef>
      <measure xml:id="measure-L17">
       <staff xml:id="staff-L17F1N1" n="1">
        <layer xml:id="layer-L17F1N1" n="1">
         <note xml:id="note-L19F1" dur.recip="1%4" dur="long" oct="4" pname="c" accid.ges="n" />
        </layer>
       </staff>
      </measure>
      <measure xml:id="measure-L20">
       <staff xml:id="staff-L20F1N1" n="1">
        <layer xml:id="layer-L20F1N1" n="1">
         <note xml:id="note-L21F1" dots="3" dur.recip="1%2..." dur="breve" oct="4" pname="c" accid.ges="n" />
         <note xml:id="note-L22F1" dur.recip="4" dur="4" oct="4" pname="d" accid.ges="n" />
        </layer>
       </staff>
      </measure>
      <scoreDef xml:id="scoredef-0000000211142435">
       <meterSig xml:id="msig-0000001027920201" count="3" unit="4" />
      </scoreDef>
      <measure xml:id="measure-L23" right="end">
       <staff xml:id="staff-L23F1N1" n="1">
        <layer xml:id="layer-L23F1N1" n="1">
         <tuplet xml:id="tuplet-L26F1-L30F1" num="5" numbase="6" bracket.visible="false" num.format="count">
          <beam xml:id="beam-L26F1-L30F1">
           <note xml:id="note-L26F1" dur.recip="20%3" dur="8" oct="5" pname="c" accid.ges="n" />
           <note xml:id="note-L27F1" dur.recip="20%3" dur="8" oct="5" pname="d" accid.ges="n" />
           <note xml:id="note-L28F1" dur.recip="20%3" dur="8" oct="5" pname="e" accid.ges="n" />
           <note xml:id="note-L29F1" dur.recip="20%3" dur="8" oct="5" pname="f" accid.ges="n" />
           <note xml:id="note-L30F1" dur.recip="20%3" dur="8" oct="5" pname="g" accid.ges="n" />
          </beam>
         </tuplet>
        </layer>
        <layer xml:id="layer-L26F2N2" n="2">
         <note xml:id="note-L26F2" dots="1" dur.recip="2." dur="2" oct="4" pname="g" accid.ges="n" />
        </layer>
       </staff>
      </measure>
     </section>
    </score>
   </mdiv>
  </body>
 </music>
</mei>

@rettinghaus
Copy link
Contributor Author

Looks good. But you know Humdrum better than me ;-) Is "q" more of a visual marker or does it belong to **recip?
And I'm still not sure about the 0. Does it actually have two meanings, or did it change?
See my commit here: music-encoding/music-encoding@07fde5d

@craigsapp
Copy link
Contributor

Here is the original **recip documentation:
https://www.humdrum.org/rep/recip

The problem is that grace notes were not considered in the original system, as well as non-integer divisions of the whole note (other than augmentation dots which are included). To fix the latter problem, I implemented a fractional generalization such as 3%2 which means 2/3rds of a whole note for triplet whole notes (or as a reciprocal, it takes 3/2 of a triplet whole note to fill a whole note). This fully generalizes to rational rhythms/durations, even if it cannot be represented as a single notehead in graphical music notation, such as 5 eighth notes being 8%5. A quarter note is represented as 4, but with the rational extension 4%1 is equivalent (but should not be used since the values should always be reduced to the simplest fraction). Likewise, 3%2 can be interpreted as the duration of two triplet half notes (where 3 means a triplet half note).

Originally 0 meant a breve (double whole note), so I kept the meaning for backward compatibility. There was also no representation for longs and maximas, so I added 00 for longs and 000 for maximas, extending the symbolic meaning of 0. In the rational system, these can be represented properly with 1%2 for a breve (literally meaning two whole notes), 1%4 for imperfect longs, and 1%8 for maximas. If the **recip data format were to be (re-)designed from scratch today, I would remove the symbolic0 system and only allow the rational system. However, 0 is a convenient symbol for breves and 00 for longs in Renaissance musical scores since they are easier to type and read, so that is another reason why I kept the backwards compatibility. So in **recip there is currently only one meaning for 0, which is a breve.

Since 0 was already used for a breve, and I want to allow grace notes to be represented occasionally in pure **recip data, so I borrow the q from **kern data where it means acciaccatura and implies a grace note of any kind with zero score duration. So currently in **recip data, q really means 0, and 0 means two whole notes.

If I were redesigning **recip today, grace notes would be 0, followed by an optional graphic duration, such as 08 for a grace note that looks like an 8th note, or 016 for a grace note that looks like a sixteenth note. This is not much different than using q, but allows for 0 by itself to have an obvious meaning consistent with the other numbers used in the system.


So values in @dur.recip could either be the way they are exactly used in practice in Humdrum **recip, or they could be designed to be the more self-consistent fully numeric system that is an extension of the original system. It is conceivable that I might allow both systems to exist at the same time, with some qualifier such as *num meaning the numeric form rather than the default symbolic form (maybe adding *sym to explicitly note this variant representation) is being used:

**recip	**recip
*sym	*num
0	1%2
q	0
8q	08
16q	016
4	4
32	32
1024	1024
*-	*-

How to represent in @dur.recip will depend on what it is used for. For converting in/out of Humdrum data, the old system is the obvious choice, but the conversion between the numeric and symbolic forms would be trivial. If @dur.recip is used only inside of MEI, then removing the complications of the symbolic rhythms would be useful since that would simplify programs that process the values.

Tecnically the q can exist by itself, so to update the previous regular expression:

[0-9]+(%[0-9]+)?\.*q{0,2}|q+

q and qq are graphical differences (controlling the slash visibility for acciaccaturas and appoggiaturas, respectively in **kern data), so if these distinctions should not be included, then only a single q could be allowed:

[0-9]+(%[0-9]+)?\.*q?|q

Or to only allow purely numeric **recip with the 0 system for grace notes that allows the visual rhythm for the grace notes:

0?[0-9]+(%[0-9]+)?\.*

or to be overly pedantic:

0?[1-9][0-9]*(%[1-9][0-9]*)?\.*

Using purely numeric **recip in @dur.recip is reasonable since this might be a future allowed alternate form in **recip data, and this would prevent the problem of having to deal with potential backwards compatibility problems for @dur.recip in the future. A translation from numeric to symbolic forms of **recip would be needed when interfacing with Humdrum data, but since there would be a fixed variant system in each, the conversions would be easy. If the symbolic form is allowed in @dur.recip, then it would be difficult to update it if **recip every changes to a purely numeric system. It would also be more convenient for data processing to have only the numeric form as well (so internal processing within MEI would be simpler).

@craigsapp
Copy link
Contributor

See my commit here: music-encoding/music-encoding@07fde5d

That is a good definition for symbolic **recip:

[0-9]+(%[0-9]+)?\.*q?

with the exception that I noticed that q could exist by itself, so an update:

[0-9]+(%[0-9]+)?\.*q?|q

I see that \d can be used as a shortcut for [0-9], so this can be utilized:

\d+(%\d+)?\.*q?|q

Or if the pure numeric form should be used that disallows 0 for breves, etc, and changing q postfix to a 0 prefix (which is what I would want if **recip were not backwards compatible):

0?[1-9]\d*(%[1-9]\d*)?\.*|0

(I needed to add |0 to the pedantic numeric version to allow 0 by itself.)

@lpugin
Copy link
Contributor

lpugin commented Feb 5, 2021

What is the status of this?

@rettinghaus
Copy link
Contributor Author

For the time being (i.e. until MEI allows it) I would prefer the unchanged, original pure **recip and not the updated values, to make sure valid MEI is produced.

@craigsapp
Copy link
Contributor

craigsapp commented Feb 7, 2021

I don't want to preserve the **recip data by default in the Humdrum conversion since in theory it can be extracted from the MEI parameters (sometimes difficult to reconstruct when <tupletSpan> is used), so ideally there would be an option to set if it this should be included in the conversion or not.

What should that option name be? Perhaps --recip, -humRecip or --humdrumRecip. One of the last two seems better, since there could be other classes of information that could be included. This is related to the current --humType option (which I used to embed pitch and scoreTimestamps into svg g@class for notes and rests), so perhaps --humRecip is best to match with that option name.

The current MEI implementation would be the same as the newer implemtation of **recip except for:

  • Grace notes should convert to a **recip of an empty string.
  • Unrepresentable rhythms such as a triplet whole note should convert as a null string (i.e., do not give @dur.recip for such extended rhythms). This can sometimes apply to exotic tuplets not based on a power of two, such as 5 quarter notes in the time of 3 quarter notes.
  • breves would always convert to 0 instead of 1%2, longs as 00 instead of 1%4 and maximas as 000 instead of 1%8.

When a new implementation is allowed in MEI, the main question will be how to represent grace notes: Either by adding a q to the visual duration (also allowing inclusion of the visual duration will allow appoggiatura performance rendering if qq is given as the grace-note qualification), or to give grace-notes a more accurate numerical rendering of '0', in which case breves would be converted always as 1%2 even if they are 0 in the original Humdrum data. Otherwise, 0 would remain an alias for 1%2, etc.

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

No branches or pull requests

3 participants