Skip to content

Commit

Permalink
Fix the tuplet attribute parser in case of actual-type != normal-type
Browse files Browse the repository at this point in the history
  • Loading branch information
leleogere committed Jan 22, 2025
1 parent f882d53 commit cd0e308
Show file tree
Hide file tree
Showing 5 changed files with 252 additions and 54 deletions.
11 changes: 8 additions & 3 deletions partitura/io/exportmusicxml.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,19 +228,24 @@ def make_note_el(note, dur, voice, counter, n_of_staves):
del counter[tuplet_key]

tuplet_e = etree.Element("tuplet", number="{}".format(number), type="start")
if tuplet.actual_notes is not None and tuplet.normal_notes is not None and tuplet.type is not None:
if (
tuplet.actual_notes is not None
and tuplet.normal_notes is not None
and tuplet.actual_type is not None
and tuplet.normal_type is not None
):
# tuplet-actual tag
tuplet_actual_e = etree.SubElement(tuplet_e, "tuplet-actual")
tuplet_actual_notes_e = etree.SubElement(tuplet_actual_e, "tuplet-number")
tuplet_actual_notes_e.text = str(tuplet.actual_notes)
tuplet_actual_type_e = etree.SubElement(tuplet_actual_e, "tuplet-type")
tuplet_actual_type_e.text = str(tuplet.type)
tuplet_actual_type_e.text = str(tuplet.actual_type)
# tuplet-normal tag
tuplet_normal_e = etree.SubElement(tuplet_e, "tuplet-normal")
tuplet_normal_notes_e = etree.SubElement(tuplet_normal_e, "tuplet-number")
tuplet_normal_notes_e.text = str(tuplet.normal_notes)
tuplet_normal_type_e = etree.SubElement(tuplet_normal_e, "tuplet-type")
tuplet_normal_type_e.text = str(tuplet.type)
tuplet_normal_type_e.text = str(tuplet.normal_type)
notations.append(tuplet_e)

if notations:
Expand Down
23 changes: 14 additions & 9 deletions partitura/io/importmusicxml.py
Original file line number Diff line number Diff line change
Expand Up @@ -1492,36 +1492,41 @@ def handle_tuplets(notations, ongoing, note):
tuplet_actual_type = get_value_from_tag(tuplet_actual, "tuplet-type", str)
tuplet_normal_notes = get_value_from_tag(tuplet_normal, "tuplet-number", int)
tuplet_normal_type = get_value_from_tag(tuplet_normal, "tuplet-type", str)
# Types should always be the same I think?
assert tuplet_actual_type == tuplet_normal_type, "Tuplet types are not the same"
tuplet_type = tuplet_actual_type
# If no information, try to infer it from the note
else:
tuplet_actual_notes = note.symbolic_duration.get("actual_notes", None)
tuplet_normal_notes = note.symbolic_duration.get("normal_notes", None)
tuplet_type = note.symbolic_duration.get("type", None)
tuplet_actual_type = note.symbolic_duration.get("type", None)
tuplet_normal_type = tuplet_actual_type

# If anyone of the attributes is not set, we set them all to None as we can't really
# do anything useful with only partial information about the tuplet
if None in (tuplet_actual_notes, tuplet_normal_notes, tuplet_type):
if None in (tuplet_actual_notes, tuplet_normal_notes, tuplet_actual_type, tuplet_normal_type):
tuplet_actual_notes = None
tuplet_normal_notes = None
tuplet_type = None

tuplet_actual_type = None
tuplet_normal_type = None

# check if we have a stopped_tuplet in ongoing that corresponds to
# this start
tuplet = ongoing.pop(stop_tuplet_key, None)

if tuplet is None:
tuplet = score.Tuplet(note, actual_notes=tuplet_actual_notes, normal_notes=tuplet_normal_notes, type=tuplet_type)
tuplet = score.Tuplet(
note,
actual_notes=tuplet_actual_notes,
normal_notes=tuplet_normal_notes,
actual_type=tuplet_actual_type,
normal_type=tuplet_normal_type,
)
ongoing[start_tuplet_key] = tuplet

else:
tuplet.start_note = note
tuplet.actual_notes = tuplet_actual_notes
tuplet.normal_notes = tuplet_normal_notes
tuplet.type = tuplet_type
tuplet.actual_type = tuplet_actual_type
tuplet.normal_type = tuplet_normal_type

starting_tuplets.append(tuplet)

Expand Down
30 changes: 25 additions & 5 deletions partitura/score.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
MUSICAL_BEATS,
INTERVALCLASSES,
INTERVAL_TO_SEMITONES,
LABEL_DURS,
)
import warnings, sys
import numpy as np
Expand Down Expand Up @@ -2402,15 +2403,24 @@ class Tuplet(TimedObject):
"""

def __init__(self, start_note=None, end_note=None, actual_notes=None, normal_notes=None, type=None):
def __init__(
self,
start_note=None,
end_note=None,
actual_notes=None,
normal_notes=None,
actual_type=None,
normal_type=None
):
super().__init__()
self._start_note = None
self._end_note = None
self.start_note = start_note
self.end_note = end_note
self.actual_notes = actual_notes
self.normal_notes = normal_notes
self.type = type
self.actual_type = actual_type
self.normal_type = normal_type
# maintain a list of attributes to update when cloning this instance
self._ref_attrs.extend(["start_note", "end_note"])

Expand Down Expand Up @@ -2457,15 +2467,25 @@ def duration_multiplier(self) -> Fraction:
For example, in a triplet of eighth notes, each eighth note would have a duration of
duration_multiplier * normal_eighth_duration = 2/3 * normal_eighth_duration
"""
return Fraction(self.normal_notes, self.actual_notes)
if self.actual_type == self.normal_type:
return Fraction(self.normal_notes, self.actual_notes)
else:
# In that case, we need to convert the normal_type into the actual_type, therefore
# adapting normal_notes
actual_dur = Fraction(LABEL_DURS[self.actual_type])
normal_dur = Fraction(LABEL_DURS[self.normal_type])
return Fraction(self.normal_notes, self.actual_notes) * normal_dur / actual_dur



def __str__(self):
n_actual = "" if self.actual_notes is None else "actual_notes={}".format(self.actual_notes)
n_normal = "" if self.normal_notes is None else "normal_notes={}".format(self.normal_notes)
type_ = "" if self.type is None else "type={}".format(self.type)
t_actual = "" if self.actual_type is None else "actual_type={}".format(self.actual_type)
t_normal = "" if self.normal_type is None else "normal_type={}".format(self.normal_type)
start = "" if self.start_note is None else "start={}".format(self.start_note.id)
end = "" if self.end_note is None else "end={}".format(self.end_note.id)
return " ".join((super().__str__(), start, end, n_actual, n_normal, type_)).strip()
return " ".join((super().__str__(), start, end, n_actual, n_normal, t_actual, t_normal)).strip()


class Repeat(TimedObject):
Expand Down
Loading

0 comments on commit cd0e308

Please sign in to comment.