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

Octave handling for MIDI #3906

Merged
merged 9 commits into from
Jan 6, 2025
Merged

Conversation

brdvd
Copy link
Contributor

@brdvd brdvd commented Dec 27, 2024

This PR introduces octave handling for MIDI. This avoids the need to encode notes with oct.ges within the octave.

A simplified version of octave-001 from the test suite:

octave-001
Show MEI
<?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>Octave shift example</title>
         </titleStmt>
         <pubStmt>
            <date isodate="2017-05-09">2017-05-09</date>
         </pubStmt>
         <seriesStmt>
            <title>Verovio test suite</title>
         </seriesStmt>
         <notesStmt>
            <annot>Verovio supports the "octave" element for octave shifts. </annot>
         </notesStmt>
      </fileDesc>
      <encodingDesc>
         <appInfo>
            <application version="2.0.0" label="2">
               <name>Verovio</name>
            </application>
         </appInfo>
      </encodingDesc>
   </meiHead>
   <music>
      <body>
         <mdiv>
            <score>
               <scoreDef>
                  <staffGrp>
                     <staffDef n="1" lines="5" clef.shape="G" clef.line="2" />
                  </staffGrp>
               </scoreDef>
               <section>
                  <measure n="1">
                     <staff n="1">
                        <layer n="1">
                           <note dur="2" oct="6" pname="e" />
                           <beam>
                              <note dur="8" oct="6" pname="f" />
                              <note dur="8" oct="6" pname="a" />
                              <note dur="8" oct="6" pname="g" />
                              <note dur="8" oct="6" pname="b" />
                           </beam>
                        </layer>
                     </staff>
                  </measure>
                  <measure right="dbl" n="2">
                     <staff n="1">
                        <layer n="1">
                           <note dur="1" oct="7" pname="c" />
                        </layer>
                     </staff>
                  </measure>
                  <measure n="3">
                     <staff n="1">
                        <layer n="1">
                           <note xml:id="n1" dur="2" oct="5" pname="e" />
                           <beam>
                              <note dur="8" oct="5" pname="f" />
                              <note dur="8" oct="5" pname="a" />
                              <note dur="8" oct="5" pname="g" />
                              <note dur="8" oct="5" pname="b" />
                           </beam>
                        </layer>
                     </staff>
                     <octave startid="#n1" endid="#n2" dis="8" dis.place="above" />
                  </measure>
                  <measure right="dbl" n="4">
                     <staff n="1">
                        <layer n="1">
                           <note xml:id="n2" dur="1" oct="6" pname="c" />
                        </layer>
                     </staff>
                  </measure>
               </section>
            </score>
         </mdiv>
      </body>
   </music>
</mei>

Another example with tstamp boundary:
elise-part

Show MEI
<?xml version="1.0" encoding="UTF-8"?>
<mei xmlns="http://www.music-encoding.org/ns/mei" meiversion="4.0.0">
  <meiHead>
    <fileDesc>
      <titleStmt/>
      <pubStmt/>
    </fileDesc>
  </meiHead>
  <music>
    <body>
      <mdiv>
        <score>
          <scoreDef xmlns="http://www.music-encoding.org/ns/mei" midi.bpm="90">
            <staffGrp xml:id="staffgrp-0000000106419105">
              <staffGrp xml:id="staffgrp-0000000696589666" symbol="brace" bar.thru="true">
                <staffGrp xml:id="P1-NULL" bar.thru="true">
                  <staffDef xml:id="staffdef-0000000287827666" n="1" lines="5" clef.shape="G" clef.line="2" meter.count="3" meter.unit="8"/>
                  <staffDef xml:id="staffdef-0000000015848063" n="2" lines="5" clef.shape="F" clef.line="4" meter.count="3" meter.unit="8"/>
                </staffGrp>
              </staffGrp>
            </staffGrp>
          </scoreDef>
          <section xmlns="http://www.music-encoding.org/ns/mei" xml:id="section-0000001257157093">
            <measure xml:id="measure-81-mdiv-1" n="81">
              <staff xml:id="staff-0000001034331057" n="1">
                <layer xml:id="layer-0000000550117602" n="1">
                  <beam xml:id="beam-0000001868313858">
                    <tuplet xml:id="tuplet-0000000840732543" num="3" numbase="2" num.place="below" num.visible="true" bracket.place="above" bracket.visible="false">
                      <note xml:id="note-843" dur="16" oct="3" pname="a" stem.dir="up"/>
                      <note xml:id="note-844" dur="16" oct="4" pname="c" stem.dir="up"/>
                      <note xml:id="note-845" dur="16" oct="4" pname="e" stem.dir="up"/>
                    </tuplet>
                  </beam>
                  <beam xml:id="beam-0000000840873010">
                    <tuplet xml:id="tuplet-0000000359418744" num="3" numbase="2" num.place="below" num.visible="true" bracket.place="above" bracket.visible="false">
                      <note xml:id="note-846" dur="16" oct="4" pname="a" stem.dir="down"/>
                      <note xml:id="note-847" dur="16" oct="5" pname="c" stem.dir="down"/>
                      <note xml:id="note-848" dur="16" oct="5" pname="e" stem.dir="down"/>
                    </tuplet>
                  </beam>
                  <beam xml:id="beam-0000001950805604">
                    <tuplet xml:id="tuplet-0000001628911397" num="3" numbase="2" num.place="above" num.visible="false" bracket.place="above" bracket.visible="false">
                      <note xml:id="note-849" dur="16" oct="5" pname="d" stem.dir="down"/>
                      <note xml:id="note-850" dur="16" oct="5" pname="c" stem.dir="down"/>
                      <note xml:id="note-851" dur="16" oct="4" pname="b" stem.dir="down"/>
                    </tuplet>
                  </beam>
                </layer>
              </staff>
              <staff xml:id="staff-0000000986812113" n="2">
                <layer xml:id="layer-0000000199966148" n="2">
                  <note xml:id="note-852" dur="8" oct="1" pname="a" stem.dir="up"/>
                  <rest xml:id="note-853" dur="8"/>
                  <chord xml:id="chord-0000001443178396" dur="8" stem.dir="down">
                    <note xml:id="note-854" oct="4" pname="e"/>
                    <note xml:id="note-855" oct="4" pname="c"/>
                    <note xml:id="note-856" oct="3" pname="a"/>
                  </chord>
                </layer>
              </staff>
              <dynam xml:id="dynam-0000000984424965" place="below" staff="1" tstamp="1.0">pp</dynam>
            </measure>
            <measure xml:id="measure-82-mdiv-1" n="82">
              <staff xml:id="staff-0000001503089162" n="1">
                <layer xml:id="layer-0000002029298334" n="1">
                  <beam xml:id="beam-0000001777387213">
                    <tuplet xml:id="tuplet-0000001327974041" num="3" numbase="2" num.place="above" num.visible="false" bracket.place="above" bracket.visible="false">
                      <note xml:id="note-857" dur="16" oct="4" pname="a" stem.dir="down"/>
                      <note xml:id="note-858" dur="16" oct="5" pname="c" stem.dir="down"/>
                      <note xml:id="note-859" dur="16" oct="5" pname="e" stem.dir="down"/>
                    </tuplet>
                  </beam>
                  <beam xml:id="beam-0000001960089799">
                    <tuplet xml:id="tuplet-0000001526754544" num="3" numbase="2" num.place="above" num.visible="false" bracket.place="above" bracket.visible="false">
                      <note xml:id="note-860" dur="16" oct="5" pname="a" stem.dir="down"/>
                      <note xml:id="note-861" dur="16" oct="6" pname="c" stem.dir="down"/>
                      <note xml:id="note-862" dur="16" oct="6" pname="e" stem.dir="down"/>
                    </tuplet>
                  </beam>
                  <beam xml:id="beam-0000000032863324">
                    <tuplet xml:id="tuplet-0000001111766325" num="3" numbase="2" num.place="above" num.visible="false" bracket.place="above" bracket.visible="false">
                      <note xml:id="note-863" dur="16" oct="6" pname="d" stem.dir="down"/>
                      <note xml:id="note-864" dur="16" oct="6" pname="c" stem.dir="down"/>
                      <note xml:id="note-865" dur="16" oct="5" pname="b" stem.dir="down"/>
                    </tuplet>
                  </beam>
                </layer>
              </staff>
              <staff xml:id="staff-0000000094096503" n="2">
                <layer xml:id="layer-0000001006008095" n="2">
                  <chord xml:id="chord-0000001681447643" dur="8" stem.dir="down">
                    <note xml:id="note-866" oct="4" pname="e"/>
                    <note xml:id="note-867" oct="4" pname="c"/>
                    <note xml:id="note-868" oct="3" pname="a"/>
                  </chord>
                  <rest xml:id="note-869" dur="8"/>
                  <chord xml:id="chord-0000001354022777" dur="8" stem.dir="down">
                    <note xml:id="note-870" oct="4" pname="e"/>
                    <note xml:id="note-871" oct="4" pname="c"/>
                    <note xml:id="note-872" oct="3" pname="a"/>
                  </chord>
                </layer>
              </staff>
            </measure>
            <measure xml:id="measure-83-mdiv-1" n="83">
              <staff xml:id="staff-0000000338256259" n="1">
                <layer xml:id="layer-0000000235594154" n="1">
                  <beam xml:id="beam-0000000184096558">
                    <tuplet xml:id="tuplet-0000000190342355" num="3" numbase="2" num.place="above" num.visible="false" bracket.place="above" bracket.visible="false">
                      <note xml:id="note-873" dur="16" oct="4" pname="a" stem.dir="down"/>
                      <note xml:id="note-874" dur="16" oct="5" pname="c" stem.dir="down"/>
                      <note xml:id="note-875" dur="16" oct="5" pname="e" stem.dir="down"/>
                    </tuplet>
                  </beam>
                  <beam xml:id="beam-0000001030417713">
                    <tuplet xml:id="tuplet-0000000775164343" num="3" numbase="2" num.place="above" num.visible="false" bracket.place="above" bracket.visible="false">
                      <note xml:id="note-876" dur="16" oct="5" pname="a" stem.dir="down"/>
                      <note xml:id="note-877" dur="16" oct="6" pname="c" stem.dir="down"/>
                      <note xml:id="note-878" dur="16" oct="6" pname="e" stem.dir="down"/>
                    </tuplet>
                  </beam>
                  <beam xml:id="beam-0000000954395728">
                    <tuplet xml:id="tuplet-0000001318429343" num="3" numbase="2" num.place="above" num.visible="false" bracket.place="above" bracket.visible="false">
                      <note xml:id="note-879" dur="16" oct="6" pname="d" stem.dir="down"/>
                      <note xml:id="note-880" dur="16" oct="6" pname="c" stem.dir="down"/>
                      <note xml:id="note-881" dur="16" oct="5" pname="b" stem.dir="down"/>
                    </tuplet>
                  </beam>
                </layer>
              </staff>
              <staff xml:id="staff-0000000439111842" n="2">
                <layer xml:id="layer-0000000529262024" n="2">
                  <chord xml:id="chord-0000001365715929" dur="8" stem.dir="down">
                    <note xml:id="note-882" oct="4" pname="e"/>
                    <note xml:id="note-883" oct="4" pname="c"/>
                    <note xml:id="note-884" oct="3" pname="a"/>
                  </chord>
                  <rest xml:id="note-885" dur="8"/>
                  <chord xml:id="chord-0000000204723500" dur="8" stem.dir="down">
                    <note xml:id="note-886" oct="4" pname="e"/>
                    <note xml:id="note-887" oct="4" pname="c"/>
                    <note xml:id="note-888" oct="3" pname="a"/>
                  </chord>
                </layer>
              </staff>
              <octave xml:id="octave-00001" staff="1" tstamp="1.0" tstamp2="1m+3.8000" extender="true" dis="8" dis.place="above"/>
            </measure>
            <measure xml:id="measure-84-mdiv-1" n="84">
              <staff xml:id="staff-0000000555185988" n="1">
                <layer xml:id="layer-0000000893159267" n="1">
                  <beam xml:id="beam-0000001329963203">
                    <tuplet xml:id="tuplet-0000001591094149" num="3" numbase="2" num.place="above" num.visible="false" bracket.place="above" bracket.visible="false">
                      <note xml:id="note-889" dur="16" oct="5" pname="b" stem.dir="down">
                        <accid xml:id="accid-0000001842474262" accid="f" accid.ges="f"/>
                      </note>
                      <note xml:id="note-890" dur="16" oct="5" pname="a" stem.dir="down"/>
                      <note xml:id="note-891" dur="16" oct="5" pname="g" stem.dir="down">
                        <accid xml:id="accid-0000000707103357" accid="s" accid.ges="s"/>
                      </note>
                    </tuplet>
                  </beam>
                  <beam xml:id="beam-0000000595735890">
                    <tuplet xml:id="tuplet-0000001131734692" num="3" numbase="2" num.place="above" num.visible="false" bracket.place="above" bracket.visible="false">
                      <note xml:id="note-892" dur="16" oct="5" pname="g" stem.dir="down">
                        <accid xml:id="accid-0000001971439861" accid="n" accid.ges="n"/>
                      </note>
                      <note xml:id="note-893" dur="16" oct="5" pname="f" stem.dir="down">
                        <accid xml:id="accid-0000001087870657" accid="s" accid.ges="s"/>
                      </note>
                      <note xml:id="note-894" dur="16" oct="5" pname="f" stem.dir="down">
                        <accid xml:id="accid-0000000616698787" accid="n" accid.ges="n"/>
                      </note>
                    </tuplet>
                  </beam>
                  <beam xml:id="beam-0000000209115907">
                    <tuplet xml:id="tuplet-0000002073732497" num="3" numbase="2" num.place="above" num.visible="false" bracket.place="above" bracket.visible="false">
                      <note xml:id="note-895" dur="16" oct="5" pname="e" stem.dir="down"/>
                      <note xml:id="note-896" dur="16" oct="5" pname="d" stem.dir="down">
                        <accid xml:id="accid-0000000813404324" accid="s" accid.ges="s"/>
                      </note>
                      <note xml:id="note-897" dur="16" oct="5" pname="d" stem.dir="down">
                        <accid xml:id="accid-0000000899089573" accid="n" accid.ges="n"/>
                      </note>
                    </tuplet>
                  </beam>
                </layer>
              </staff>
              <staff xml:id="staff-0000000611259192" n="2">
                <layer xml:id="layer-0000001873703497" n="2">
                  <chord xml:id="chord-0000000365025765" dur="8" stem.dir="down">
                    <note xml:id="note-898" oct="4" pname="e"/>
                    <note xml:id="note-899" oct="4" pname="c"/>
                    <note xml:id="note-900" oct="3" pname="a"/>
                  </chord>
                  <rest xml:id="note-901" dur="8"/>
                  <rest xml:id="note-902" dur="8"/>
                </layer>
              </staff>
            </measure>
            <sb xml:id="sb-0000000533227830"/>
            <measure xml:id="measure-85-mdiv-1" n="85">
              <staff xml:id="staff-0000001251715675" n="1">
                <layer xml:id="layer-0000001838244554" n="1">
                  <beam xml:id="beam-0000001997850359">
                    <tuplet xml:id="tuplet-0000001767217412" num="3" numbase="2" num.place="above" num.visible="false" bracket.place="above" bracket.visible="false">
                      <note xml:id="note-903" dur="16" oct="6" pname="c" stem.dir="down">
                        <accid xml:id="accid-0000001248543048" accid="s" accid.ges="s"/>
                      </note>
                      <note xml:id="note-904" dur="16" oct="6" pname="c" stem.dir="down">
                        <accid xml:id="accid-0000000604976186" accid="n" accid.ges="n"/>
                      </note>
                      <note xml:id="note-905" dur="16" oct="5" pname="b" stem.dir="down"/>
                    </tuplet>
                  </beam>
                  <beam xml:id="beam-0000000556712911">
                    <tuplet xml:id="tuplet-0000000639466601" num="3" numbase="2" num.place="above" num.visible="false" bracket.place="above" bracket.visible="false">
                      <note xml:id="note-906" dur="16" oct="5" pname="b" stem.dir="down">
                        <accid xml:id="accid-0000000541278495" accid="f" accid.ges="f"/>
                      </note>
                      <note xml:id="note-907" dur="16" oct="5" pname="a" stem.dir="down"/>
                      <note xml:id="note-908" dur="16" oct="5" pname="g" stem.dir="down">
                        <accid xml:id="accid-0000001349660323" accid="s" accid.ges="s"/>
                      </note>
                    </tuplet>
                  </beam>
                  <beam xml:id="beam-0000002134652384">
                    <tuplet xml:id="tuplet-0000000519446170" num="3" numbase="2" num.place="above" num.visible="false" bracket.place="above" bracket.visible="false">
                      <note xml:id="note-909" dur="16" oct="5" pname="g" stem.dir="down">
                        <accid xml:id="accid-0000000408503700" accid="n" accid.ges="n"/>
                      </note>
                      <note xml:id="note-910" dur="16" oct="5" pname="f" stem.dir="down">
                        <accid xml:id="accid-0000001758450420" accid="s" accid.ges="s"/>
                      </note>
                      <note xml:id="note-911" dur="16" oct="5" pname="f" stem.dir="down">
                        <accid xml:id="accid-0000001327641635" accid="n" accid.ges="n"/>
                      </note>
                    </tuplet>
                  </beam>
                </layer>
              </staff>
              <staff xml:id="staff-0000002076166262" n="2">
                <layer xml:id="layer-0000000149263870" n="2">
                  <mRest xml:id="note-912"/>
                </layer>
              </staff>
            </measure>
          </section>
        </score>
      </mdiv>
    </body>
  </music>
</mei>

Implementation details

  • If oct.ges is still provided, then it will be preferred over any calculated shift due to octaves.
  • The octaves are collected in InitMIDI, because they are encoded after the affected notes and chords.
  • The detection whether a note/chord lies within the octave is based on alignments. This approach is due to the technical difficulty that timestamp elements are not visited in the GenerateMIDIFunctor - they have layer zero. To make this work we introduce an ordering on alignments.
  • There are still some edge cases which will not work, e.g., octaves starting at a beam element which does not have an alignment.

@lpugin lpugin merged commit e535b90 into rism-digital:develop Jan 6, 2025
5 checks passed
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.

2 participants