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 8faa97a commit a106b0b
Show file tree
Hide file tree
Showing 5 changed files with 40 additions and 24 deletions.
15 changes: 15 additions & 0 deletions ly/musicxml/create_musicxml.py
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,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
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 a106b0b

Please sign in to comment.