de-men / music_xml

A Dart package project to parse MusicXML.
https://pub.dev/packages/music_xml
BSD 3-Clause "New" or "Revised" License
7 stars 6 forks source link

Problem with double tied notes #6

Closed synchronisator closed 1 year ago

synchronisator commented 1 year ago

Hi,

i tried to use your library and found an issue with more than one tie. image

The music xml is this:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 3.1 Partwise//EN" "http://www.musicxml.org/dtds/partwise.dtd">
<score-partwise version="3.1">
  <work>
    <work-title></work-title>
    </work>
  <identification>
    <creator type="composer"></creator>
    <rights>Copyright © </rights>
    <encoding>
      <software>MuseScore 3.6.2</software>
      <encoding-date>2023-04-07</encoding-date>
      <supports element="accidental" type="yes"/>
      <supports element="beam" type="yes"/>
      <supports element="print" attribute="new-page" type="yes" value="yes"/>
      <supports element="print" attribute="new-system" type="yes" value="yes"/>
      <supports element="stem" type="yes"/>
      </encoding>
    </identification>
  <defaults>
    <scaling>
      <millimeters>5.80112</millimeters>
      <tenths>40</tenths>
      </scaling>
    <page-layout>
      <page-height>2047.88</page-height>
      <page-width>1448</page-width>
      <page-margins type="even">
        <left-margin>68.9522</left-margin>
        <right-margin>68.9522</right-margin>
        <top-margin>68.9522</top-margin>
        <bottom-margin>137.904</bottom-margin>
        </page-margins>
      <page-margins type="odd">
        <left-margin>68.9522</left-margin>
        <right-margin>68.9522</right-margin>
        <top-margin>68.9522</top-margin>
        <bottom-margin>137.904</bottom-margin>
        </page-margins>
      </page-layout>
    <word-font font-family="FreeSerif" font-size="10"/>
    <lyric-font font-family="Plantin MT Std" font-size="9.5049"/>
    </defaults>
  <credit page="1">
    <credit-type>rights</credit-type>
    <credit-words default-x="724" default-y="137.904" justify="center" valign="bottom">Copyright © </credit-words>
    </credit>
  <part-list>
    <part-group type="start" number="1">
      <group-symbol>bracket</group-symbol>
      </part-group>
    <score-part id="P1">
      <part-name>SOPRAN</part-name>
      <part-abbreviation>S.</part-abbreviation>
      <score-instrument id="P1-I1">
        <instrument-name>Soprano</instrument-name>
        </score-instrument>
      <midi-device id="P1-I1" port="1"></midi-device>
      <midi-instrument id="P1-I1">
        <midi-channel>1</midi-channel>
        <midi-program>1</midi-program>
        <volume>78.7402</volume>
        <pan>0</pan>
        </midi-instrument>
      </score-part>
    <part-group type="stop" number="1"/>
    </part-list>
  <part id="P1">
    <measure number="1" width="305.51">
      <print>
        <system-layout>
          <system-margins>
            <left-margin>56.35</left-margin>
            <right-margin>948.23</right-margin>
            </system-margins>
          <top-system-distance>70.00</top-system-distance>
          </system-layout>
        </print>
      <attributes>
        <divisions>1</divisions>
        <key>
          <fifths>0</fifths>
          </key>
        <clef>
          <sign>G</sign>
          <line>2</line>
          </clef>
        </attributes>
      <note default-x="46.59" default-y="-15.00">
        <pitch>
          <step>C</step>
          <octave>5</octave>
          </pitch>
        <duration>2</duration>
        <tie type="start"/>
        <voice>1</voice>
        <type>half</type>
        <stem>down</stem>
        <notations>
          <tied type="start"/>
          </notations>
        </note>
      <note default-x="46.59" default-y="-5.00">
        <chord/>
        <pitch>
          <step>E</step>
          <octave>5</octave>
          </pitch>
        <duration>2</duration>
        <tie type="start"/>
        <voice>1</voice>
        <type>half</type>
        <stem>down</stem>
        <notations>
          <tied type="start"/>
          </notations>
        </note>
      <note default-x="170.73" default-y="-15.00">
        <pitch>
          <step>C</step>
          <octave>5</octave>
          </pitch>
        <duration>2</duration>
        <tie type="stop"/>
        <voice>1</voice>
        <type>half</type>
        <stem>down</stem>
        <notations>
          <tied type="stop"/>
          </notations>
        </note>
      <note default-x="170.73" default-y="-5.00">
        <chord/>
        <pitch>
          <step>E</step>
          <octave>5</octave>
          </pitch>
        <duration>2</duration>
        <tie type="stop"/>
        <voice>1</voice>
        <type>half</type>
        <stem>down</stem>
        <notations>
          <tied type="stop"/>
          </notations>
        </note>
      <barline location="right">
        <bar-style>light-heavy</bar-style>
        </barline>
      </measure>
    </part>
  </score-partwise>

When parsing this i got: [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: 'package:music_xml/src/part.dart': Failed assertion: line 66 pos 18: 'notes != null': is not true.

It works if i remove

        <tie type="start"/>

and

        <notations>
          <tied type="start"/>
          </notations>

from the notes 2 and 4

synchronisator commented 1 year ago

To be more specific. This is only a problem with multiple ties in the same voice! I just looked at the code to find the problem and it seems to be here: lib/src/part.dart:58

        if (currentNote.isNoteOn) {
          tiedNotes[currentNote.voice] = {
            currentNote.pitch!.value: [currentNote],
          };
        }

Tied Notes are seperated by voice. But in my case there are two notes in the same voice, starting a tie. The second note overrides the content in the tiedNotes Array, so the following final notes = tiedNotes[currentNote.voice]?[currentNote.pitch!.value]; Cant find the first note.

I am not sure how to solve this, but i think this is the problem.

synchronisator commented 1 year ago

I think the Problem can be solved, adding new pitches to the Map:

Current code:

        // If note is note on, create a new entry in tiedNotes
        if (currentNote.isNoteOn) {
          tiedNotes[currentNote.voice] = {
            currentNote.pitch!.value: [currentNote],
          };
        }

Bugfix:

        // If note is note on, create a new entry in tiedNotes
        if (currentNote.isNoteOn) {
          if(!tiedNotes.containsKey(currentNote.voice)){
            tiedNotes[currentNote.voice] = {};
          }
          tiedNotes[currentNote.voice]!.putIfAbsent(currentNote.pitch!.value, () => [currentNote]);
        }

In my testcase this is working, but i cant say if the outcome of this method is still correct. Maybe someone else could verify this solution.

Thanks

thaihuynhxyz commented 1 year ago

fixed at #9