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
21 changes: 20 additions & 1 deletion ly/musicxml/create_musicxml.py
Original file line number Diff line number Diff line change
Expand Up @@ -554,13 +554,32 @@ def add_bar_style(self, multirest=None):
def new_system(self, force_break):
etree.SubElement(self.current_bar, "print", {'new-system':force_break})

def add_barline(self, bl_type, repeat=None):
def add_barline(self, bl_type, repeat=None, repeat_times=None):
barnode = etree.SubElement(self.current_bar, "barline", location="right")
barstyle = etree.SubElement(barnode, "bar-style")
barstyle.text = bl_type
if repeat:
if repeat == "forward":
barnode.attrib["location"] = "left"
repeatnode = etree.SubElement(barnode, "repeat", direction=repeat)

if repeat_times and repeat_times > 2:
repeatnode.attrib['times'] = str(repeat_times)

def add_ending(self, ending, number):
barnode = etree.SubElement(self.current_bar, "barline", location="right")

if not(isinstance(number,list)):
number = [number]

number_attr = ','.join(str(n) for n in number)

endingnode = etree.SubElement(barnode, "ending", type=ending, number=number_attr)

if ending == 'start':
barnode.attrib['location'] = 'left'
endingnode.text = ', '.join('{}.'.format(n) for n in number)

def add_backup(self, duration):
if duration <= 0:
return
Expand Down
39 changes: 37 additions & 2 deletions ly/musicxml/ly2xml_mediator.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ class Mediator():
def __init__(self):
""" create global lists """
self.score = xml_objs.Score()

# Previous and active bar, necessary for putting repeat sign at correct position
self.prev_bar = None
self.bar = None

self.sections = []
""" default and initial values """
self.insert_into = None
Expand Down Expand Up @@ -103,13 +108,15 @@ def new_section(self, name, glob=False):
self.insert_into = section
self.sections.append(section)
self.bar = None
self.prev_bar = None

def new_snippet(self, name):
name = self.check_name(name)
snippet = xml_objs.Snippet(name, self.insert_into)
self.insert_into = snippet
self.sections.append(snippet)
self.bar = None
self.prev_bar = None

def new_lyric_section(self, name, voice_id):
name = self.check_name(name)
Expand Down Expand Up @@ -160,6 +167,7 @@ def new_part(self, pid=None, to_part=None, piano=False):
self.score.partlist.append(self.part)
self.insert_into = self.part
self.bar = None
self.prev_bar = None

def part_not_empty(self):
return self.part and self.part.barlist
Expand Down Expand Up @@ -323,6 +331,9 @@ def set_pickup(self):
def new_bar(self, fill_prev=True):
if self.bar and fill_prev:
self.bar.list_full = True

self.prev_bar = self.bar

self.current_attr = xml_objs.BarAttr()
self.bar = xml_objs.Bar()
if self.bar_is_pickup:
Expand All @@ -342,13 +353,37 @@ def create_barline(self, bl):
self.bar.add(barline)
self.new_bar()

def new_repeat(self, rep):
def new_repeat(self, rep, times=None):

barline = xml_objs.BarAttr()
barline.set_barline(rep)
barline.repeat = rep
barline.repeat_times = times

if self.bar is None:
self.new_bar()
self.bar.add(barline)

if rep == 'backward' and not self.bar.has_music() and self.prev_bar:
# If we are ending a repeat, but the current bar has no music (no rests, too),
# use the previous bar
self.prev_bar.add(barline)
else:
self.bar.add(barline)

def new_ending(self, type, number):
# type must be either 'start', 'stop' or 'discontinue'
# number must be ending number or a list of ending numbers

ending = xml_objs.BarAttr()
ending.set_ending(type, number)

if self.bar is None:
self.new_bar()

if type in ['stop', 'discontinue'] and not self.bar.has_music() and self.prev_bar:
self.prev_bar.add(ending)
else:
self.bar.add(ending)

def new_key(self, key_name, mode):
if self.bar is None:
Expand Down
58 changes: 57 additions & 1 deletion ly/musicxml/lymus2musxml.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,10 @@ def MusicList(self, musicList):
self.sims_and_seqs.append('sim')
elif musicList.token == '{':
self.sims_and_seqs.append('seq')
if isinstance(musicList.parent().parent(), ly.music.items.Alternative):
# The grandparent node is an instance of \alternative,
# this indicates that this musiclist instance is an ending to a \repeat
self.alternative_handler(musicList, 'start')

def Chord(self, chord):
self.mediator.clear_chord()
Expand Down Expand Up @@ -450,6 +454,54 @@ def Repeat(self, repeat):
elif repeat.specifier() == 'tremolo':
self.trem_rep = repeat.repeat_count()

def Alternative(self, alternative):
"""\alternative"""
pass

def alternative_handler(self, node, type):
"""
Helper method for handling alternative endings.
Generates number lists for the number attribute in MusicXML's ending element.

It tries to follow the same pattern as the default in Lilypond
(see http://lilypond.org/doc/v2.18/Documentation/notation/long-repeats#normal-repeats)
"""

# Should contain an array of MusicLists
alternative_container = node.parent()

# Instance of \alternative
alternative_instance = alternative_container.parent()

# instance of \repeat
repeat_instance = alternative_instance.parent()

num_repeats = repeat_instance.repeat_count()
num_alternatives = len(alternative_container._children)

idx = alternative_container.index(node) + 1
ending_numbers = [idx]

if num_alternatives < num_repeats:
# If there are fewer ending alternatives than repeats, generate
# ending numbers following the same order as Lilypond.
if idx == 1:
ending_numbers = list(range(1, num_repeats - num_alternatives + 2))
else:
ending_numbers = [idx + num_repeats - num_alternatives]

if type == 'start':
self.mediator.new_ending(type, ending_numbers)
elif type == 'stop':
if idx == 1 and num_alternatives < num_repeats:
self.mediator.new_repeat('backward', len(ending_numbers)+1)
elif idx < num_alternatives:
self.mediator.new_repeat('backward')
if idx == num_alternatives:
type = 'discontinue'

self.mediator.new_ending(type, ending_numbers)

def Tremolo(self, tremolo):
"""A tremolo item ":"."""
if self.look_ahead(tremolo, ly.music.items.Duration):
Expand Down Expand Up @@ -624,7 +676,9 @@ def End(self, end):
self.grace_seq = False
elif end.node.token == '\\repeat':
if end.node.specifier() == 'volta':
self.mediator.new_repeat('backward')
if not end.node.find_child(ly.music.items.Alternative, 1):
# the repeat does not contain alternative endings, treat as normal repeat
self.mediator.new_repeat('backward', end.node.repeat_count())
elif end.node.specifier() == 'tremolo':
if self.look_ahead(end.node, ly.music.items.MusicList):
self.mediator.set_tremolo(trem_type="stop")
Expand Down Expand Up @@ -658,6 +712,8 @@ def End(self, end):
if self.sims_and_seqs:
self.sims_and_seqs.pop()
elif end.node.token == '{':
if isinstance(end.node.parent().parent(), ly.music.items.Alternative):
self.alternative_handler(end.node, 'stop')
if self.sims_and_seqs:
self.sims_and_seqs.pop()
elif end.node.token == '<': #chord
Expand Down
34 changes: 32 additions & 2 deletions ly/musicxml/xml_objs.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,9 +134,11 @@ def new_xml_bar_attr(self, obj):
if obj.new_system:
self.musxml.new_system(obj.new_system)
if obj.repeat:
self.musxml.add_barline(obj.barline, obj.repeat)
self.musxml.add_barline(obj.barline, obj.repeat, obj.repeat_times)
elif obj.barline:
self.musxml.add_barline(obj.barline)
elif obj.ending:
self.musxml.add_ending(obj.ending, obj.ending_number)
if obj.staves:
self.musxml.add_staves(obj.staves)
if obj.multiclef:
Expand Down Expand Up @@ -471,7 +473,20 @@ def __repr__(self):
return '<{0} {1}>'.format(self.__class__.__name__, self.obj_list)

def add(self, obj):
self.obj_list.append(obj)
if isinstance(obj, BarAttr):
# Find first idx of instance which is not a BarAttr
idx = len(self.obj_list)
for (i, x) in enumerate(self.obj_list):
if not isinstance(x, BarAttr):
idx = i
break

if (obj not in self.obj_list):
# The candidate does not exists in our obj_list, add it at a fitting
# location in the list
self.obj_list.insert(idx, obj)
else:
self.obj_list.append(obj)

def has_music(self):
""" Check if bar contains music. """
Expand Down Expand Up @@ -796,6 +811,7 @@ def __init__(self):
self.divs = 0
self.barline = None
self.repeat = None
self.repeat_times = None
self.staves = 0
self.multiclef = []
self.tempo = None
Expand All @@ -804,9 +820,19 @@ def __init__(self):
self.word = None
self.new_system = None

# Ending type, 'start', 'stop' or 'discontinue'
self.ending = None
# Ending number should either be a number, or a list of numbers
self.ending_number = None

def __repr__(self):
return '<{0} {1}>'.format(self.__class__.__name__, self.time)

def __eq__(self, other):
if self.__class__ != other.__class__:
return False
return self.__dict__ == other.__dict__

def add_break(self, force_break):
self.new_system = force_break

Expand All @@ -828,6 +854,10 @@ def set_barline(self, bl):
def set_tempo(self, unit=0, unittype='', beats=0, dots=0, text=""):
self.tempo = TempoDir(unit, unittype, beats, dots, text)

def set_ending(self, type, number):
self.ending = type
self.ending_number = number

def set_multp_rest(self, size=0):
self.multirest = size

Expand Down
6 changes: 6 additions & 0 deletions tests/test_xml.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,12 @@ def test_no_barcheck():
compare_output('no_barcheck')


def test_repeat():
compare_output('repeat')

def test_repeat_with_alternative():
compare_output('repeat_with_alternative')

def ly_to_xml(filename):
"""Read Lilypond file and return XML string."""
writer = ly.musicxml.writer()
Expand Down
43 changes: 43 additions & 0 deletions tests/test_xml_files/repeat.ly
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
\version "2.19.55"

\header {
title = "repeat"
}

\score {
\new ChoirStaff <<
\new Staff {
\relative c' {
c1 |
\repeat volta 2{ c1| }
d1 |
\repeat volta 3{ d1| }
}
}

\new Staff
<<
\clef treble
\new Voice {
\voiceOne
\relative c' {
c1 |
\repeat volta 2{ d1| }
e1 |
\repeat volta 3{ d1| }
}
}
\new Voice {
\voiceTwo
\relative c' {
f4 f f f |
\repeat volta 2{ g g g g| }
a a a a |
\repeat volta 3{ g2 g| }
}
}
>>
>>

\layout {}
}
Loading