From c145754940b7cb6f6f0dde5cd7fb272d4d7f3dda Mon Sep 17 00:00:00 2001 From: Reinhold Kainhofer Date: Fri, 21 Nov 2008 22:26:24 +0100 Subject: [PATCH] MusicXML: Implement conversion of non-standard tuplet numbers and note types --- .../12f-Tuplet-Display-NonStandard.xml | 615 ++++++++++++++++++ python/musicexp.py | 31 +- python/musicxml.py | 45 +- scripts/musicxml2ly.py | 76 ++- 4 files changed, 746 insertions(+), 21 deletions(-) create mode 100644 input/regression/musicxml/12f-Tuplet-Display-NonStandard.xml diff --git a/input/regression/musicxml/12f-Tuplet-Display-NonStandard.xml b/input/regression/musicxml/12f-Tuplet-Display-NonStandard.xml new file mode 100644 index 0000000000..a0810b4fcb --- /dev/null +++ b/input/regression/musicxml/12f-Tuplet-Display-NonStandard.xml @@ -0,0 +1,615 @@ + + + + + + Displaying tuplet note types, + that might not coincide with the displayed note. The first two tuplets + take the type from the note, the second two from the + <time-modification> element, the remaining pair of tuplets from the + <tuplet> notation element. The tuplets in measure 3 specify both + a number of notes and a type inside the <tuplet-actual> and + <tuplet-normal> elements, the ones in measure 4 specify only a + note type (but no number), and the ones in measure 5 specify only a + number of tuplet-notes (but no type, which is deduced from the + note's type). The first tuplet of measures 3-5 uses + 'display-type="actual"', the second one 'display-type="both"'. + + + + + MusicXML Part + + + + + + + 408 + + 0 + major + + + + G + 2 + + + + + C + 5 + + 136 + 1 + eighth + + 3 + 2 + + + + + + + + C + 5 + + 136 + 1 + eighth + + 3 + 2 + + + + + C + 5 + + 136 + 1 + eighth + + 3 + 2 + + + + + + + + C + 5 + + 408 + 1 + quarter + + + 3 + 2 + + + + + + + + C + 5 + + 408 + 1 + quarter + + + 3 + 2 + + + + + C + 5 + + 408 + 1 + quarter + + + 3 + 2 + + + + + + + + + + + C + 5 + + 136 + 1 + eighth + + 3 + 2 + breve + + + + + + + + C + 5 + + 136 + 1 + eighth + + 3 + 2 + breve + + + + + C + 5 + + 136 + 1 + eighth + + 3 + 2 + breve + + + + + + + + C + 5 + + 408 + 1 + quarter + + + 3 + 2 + breve + + + + + + + + + + C + 5 + + 408 + 1 + quarter + + + 3 + 2 + breve + + + + + + + C + 5 + + 408 + 1 + quarter + + + 3 + 2 + breve + + + + + + + + + + + + + C + 5 + + 136 + 1 + eighth + + 3 + 2 + breve + + + + + 7 + quarter + + + + 5 + quarter + + + + + + + + C + 5 + + 136 + 1 + eighth + + 3 + 2 + breve + + + + + C + 5 + + 136 + 1 + eighth + + 3 + 2 + breve + + + + + + + + C + 5 + + 408 + 1 + quarter + + + 3 + 2 + breve + + + + + + + 7 + half + + + + 5 + 16th + + + + + + + C + 5 + + 408 + 1 + quarter + + + 3 + 2 + breve + + + + + + + C + 5 + + 408 + 1 + quarter + + + 3 + 2 + breve + + + + + + + + + + + + + C + 5 + + 136 + 1 + eighth + + 3 + 2 + breve + + + + + quarter + + + + quarter + + + + + + + + C + 5 + + 136 + 1 + eighth + + 3 + 2 + breve + + + + + C + 5 + + 136 + 1 + eighth + + 3 + 2 + breve + + + + + + + + C + 5 + + 408 + 1 + quarter + + + 3 + 2 + breve + + + + + + + half + + + + 16th + + + + + + + C + 5 + + 408 + 1 + quarter + + + 3 + 2 + breve + + + + + + + C + 5 + + 408 + 1 + quarter + + + 3 + 2 + breve + + + + + + + + + + + + + C + 5 + + 136 + 1 + eighth + + 3 + 2 + + + + + 7 + + + 5 + + + + + + + C + 5 + + 136 + 1 + eighth + + 3 + 2 + + + + + C + 5 + + 136 + 1 + eighth + + 3 + 2 + + + + + + + + C + 5 + + 408 + 1 + quarter + + + 3 + 2 + + + + + 7 + + + + 5 + + + + + + + C + 5 + + 408 + 1 + quarter + + + 3 + 2 + + + + + C + 5 + + 408 + 1 + quarter + + + 3 + 2 + + + + + + + light-heavy + + + + + diff --git a/python/musicexp.py b/python/musicexp.py index bdd7074761..6909adcdf8 100644 --- a/python/musicexp.py +++ b/python/musicexp.py @@ -454,10 +454,16 @@ class RelativeMusic (MusicWrapper): class TimeScaledMusic (MusicWrapper): def __init__ (self): MusicWrapper.__init__ (self) + self.numerator = 1 + self.denominator = 1 self.display_number = "actual" # valid values "actual" | "both" | None # Display the basic note length for the tuplet: self.display_type = None # value values "actual" | "both" | None self.display_bracket = "bracket" # valid values "bracket" | "curved" | None + self.actual_type = None # The actually played unit of the scaling + self.normal_type = None # The basic unit of the scaling + self.display_numerator = None + self.display_denominator = None def print_ly (self, func): if self.display_bracket == None: @@ -471,21 +477,36 @@ class TimeScaledMusic (MusicWrapper): base_number_function = {None: "#f", "actual": "tuplet-number::calc-denominator-text", "both": "tuplet-number::calc-fraction-text"}.get (self.display_number, None) + # If we have non-standard numerator/denominator, use our custom function + if self.display_number == "actual" and self.display_denominator: + base_number_function = "(tuplet-number::non-default-tuplet-denominator-text %s)" % self.display_denominator + elif self.display_number == "both" and (self.display_denominator or self.display_numerator): + if self.display_numerator: + num = self.display_numerator + else: + num = "#f" + if self.display_denominator: + den = self.display_denominator + else: + den = "#f" + base_number_function = "(tuplet-number::non-default-tuplet-fraction-text %s %s)" % (den, num) + - if self.display_type == "actual": - base_duration = "8" # TODO!!! + if self.display_type == "actual" and self.normal_type: + # Obtain the note duration in scheme-mode, i.e. \longa as \\longa + base_duration = self.normal_type.ly_expression (None, True) func ("\\once \\override TupletNumber #'text = #(tuplet-number::append-note-wrapper %s \"%s\")" % (base_number_function, base_duration)) func.newline () - elif self.display_type == None: + elif self.display_type == "both": # TODO: Implement this using actual_type and normal_type! + warning (_ ("Tuplet brackets displaying both note durations are not implemented, using default")) if self.display_number == None: func ("\\once \\override TupletNumber #'stencil = ##f") func.newline () elif self.display_number == "both": func ("\\once \\override TupletNumber #'text = #%s" % base_number_function) func.newline () - elif self.display_type == "both": - warning (_ ("Tuplet brackets displaying both note durations are not implemented, using default")) + else: if self.display_number == None: func ("\\once \\override TupletNumber #'stencil = ##f") func.newline () diff --git a/python/musicxml.py b/python/musicxml.py index f3487d4a8e..0c06264812 100644 --- a/python/musicxml.py +++ b/python/musicxml.py @@ -863,6 +863,16 @@ class Time_modification(Music_xml_node): a = self.get_maybe_exist_named_child ('normal-notes') return (int(a.get_text ()), int (b.get_text ())) + def get_normal_type (self): + tuplet_type = self.get_maybe_exist_named_child ('normal-type') + if tuplet_type: + dots = self.get_named_children ('normal-dot') + log = musicxml_duration_to_log (tuplet_type.get_text ().strip ()) + return (log , len (dots)) + else: + return None + + class Accidental (Music_xml_node): def __init__ (self): Music_xml_node.__init__ (self) @@ -885,7 +895,40 @@ class Wedge (Music_xml_spanner): pass class Tuplet (Music_xml_spanner): - pass + def duration_info_from_tuplet_note (self, tuplet_note): + tuplet_type = tuplet_note.get_maybe_exist_named_child ('tuplet-type') + if tuplet_type: + dots = tuplet_note.get_named_children ('tuplet-dot') + log = musicxml_duration_to_log (tuplet_type.get_text ().strip ()) + return (log, len (dots)) + else: + return None + + # Return tuplet note type as (log, dots) + def get_normal_type (self): + tuplet = self.get_maybe_exist_named_child ('tuplet-normal') + if tuplet: + return self.duration_info_from_tuplet_note (tuplet) + else: + return None + + def get_actual_type (self): + tuplet = self.get_maybe_exist_named_child ('tuplet-actual') + if tuplet: + return self.duration_info_from_tuplet_note (tuplet) + else: + return None + + def get_tuplet_note_count (self, tuplet_note): + if tuplet_note: + tuplet_nr = tuplet_note.get_maybe_exist_named_child ('tuplet-number') + if tuplet_nr: + return int (tuplet_nr.get_text ()) + return None + def get_normal_nr (self): + return self.get_tuplet_note_count (self.get_maybe_exist_named_child ('tuplet-normal')) + def get_actual_nr (self): + return self.get_tuplet_note_count (self.get_maybe_exist_named_child ('tuplet-actual')) class Bracket (Music_xml_spanner): pass diff --git a/scripts/musicxml2ly.py b/scripts/musicxml2ly.py index 529de96020..326a6d62eb 100644 --- a/scripts/musicxml2ly.py +++ b/scripts/musicxml2ly.py @@ -88,6 +88,19 @@ eyeglasses = \markup { \with-dimensions #'(0 . 4.4) #'(0 . 2.5) \postscript #ey ) )""", + "tuplet-non-default-denominator": """#(define ((tuplet-number::non-default-tuplet-denominator-text denominator) grob) + (number->string (if denominator + denominator + (ly:event-property (event-cause grob) 'denominator)))) +""", + + "tuplet-non-default-fraction": """#(define ((tuplet-number::non-default-tuplet-fraction-text denominator numerator) grob) + (let* ((ev (event-cause grob)) + (den (if denominator denominator (ly:event-property ev 'denominator))) + (num (if numerator numerator (ly:event-property ev 'numerator)))) + (format "~a:~a" den num))) +""" + } def round_to_two_digits (val): @@ -610,11 +623,43 @@ def group_repeats (music_list): # TODO: Implement repeats until the end without explicit ending bar return music_list -def musicxml_tuplet_to_lily (tuplet_elt, fraction): + +# Extract the settings for tuplets from the and the +# elements of the note: +def musicxml_tuplet_to_lily (tuplet_elt, time_modification): tsm = musicexp.TimeScaledMusic () + fraction = (1,1) + if time_modification: + fraction = time_modification.get_fraction () tsm.numerator = fraction[0] tsm.denominator = fraction[1] + + normal_type = tuplet_elt.get_normal_type () + if not normal_type and time_modification: + normal_type = time_modification.get_normal_type () + if not normal_type and time_modification: + note = time_modification.get_parent () + if note: + normal_type = note.get_duration_info () + if normal_type: + normal_note = musicexp.Duration () + (normal_note.duration_log, normal_note.dots) = normal_type + tsm.normal_type = normal_note + + actual_type = tuplet_elt.get_actual_type () + if actual_type: + actual_note = musicexp.Duration () + (actual_note.duration_log, actual_note.dots) = normal_type + tsm.actual_type = actual_note + + # Obtain non-default nrs of notes from the tuplet object! + tsm.display_numerator = tuplet_elt.get_normal_nr () + tsm.display_denominator = tuplet_elt.get_actual_nr () + + print ("num: %s, den: %s" % (tsm.display_numerator, tsm.display_denominator)) + + if hasattr (tuplet_elt, 'bracket') and tuplet_elt.bracket == "no": tsm.display_bracket = None elif hasattr (tuplet_elt, 'line-shape') and getattr (tuplet_elt, 'line-shape') == "curved": @@ -625,14 +670,20 @@ def musicxml_tuplet_to_lily (tuplet_elt, fraction): display_values = {"none": None, "actual": "actual", "both": "both"} if hasattr (tuplet_elt, "show-number"): tsm.display_number = display_values.get (getattr (tuplet_elt, "show-number"), "actual") - if getattr (tuplet_elt, "show-number") == "both": - needed_additional_definitions.append ("tuplet-note-wrapper") + if tsm.display_number == "actual" and tsm.display_denominator: + print "Add denom-function\n"; + needed_additional_definitions.append ("tuplet-non-default-denominator") + elif tsm.display_number == "both" and (tsm.display_numerator or tsm.display_denominator): + print "Add fraction-function\n"; + needed_additional_definitions.append ("tuplet-non-default-fraction") + else: + print "No display-function, display_number=%s, den=%s\n" % (tsm.display_number, tsm.display_denominator); + if hasattr (tuplet_elt, "show-type"): + if getattr (tuplet_elt, "show-type") == "actual": + needed_additional_definitions.append ("tuplet-note-wrapper") tsm.display_type = display_values.get (getattr (tuplet_elt, "show-type"), None) - # TODO: Handle non-standard display (extract the type from the tuplet-actual - # and tuplet-normal children - # TODO: We need the type from the time-modification tag! return tsm @@ -648,7 +699,7 @@ def group_tuplets (music_list, events): brackets = {} j = 0 - for (ev_chord, tuplet_elt, fraction) in events: + for (ev_chord, tuplet_elt, time_modification) in events: while (j < len (music_list)): if music_list[j] == ev_chord: break @@ -657,7 +708,7 @@ def group_tuplets (music_list, events): if hasattr (tuplet_elt, 'number'): nr = getattr (tuplet_elt, 'number') if tuplet_elt.type == 'start': - tuplet_object = musicxml_tuplet_to_lily (tuplet_elt, fraction) + tuplet_object = musicxml_tuplet_to_lily (tuplet_elt, time_modification) tuplet_info = [j, None, tuplet_object] indices.append (tuplet_info) brackets[nr] = tuplet_info @@ -2037,13 +2088,8 @@ def musicxml_voice_to_lily_voice (voice): # accidental-mark | other-notation for notations in notations_children: for tuplet_event in notations.get_tuplets(): - mod = n.get_maybe_exist_typed_child (musicxml.Time_modification) - # TODO: Extract the type of note (for possible display later on!) - frac = (1,1) - if mod: - frac = mod.get_fraction () - - tuplet_events.append ((ev_chord, tuplet_event, frac)) + time_mod = n.get_maybe_exist_typed_child (musicxml.Time_modification) + tuplet_events.append ((ev_chord, tuplet_event, time_mod)) # First, close all open slurs, only then start any new slur # TODO: Record the number of the open slur to dtermine the correct -- 2.39.5