Skip to content
Open
16 changes: 16 additions & 0 deletions ly/lex/lilypond.py
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,13 @@ def test_match(cls, match):
return s in words.lilypond_music_commands
return False

def update_state(self, state):
super().update_state(state)
# at toplevel, a music function (e.g. \afterGrace) is followed by its
# music arguments, like the value of an assignment
if isinstance(state.parser(), ParseGlobal):
state.enter(ParseGlobalAssignment())


class Keyword(_token.Item, IdentifierRef):
@classmethod
Expand Down Expand Up @@ -889,6 +896,13 @@ class Name(UserVariable):
r"""A variable name without \ prefix."""


class AssignmentName(Name):
r"""The name of a new assignment, ending a toplevel assignment value."""
rx = Identifier.rx + r"(?=\s*=(?![=,']))"
def update_state(self, state):
state.leave()


class EqualSign(_token.Token):
rx = r"="

Expand Down Expand Up @@ -1008,11 +1022,13 @@ def update_state(self, state, token):

class ParseGlobalAssignment(FallthroughParser, ParseLilyPond):
items = space_items + (
AssignmentName,
Skip,
Spacer,
Q,
Rest,
Note,
Octave,
Length,
Fraction,
DecimalValue,
Expand Down
13 changes: 12 additions & 1 deletion ly/music/items.py
Original file line number Diff line number Diff line change
Expand Up @@ -629,9 +629,20 @@ def preceding(self, node=None):
class AfterGrace(Music):
r"""The \afterGrace function with its two arguments.

Only the duration of the first is counted.
Only the duration of the first is counted. The music arguments may be
preceded by a fraction (a Number).

"""
def fraction(self):
"""Return the fraction of the main note after which the graces start.

This is the optional fraction argument, or LilyPond's default
afterGraceFraction 3/4.

"""
if isinstance(self[0], Number):
return self[0].value()
return Fraction(3, 4)


class PartCombine(Music):
Expand Down
13 changes: 11 additions & 2 deletions ly/music/read.py
Original file line number Diff line number Diff line change
Expand Up @@ -626,10 +626,19 @@ def handle_grace(self, t, source):
@_commands('\\afterGrace')
def handle_after_grace(self, t, source):
item = self.factory(AfterGrace, t)
for i in itertools.islice(self.read(source), 2):
post_events = (Postfix, Tie, Slur, PhrasingSlur, Beam, Dynamic, Articulation)
items = self.read(source)
item.extend(itertools.islice(items, 1))
# the main music may be preceded by an optional fraction
if len(item) and isinstance(item[0], Number):
item.extend(itertools.islice(items, 1))
# the post-events of the main music come before the grace music
for i in items:
item.append(i)
if not isinstance(i, post_events):
break
# put the grace music in a Grace item
if len(item) > 1:
if len(item) > 1 and not isinstance(item[-1], post_events):
i = self.factory(Grace, position=item[-1].position)
i.append(item[-1])
item.append(i)
Expand Down
68 changes: 35 additions & 33 deletions ly/musicxml/create_musicxml.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ class CreateMusicXML():

def __init__(self):
"""Creates the basic structure of the XML without any music."""
self.root = etree.Element("score-partwise", version="3.0")
self.root = etree.Element("score-partwise", version="4.0")
self.tree = etree.ElementTree(self.root)
self.score_info = etree.SubElement(self.root, "identification")
encoding = etree.SubElement(self.score_info, "encoding")
Expand All @@ -73,9 +73,19 @@ def create_title(self, title):
self.root.insert(0, mov_title)

def create_score_info(self, tag, info, attr={}):
"""Create score info."""
info_node = etree.SubElement(self.score_info, tag, attr)
"""Create score info (creator or rights), which precedes the encoding."""
info_node = etree.Element(tag, attr)
info_node.text = info
self.score_info.insert(get_tag_index(self.score_info, "encoding"), info_node)

def add_miscellaneous(self, name, text):
"""Add a miscellaneous field, e.g. for a header field without a
MusicXML element of its own."""
misc = self.score_info.find("miscellaneous")
if misc is None:
misc = etree.SubElement(self.score_info, "miscellaneous")
field = etree.SubElement(misc, "miscellaneous-field", name=name)
field.text = text

def create_partgroup(self, gr_type, num, name=None, abbr=None, symbol=None):
"""Create a new part group."""
Expand Down Expand Up @@ -120,9 +130,12 @@ def create_part(self, name="unnamed", abbr=False, midi=False):

def create_measure(self, pickup = False, **bar_attrs):
"""Create new measure """
if pickup and self.bar_nr == 1:
implicit = pickup and self.bar_nr == 1
if implicit:
self.bar_nr = 0
self.current_bar = etree.SubElement(self.current_part, "measure", number=str(self.bar_nr))
if implicit:
self.current_bar.set('implicit', 'yes')
self.bar_nr +=1
if bar_attrs:
self.new_bar_attr(**bar_attrs)
Expand All @@ -132,11 +145,11 @@ def create_measure(self, pickup = False, **bar_attrs):
##

def new_note(self, step, octave, durtype, divdur, alter=0,
acc_token=0, voice=1, dot=0, chord=0, grace=(0, 0), stem_dir=0):
acc_token=0, voice=1, dot=0, chord=0, grace=(0, 0, 0), stem_dir=0):
"""Create all nodes needed for a normal note. """
self.create_note()
if grace[0]:
self.add_grace(grace[1])
self.add_grace(*grace[1:])
if chord:
self.add_chord()
self.add_pitch(step, alter, octave)
Expand All @@ -158,34 +171,26 @@ def new_note(self, step, octave, durtype, divdur, alter=0,
self.set_stem_dir(stem_dir)

def new_unpitched_note(self, step, octave, durtype, divdur, voice=1,
dot=0, chord=0, grace=(0, 0)):
dot=0, chord=0, grace=(0, 0, 0)):
"""Create all nodes needed for an unpitched note. """
self.create_note()
if grace[0]:
self.add_grace(grace[1])
self.add_grace(*grace[1:])
if chord:
self.add_chord()
self.add_unpitched(step, octave)
if not grace[0]:
self.add_div_duration(divdur)
self.add_duration_type(durtype)
self.add_voice(voice)
self.add_duration_type(durtype)
if dot:
for i in range(dot):
self.add_dot()

def tuplet_note(self, fraction, bs, ttype, nr, divs, atyp='', ntyp=''):
def tuplet_note(self, fraction, ttype, nr, atyp='', ntyp=''):
"""Convert current note to tuplet """
base = self.mult * bs[0]
scaling = bs[1]
a = divs*4*fraction[1]
b = (1/base)*fraction[0]
duration = (a/b)*scaling
self.change_div_duration(duration)
from fractions import Fraction
self.mult = Fraction(fraction[1], fraction[0])
timemod_node = self.get_time_modify()
if timemod_node:
if timemod_node is not None:
self.adjust_time_modify(timemod_node, fraction)
else:
self.add_time_modify(fraction)
Expand Down Expand Up @@ -354,13 +359,8 @@ def add_skip(self, duration, forward=True):

def add_div_duration(self, divdur):
"""Create new duration """
self.duration = etree.SubElement(self.current_note, "duration")
self.duration.text = str(divdur)
self.mult = 1

def change_div_duration(self, newdura):
"""Set new duration when tuplet """
self.duration.text = str(newdura)
duration = etree.SubElement(self.current_note, "duration")
duration.text = str(divdur)

def add_duration_type(self, durtype):
"""Create new type """
Expand All @@ -378,17 +378,19 @@ def add_beam(self, nr, beam_type):

def add_tie(self, tie_type):
"""Create node tie (used for sound of tie) """
# A tie must be directly after a duration
insert_at = get_tag_index(self.current_note, "duration") + 1
# A tie must be directly after a duration (or a previous tie)
insert_at = (get_tag_index(self.current_note, "duration") + 1
+ len(self.current_note.findall("tie")))
tie_element = etree.Element("tie", type=tie_type)
self.current_note.insert(insert_at, tie_element)

def add_grace(self, slash):
def add_grace(self, slash, steal_time_previous=0):
"""Create grace node """
grace = etree.SubElement(self.current_note, "grace")
if steal_time_previous:
grace.set("steal-time-previous", "{:g}".format(float(steal_time_previous)))
if slash:
etree.SubElement(self.current_note, "grace", slash="yes")
else:
etree.SubElement(self.current_note, "grace")
grace.set("slash", "yes")

def add_notations(self):
if self.current_notation is None:
Expand Down Expand Up @@ -721,5 +723,5 @@ def get_tag_index(node, tag):

xml_decl_txt = """<?xml version="1.0" encoding="{encoding}"?>"""

doctype_txt = """<!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 2.0 Partwise//EN"
doctype_txt = """<!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 4.0 Partwise//EN"
"http://www.musicxml.org/dtds/partwise.dtd">"""
Loading