Skip to content

Commit

Permalink
#121: XML: Properly handle \markup
Browse files Browse the repository at this point in the history
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 <direction> 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 <direction> element. This contains
a sequence of <words> elements (currently only one), which can individually
be assigned formatting attributes.
  • Loading branch information
uliska committed May 7, 2018
1 parent 79eae64 commit 29089bf
Show file tree
Hide file tree
Showing 6 changed files with 81 additions and 25 deletions.
15 changes: 15 additions & 0 deletions ly/musicxml/create_musicxml.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
12 changes: 4 additions & 8 deletions ly/musicxml/ly2xml_mediator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
18 changes: 13 additions & 5 deletions ly/musicxml/lymus2musxml.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,25 +91,26 @@ 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
self.parse_document(doc)

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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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):
Expand Down
42 changes: 41 additions & 1 deletion ly/musicxml/xml_objs.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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:
Expand Down Expand Up @@ -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. """
Expand All @@ -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):
Expand All @@ -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

Expand Down Expand Up @@ -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
Expand Down
8 changes: 5 additions & 3 deletions tests/test_xml_files/markup.ly
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
11 changes: 3 additions & 8 deletions tests/test_xml_files/markup.xml
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 2.0 Partwise//EN"
"http://www.musicxml.org/dtds/partwise.dtd">
<score-partwise version="3.0">
<identification>
<encoding>
<software>python-ly 0.9.5</software>
<encoding-date>2017-06-30</encoding-date>
<encoding-date>2018-05-05</encoding-date>
</encoding>
</identification>
<part-list>
Expand All @@ -26,11 +26,6 @@
<line>2</line>
</clef>
</attributes>
<direction placement="above">
<direction-type>
<words>intenso </words>
</direction-type>
</direction>
<note>
<pitch>
<step>A</step>
Expand Down Expand Up @@ -60,7 +55,7 @@
<measure number="3">
<direction placement="above">
<direction-type>
<words>poco più forte </words>
<words>intenso poco più forte </words>
</direction-type>
</direction>
<note>
Expand Down

0 comments on commit 29089bf

Please sign in to comment.