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

Tuplet parsing issue in measure 125 of Chopin/Sonata_2/1st_no_repeat #420

Open
leleogere opened this issue Jan 27, 2025 · 4 comments · May be fixed by #423
Open

Tuplet parsing issue in measure 125 of Chopin/Sonata_2/1st_no_repeat #420

leleogere opened this issue Jan 27, 2025 · 4 comments · May be fixed by #423

Comments

@leleogere
Copy link
Collaborator

leleogere commented Jan 27, 2025

Here are the measures 125 and 126 of Chopin/Sonata_2/1st_no_repeat:

Image

They each contain 3 tuplets, two on voice 5 and one on voice 6. However, when parsing the score with the following code, we can see that only two of them:

from pathlib import Path

import partitura as pt
import partitura.score

score_path = Path("asap-dataset/Chopin/Sonata_2/1st_no_repeat/xml_score.musicxml")

score = pt.load_musicxml(score_path)
score = score.parts[0]

measure_map = score.measure_number_map
printed_measures = set()  # To avoid printing the measure number each time

for element in score.iter_all(cls=pt.score.Tuplet):
    # Print measure number for first element of measure
    if (measure := int(measure_map(element.start.t))) not in printed_measures:
        print(f"Measure {measure}")
        printed_measures.add(measure)
    print(f"  * {str(element)}")
    print(f"      - start_note = {str(element.start_note)}")
    print(f"      - end_note   = {str(element.end_note)}")
[...]
Measure 125
  * 11916--11964 Tuplet start=None end=None actual_notes=3 normal_notes=2 actual_type=quarter normal_type=quarter
      - start_note = 11916--11932 Note id=None voice=5 staff=2 type=quarter_3/2 pitch=E2
      - end_note   = 11948--11964 Note id=None voice=5 staff=2 type=quarter_3/2 pitch=G3
  * 11964--12012 Tuplet start=None end=None actual_notes=3 normal_notes=2 actual_type=quarter normal_type=quarter
      - start_note = 11964--11980 Note id=None voice=5 staff=2 type=quarter_3/2 pitch=F#3
      - end_note   = 11996--12012 Note id=None voice=5 staff=2 type=quarter_3/2 pitch=D2
Measure 126
  * 12012--11964 Tuplet start=None end=None actual_notes=3 normal_notes=2 actual_type=quarter normal_type=quarter
      - start_note = 12012--12028 Note id=None voice=5 staff=2 type=quarter_3/2 pitch=F2
      - end_note   = 11932--11964 Note id=None voice=6 staff=2 type=half_3/2 pitch=A2
  * 12060--12060 Tuplet start=None end=None actual_notes=3 normal_notes=2 actual_type=quarter normal_type=quarter
      - start_note = 12060--12076 Note id=None voice=5 staff=2 type=quarter_3/2 pitch=G#3
      - end_note   = 12044--12060 Note id=None voice=5 staff=2 type=quarter_3/2 pitch=A3
[...]

More interestingly, when I'm extracting those two measures and pasting them into a new empty score on its own, the parsing seems to work as expected:

Measure 1
  * 0--12 Tuplet start=None end=None actual_notes=3 normal_notes=2 actual_type=quarter normal_type=quarter
      - start_note = 0--4 Note id=None voice=5 staff=2 type=quarter_3/2 pitch=E2
      - end_note   = 8--12 Note id=None voice=5 staff=2 type=quarter_3/2 pitch=G3
  * 0--12 Tuplet start=None end=None actual_notes=3 normal_notes=2 actual_type=quarter normal_type=quarter
      - start_note = 0--4 Rest id=None voice=6 staff=2 type=quarter_3/2
      - end_note   = 4--12 Note id=None voice=6 staff=2 type=half_3/2 pitch=A2
  * 12--24 Tuplet start=None end=None actual_notes=3 normal_notes=2 actual_type=quarter normal_type=quarter
      - start_note = 12--16 Note id=None voice=5 staff=2 type=quarter_3/2 pitch=F#3
      - end_note   = 20--24 Note id=None voice=5 staff=2 type=quarter_3/2 pitch=D2
Measure 2
  * 24--36 Tuplet start=None end=None actual_notes=3 normal_notes=2 actual_type=quarter normal_type=quarter
      - start_note = 24--28 Note id=None voice=5 staff=2 type=quarter_3/2 pitch=F2
      - end_note   = 32--36 Note id=None voice=5 staff=2 type=quarter_3/2 pitch=A3
  * 24--36 Tuplet start=None end=None actual_notes=3 normal_notes=2 actual_type=quarter normal_type=quarter
      - start_note = 24--28 Rest id=None voice=6 staff=2 type=quarter_3/2
      - end_note   = 28--36 Note id=None voice=6 staff=2 type=half_3/2 pitch=B2
  * 36--48 Tuplet start=None end=None actual_notes=3 normal_notes=2 actual_type=quarter normal_type=quarter
      - start_note = 36--40 Note id=None voice=5 staff=2 type=quarter_3/2 pitch=G#3
      - end_note   = 44--48 Note id=None voice=5 staff=2 type=quarter_3/2 pitch=E2

I'm going to investigate a bit about that, but if you have any insights about the origin of this bug it would save me some time!

Another note, the start note and the end note of the first tuplet in measure 126 belong to different voices, which should not be possible afaik. However, this does happen quite often in other measures (e.g. measure 127, 128, 131...) so it might be a completely unrelated issue. Should I open another issue to deal with that?

@leleogere
Copy link
Collaborator Author

The issue was caused by a malformed XML (what a surprise) : the first rest of the voice 6 didn't have the <tuplet type="start"> tag, there were only the <tuplet type="stop"> in the half note. MuseScore was able to fix the bug itself, explaining why it seems OK on the rendered image. I'll fill a PR to ASAP to fix it.

@leleogere
Copy link
Collaborator Author

leleogere commented Jan 27, 2025

It seems that the measures 133, 134 and 135 were also problematic for this same missing tuplet start tag. After adding back all the missing tags, the second issue, the one assigning start/end_note of the tuplet to different voices, was gone too. It was likely due to tuplets extending across ranges that they shouldn't because of the missing tags. However, this mismatch could be a good indication that the score is malformed. Should I add an assert to fail the parsing when the end_note of a tuplet is not in the same voice as the start_note? Or at least a warning?

@leleogere leleogere reopened this Jan 27, 2025
@manoskary
Copy link
Member

Hello good catch. Adding a warning would be definitely nice. I was also wondering if we should have a similar approach to musescore and correct this on import with the warning of course.

@leleogere
Copy link
Collaborator Author

I might be possible to try to fix it ourselves, however, I guess that there could be quite complex cases happening (nested tuplets?). In simple cases, I guess a naive approach would be to:

  • Find an additional <tuplet type="stop"> without pending <tuplet type="start"> registered.
  • In that case, iterate backward over previous notes of the same voice, as long as they have the same <time-modification> as the "end" note, and they do not have any <tuplet> attribute.
    • Should we search only on the current measure? Tuplets crossing bar lines are extremely rare;
  • Set the start_note of the tuplet to the last note verifying the previous conditions.

In case of missing <tuplet type="stop">, the same could be applied but iterating forward from the <tuplet type="start"> rather than backward. However, detecting missing stops might be a bit more difficult. Indeed, in the case of a missing start, you detect it right away when you encounter a stop without having found a start before, but for missing stops, you need some rules that say "the tuplet should be ended by now, there must be a stop missing".

I'll open a PR to at least add a warning, and we can discuss the correction there if you think it is worth implementing this.

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 a pull request may close this issue.

2 participants