Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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):
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These two methods are preparing support for markup formatting

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