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

Add optional attributes to tuplets related to their time modification #414

Merged
merged 7 commits into from
Jan 23, 2025
18 changes: 15 additions & 3 deletions partitura/io/exportmusicxml.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,9 +227,21 @@ def make_note_el(note, dur, voice, counter, n_of_staves):
else:
del counter[tuplet_key]

notations.append(
etree.Element("tuplet", number="{}".format(number), type="start")
)
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:
# 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-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)
notations.append(tuplet_e)

if notations:
notations_e = etree.SubElement(note_e, "notations")
Expand Down
34 changes: 33 additions & 1 deletion partitura/io/importmusicxml.py
Original file line number Diff line number Diff line change
Expand Up @@ -1441,16 +1441,48 @@ def handle_tuplets(notations, ongoing, note):
stop_tuplet_key = ("stop_tuplet", tuplet_number)

if tuplet_type == "start":
# Get information about the tuplet if present in the XML
tuplet_actual = tuplet_e.find("tuplet-actual")
tuplet_normal = tuplet_e.find("tuplet-normal")
if tuplet_actual is not None and tuplet_normal is not None:
tuplet_actual_notes = int(tuplet_actual.find("tuplet-number").text)
tuplet_actual_type = tuplet_actual.find("tuplet-type").text
tuplet_normal_notes = int(tuplet_normal.find("tuplet-number").text)
tuplet_normal_type = tuplet_normal.find("tuplet-type").text
leleogere marked this conversation as resolved.
Show resolved Hide resolved
# 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
elif (
"actual_notes" in note.symbolic_duration
and "normal_notes" in note.symbolic_duration
and "type" in note.symbolic_duration
):
tuplet_actual_notes = note.symbolic_duration["actual_notes"]
tuplet_normal_notes = note.symbolic_duration["normal_notes"]
tuplet_type = note.symbolic_duration["type"]

# If no information is present in the XML or the note, then set to None
else:
tuplet_actual_notes = None
tuplet_normal_notes = None
tuplet_type = None

leleogere marked this conversation as resolved.
Show resolved Hide resolved

# 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)
tuplet = score.Tuplet(note, actual_notes=tuplet_actual_notes, normal_notes=tuplet_normal_notes, type=tuplet_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

starting_tuplets.append(tuplet)

Expand Down
15 changes: 13 additions & 2 deletions partitura/score.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from copy import copy, deepcopy
from collections import defaultdict
from collections.abc import Iterable
from fractions import Fraction
from numbers import Number
from partitura.utils.globals import (
MUSICAL_BEATS,
Expand Down Expand Up @@ -2401,12 +2402,15 @@ class Tuplet(TimedObject):

"""

def __init__(self, start_note=None, end_note=None):
def __init__(self, start_note=None, end_note=None, actual_notes=None, normal_notes=None, 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
# maintain a list of attributes to update when cloning this instance
self._ref_attrs.extend(["start_note", "end_note"])

Expand Down Expand Up @@ -2444,10 +2448,17 @@ def end_note(self, note):
note.tuplet_stops.append(self)
self._end_note = note

@property
def duration_multipler(self) -> Fraction:
return Fraction(self.normal_notes, self.actual_notes)
leleogere marked this conversation as resolved.
Show resolved Hide resolved

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)
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)).strip()
return " ".join((super().__str__(), start, end, n_actual, n_normal, type_)).strip()


class Repeat(TimedObject):
Expand Down
4 changes: 4 additions & 0 deletions tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@
)
]
]
MUSICXML_TUPLET_ATTRIBUTES_TESTFILES = [
os.path.join(MUSICXML_PATH, fn)
for fn in ["test_tuplet_attributes.musicxml"]
]

MUSICXML_UNFOLD_COMPLEX = [
(
Expand Down
Loading
Loading