From 29089bf72d0bdfbc53d30bc68d30a55fd230118a Mon Sep 17 00:00:00 2001 From: Urs Liska Date: Mon, 7 May 2018 09:33:32 +0200 Subject: [PATCH] #121: XML: Properly handle \markup Closes #121 As described in the Issue the previous implementation of \markup had two flaws: - direction modifier wasn't respected - all markups in a bar would be added as a child of the bar instead of being attached to the notes This commit creates each \markup as a element immediately preceding the note/rest the markup is attached to. If there's an explicit direction operator it is respected. Multiple markups may be attached to a single note, each with its own direction (or lack thereof). The handling of Markup() and MarkupElement() objects is already prepared to dealing with variable formatting, insofar as a Markup() contains a list of MarkupElement() items, which will later be used to handle formatting. One \markup will generate one element. This contains a sequence of elements (currently only one), which can individually be assigned formatting attributes. --- ly/musicxml/create_musicxml.py | 15 ++++++++++++ ly/musicxml/ly2xml_mediator.py | 12 ++++------ ly/musicxml/lymus2musxml.py | 18 ++++++++++---- ly/musicxml/xml_objs.py | 42 ++++++++++++++++++++++++++++++++- tests/test_xml_files/markup.ly | 8 ++++--- tests/test_xml_files/markup.xml | 11 +++------ 6 files changed, 81 insertions(+), 25 deletions(-) diff --git a/ly/musicxml/create_musicxml.py b/ly/musicxml/create_musicxml.py index 3142abc7..05e8d779 100644 --- a/ly/musicxml/create_musicxml.py +++ b/ly/musicxml/create_musicxml.py @@ -286,6 +286,21 @@ def create_note(self): self.current_ornament = None self.current_tech = None + def add_markup(self, markup): + """ Create a new Markup element, attached to (exported before) a note""" + d = etree.SubElement( + self.current_bar, + 'direction', + { 'placement': markup.direction } if markup.direction else {}) + dt = etree.SubElement(d, 'direction-type') + for e in markup.elements: + # TODO: This loop iterates over all MarkupElement() objects, + # which are intended to handle different formattings. + # Currently there will always be only *one* such element + # without any explicit formatting + cont = etree.SubElement(dt, 'words') + cont.text = e.content() + def add_pitch(self, step, alter, octave): """Create new pitch.""" pitch = etree.SubElement(self.current_note, "pitch") diff --git a/ly/musicxml/ly2xml_mediator.py b/ly/musicxml/ly2xml_mediator.py index e8607ea7..8b392aac 100644 --- a/ly/musicxml/ly2xml_mediator.py +++ b/ly/musicxml/ly2xml_mediator.py @@ -387,14 +387,7 @@ def new_mark(self, num_mark = None): self.current_mark += 1 def new_word(self, word): - if self.bar is None: - self.new_bar() - if self.bar.has_attr(): - self.current_attr.set_word(word) - else: - new_bar_attr = xml_objs.BarAttr() - new_bar_attr.set_word(word) - self.add_to_bar(new_bar_attr) + self.current_note.add_word(word) def new_time(self, num, den, numeric=False): self.current_time = Fraction(num, den.denominator) @@ -914,6 +907,9 @@ def new_lyrics_item(self, item): elif item == '\\skip': self.insert_into.barlist.append("skip") + def new_markup(self, direction): + self.current_note.add_markup(direction) + def duration_from_tokens(self, tokens): """Calculate dots and multibar rests from tokens.""" dots = 0 diff --git a/ly/musicxml/lymus2musxml.py b/ly/musicxml/lymus2musxml.py index 46effdb5..d15a7c13 100644 --- a/ly/musicxml/lymus2musxml.py +++ b/ly/musicxml/lymus2musxml.py @@ -91,13 +91,14 @@ def __init__(self): self.phrslurnr = 0 self.mark = False self.pickup = False + self.postfix = None def parse_text(self, ly_text, filename=None): """Parse the LilyPond source specified as text. - + If you specify a filename, it can be used to resolve \\include commands correctly. - + """ doc = ly.document.Document(ly_text) doc.filename = filename @@ -105,11 +106,11 @@ def parse_text(self, ly_text, filename=None): def parse_document(self, ly_doc, relative_first_pitch_absolute=False): """Parse the LilyPond source specified as a ly.document document. - + If relative_first_pitch_absolute is set to True, the first pitch in a \relative expression without startpitch is considered to be absolute (LilyPond 2.18+ behaviour). - + """ # The document is copied and the copy is converted to absolute mode to # facilitate the export. The original document is unchanged. @@ -409,7 +410,8 @@ def Articulation(self, art): self.mediator.new_articulation(art.token) def Postfix(self, postfix): - pass + op = postfix.token + self.postfix = 'above' if op == '^' else 'below' if op == '_' else None def Beam(self, beam): pass @@ -529,6 +531,12 @@ def UserCommand(self, usercommand): self.tupl_span = True def Markup(self, markup): + self.mediator.new_markup(self.postfix) + + def MarkupCommand(self, markupCommand): + pass + + def MarkupUserCommand(self, markupUserCommand): pass def MarkupWord(self, markupWord): diff --git a/ly/musicxml/xml_objs.py b/ly/musicxml/xml_objs.py index 8cca5e4a..9f6d9d44 100644 --- a/ly/musicxml/xml_objs.py +++ b/ly/musicxml/xml_objs.py @@ -129,7 +129,7 @@ def iterate_bar(self, bar): def new_xml_bar_attr(self, obj): """Create bar attribute xml-nodes.""" if obj.has_attr(): - self.musxml.new_bar_attr(obj.clef, obj.time, obj.key, obj.mode, + self.musxml.new_bar_attr(obj.clef, obj.time, obj.key, obj.mode, obj.divs, obj.multirest) if obj.new_system: self.musxml.new_system(obj.new_system) @@ -152,6 +152,7 @@ def new_xml_bar_attr(self, obj): def before_note(self, obj): """Xml-nodes before note.""" + self._add_markups(obj) self._add_dynamics([d for d in obj.dynamic if d.before]) if obj.oct_shift and not obj.oct_shift.octdir == 'stop': self.musxml.add_octave_shift(obj.oct_shift.plac, obj.oct_shift.octdir, obj.oct_shift.size) @@ -174,6 +175,11 @@ def _add_dynamics(self, dyns): elif isinstance(d, DynamicsDashes): self.musxml.add_dynamic_dashes(d.sign) + def _add_markups(self, obj): + """Add XML nodes for markups attached to notes/rests.""" + for m in obj.markup: + self.musxml.add_markup(m) + def gener_xml_mus(self, obj): """Nodes generic for both notes and rests.""" if obj.tuplet: @@ -522,6 +528,29 @@ def inject_voice(self, new_voice, override=False): for bl in backup_list: self.add(bl) +class MarkupElement(): + """ Class for (formatted) markup. + One Markup() consists of one or more MarkupElement()s. """ + def __init__(self, attrs={}): + self.attributes = attrs + self.words = [] + + def add_word(self, word): + self.words.append(word) + + def content(self): + return ' '.join(self.words) + +class Markup(): + """ Class for markups attached to BarMus() elements. + Each element is a (formatted) MarkupElement(). """ + def __init__(self, direction=None): + self.direction = direction + self.curr_attrs = {} + self.elements = [MarkupElement()] + + def add_word(self, word): + self.elements[-1].add_word(word) class BarMus(): """ Common class for notes and rests. """ @@ -535,6 +564,7 @@ def __init__(self, duration, voice=1): self.chord = False self.other_notation = None self.dynamic = [] + self.markup = [] self.oct_shift = None def __repr__(self): @@ -549,6 +579,15 @@ def set_staff(self, staff): def add_dot(self): self.dot += 1 + def add_markup(self, direction): + self.markup.append(Markup(direction)) + + def add_word(self, word): + self.markup[-1].add_word(word) + + def current_markup(self): + return self.markup[-1] + def add_other_notation(self, other): self.other_notation = other @@ -647,6 +686,7 @@ def __init__(self, pitch_note, alter, accidental, duration, voice=1): self.skip = False self.slur = [] self.artic = [] + self.markup = [] self.ornament = None self.adv_ornament = None self.fingering = None diff --git a/tests/test_xml_files/markup.ly b/tests/test_xml_files/markup.ly index 747d67ba..1020eb89 100644 --- a/tests/test_xml_files/markup.ly +++ b/tests/test_xml_files/markup.ly @@ -2,8 +2,10 @@ \score { \relative { - a'1-\markup intenso | - a1-\markup intenso | - a2^\markup { poco più forte } + a'2-\markup intenso + a2_\markup intenso | + a2^\markup { poco più forte } + r2 | + r1 -\markup neutral _\markup below ^\markup above } } \ No newline at end of file diff --git a/tests/test_xml_files/markup.xml b/tests/test_xml_files/markup.xml index fdfe22b3..26317e87 100644 --- a/tests/test_xml_files/markup.xml +++ b/tests/test_xml_files/markup.xml @@ -1,11 +1,11 @@ - + python-ly 0.9.5 - 2017-06-30 + 2018-05-05 @@ -26,11 +26,6 @@ 2 - - - intenso - - A @@ -60,7 +55,7 @@ - poco più forte + intenso poco più forte