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
5 changes: 4 additions & 1 deletion ly/musicxml/create_musicxml.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,10 @@ def create_measure(self, pickup = False, **bar_attrs):
"""Create new measure """
if pickup and self.bar_nr == 1:
self.bar_nr = 0
self.current_bar = etree.SubElement(self.current_part, "measure", number=str(self.bar_nr))
attrs = {'number': str(self.bar_nr)}
if self.bar_nr == 0:
attrs['implicit'] = 'yes'
self.current_bar = etree.SubElement(self.current_part, "measure", **attrs)
self.bar_nr +=1
if bar_attrs:
self.new_bar_attr(**bar_attrs)
Expand Down
51 changes: 40 additions & 11 deletions ly/musicxml/ly2xml_mediator.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ def __init__(self):
self.group = None
self.group_num = 0
self.current_chord = []
self.chord_bar = None
self.q_chord = []
self.prev_pitch = None
self.prev_chord_pitch = None
Expand All @@ -83,6 +84,8 @@ def __init__(self):
self.multiple_rest_bar = None
self.current_mark = 1
self.bar_is_pickup = False
self.pickup_dura = None
self.in_grace = False
self.stem_dir = None

def new_header_assignment(self, name, value):
Expand Down Expand Up @@ -160,6 +163,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.reset_bar_progress()

def part_not_empty(self):
return self.part and self.part.barlist
Expand Down Expand Up @@ -318,8 +322,11 @@ def get_first_var(self):
if self.sections:
return self.sections[0].barlist

def set_pickup(self):
def set_pickup(self, token, tokens):
r"""Set the length of the incomplete bar declared by \partial."""
self.bar_is_pickup = True
# A zero-length \partial declares no pickup bar at all.
self.pickup_dura = ly.duration.fraction((token,) + tokens) or None

def new_bar(self, fill_prev=True):
if self.bar and fill_prev:
Expand Down Expand Up @@ -388,15 +395,21 @@ def new_mark(self, num_mark = None):
self.current_attr.set_mark(self.bijective(self.current_mark))
self.current_mark += 1

def new_word(self, word):
def new_word(self, word, trails_note=False):
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)
bar = self.bar
barlist = self.insert_into.barlist
if (trails_note and not bar.has_music()
and len(barlist) > 1 and bar is barlist[-1]):
# The note this markup trails completed the previous bar.
bar = barlist[-2]
attr = next((obj for obj in bar.obj_list
if isinstance(obj, xml_objs.BarAttr)), None)
if attr is None:
attr = xml_objs.BarAttr()
bar.add(attr)
attr.set_word(word)

def new_time(self, num, den, numeric=False):
self.current_time = Fraction(num, den.denominator)
Expand All @@ -421,10 +434,23 @@ def new_clef(self, clefname):
def set_relative(self, note):
self.prev_pitch = note.pitch

def reset_bar_progress(self):
"""Forget the bar position, which means nothing in the next part."""
self.bar_dura = Fraction(0, 4)
self.pickup_dura = None
self.bar_is_pickup = False

def set_grace_seq(self, value):
self.in_grace = value

def increase_bar_dura(self, duration):
if self.in_grace:
# Grace notes ornament the next note; they take up no bar time.
return
self.bar_dura += duration[0] * duration[1]
if self.bar_dura >= self.current_time:
self.bar_dura = 0
bar_length = self.current_time if self.pickup_dura is None else self.pickup_dura
if self.bar_dura >= bar_length:
self.reset_bar_progress()
self.new_bar()

def new_note(self, note, rel=False, is_unpitched=False):
Expand Down Expand Up @@ -565,6 +591,8 @@ def new_chordbase(self, note, duration, rel=False):
self.current_note.set_duration(duration)
self.current_lynote = note
self.check_current_note(rel)
# The other chord notes follow, possibly after this bar was closed.
self.chord_bar = self.bar
self.increase_bar_dura(duration)

def new_chordnote(self, note, rel):
Expand All @@ -582,7 +610,7 @@ def new_chordnote(self, note, rel):
chord_note.set_octave(p.octave + 3)
self.prev_chord_pitch = p
chord_note.chord = True
self.bar.add(chord_note)
self.chord_bar.add(chord_note)
return chord_note

def copy_prev_chord(self, duration):
Expand All @@ -607,6 +635,7 @@ def copy_prev_chord(self, duration):
def clear_chord(self):
self.q_chord = self.current_chord
self.current_chord = []
self.chord_bar = None
self.prev_chord_pitch = None

def chord_end(self):
Expand Down
12 changes: 9 additions & 3 deletions ly/musicxml/lymus2musxml.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ def __init__(self):
self.phrslurnr = 0
self.mark = False
self.pickup = False
self.markup_trails_note = False

def parse_text(self, ly_text, filename=None):
"""Parse the LilyPond source specified as text.
Expand Down Expand Up @@ -346,7 +347,7 @@ def Duration(self, duration):
self.mediator.set_tuplspan_dur(duration.token, duration.tokens)
self.tupl_span = False
elif self.pickup:
self.mediator.set_pickup()
self.mediator.set_pickup(duration.token, duration.tokens)
self.pickup = False
else:
self.mediator.new_duration_token(duration.token, duration.tokens)
Expand Down Expand Up @@ -440,6 +441,7 @@ def Dynamic(self, dynamic):

def Grace(self, grace):
self.grace_seq = True
self.mediator.set_grace_seq(True)

def TimeSignature(self, timeSign):
self.mediator.new_time(timeSign.numerator(), timeSign.fraction(), self.numericTime)
Expand Down Expand Up @@ -529,10 +531,13 @@ def UserCommand(self, usercommand):
self.tupl_span = True

def Markup(self, markup):
pass
# Only markup written as a postfix event (-, ^ or _) follows its note;
# \mark, \tempo and standalone markup precede it.
self.markup_trails_note = isinstance(markup.parent(),
ly.music.items.Postfix)

def MarkupWord(self, markupWord):
self.mediator.new_word(markupWord.token)
self.mediator.new_word(markupWord.token, self.markup_trails_note)

def MarkupList(self, markuplist):
pass
Expand Down Expand Up @@ -622,6 +627,7 @@ def End(self, end):
self.fraction = None
elif isinstance(end.node, ly.music.items.Grace): #Grace
self.grace_seq = False
self.mediator.set_grace_seq(False)
elif end.node.token == '\\repeat':
if end.node.specifier() == 'volta':
self.mediator.new_repeat('backward')
Expand Down
11 changes: 8 additions & 3 deletions tests/test_xml.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import os
import os.path
import io
import pytest
import re
import sys

Expand Down Expand Up @@ -46,7 +45,6 @@ def test_mark():
compare_output('mark')


@pytest.mark.xfail
def test_partial():
compare_output('partial')

Expand All @@ -63,11 +61,18 @@ def test_church():
compare_output('church_modes')


@pytest.mark.xfail
def test_markup():
compare_output('markup')


def test_markup_mark():
compare_output('markup_mark')


def test_incomplete_bar():
compare_output('incomplete_bar')


def test_breathe():
compare_output('breathe')

Expand Down
6 changes: 3 additions & 3 deletions tests/test_xml_files/chord_duration.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<identification>
<encoding>
<software>python-ly 0.9.10</software>
<encoding-date>2026-01-26</encoding-date>
<encoding-date>2026-08-17</encoding-date>
</encoding>
</identification>
<part-list>
Expand Down Expand Up @@ -52,8 +52,6 @@
<voice>1</voice>
<type>whole</type>
</note>
</measure>
<measure number="3">
<note>
<chord />
<pitch>
Expand All @@ -74,6 +72,8 @@
<voice>1</voice>
<type>whole</type>
</note>
</measure>
<measure number="3">
<note>
<pitch>
<step>E</step>
Expand Down
8 changes: 8 additions & 0 deletions tests/test_xml_files/incomplete_bar.ly
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
\version "2.18.2"

\score {
<<
\new Staff \relative { c''4 d4 }
\new Staff \relative { c'4 d4 e4 f4 | g1 }
>>
}
115 changes: 115 additions & 0 deletions tests/test_xml_files/incomplete_bar.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
<?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.10</software>
<encoding-date>2026-08-17</encoding-date>
</encoding>
</identification>
<part-list>
<score-part id="P1">
<part-name />
</score-part>
<score-part id="P2">
<part-name />
</score-part>
</part-list>
<part id="P1">
<measure number="1">
<attributes>
<divisions>1</divisions>
<time symbol="common">
<beats>4</beats>
<beat-type>4</beat-type>
</time>
<clef>
<sign>G</sign>
<line>2</line>
</clef>
</attributes>
<note>
<pitch>
<step>C</step>
<octave>6</octave>
</pitch>
<duration>1</duration>
<voice>1</voice>
<type>quarter</type>
</note>
<note>
<pitch>
<step>D</step>
<octave>6</octave>
</pitch>
<duration>1</duration>
<voice>1</voice>
<type>quarter</type>
</note>
</measure>
<measure number="2" />
</part>
<part id="P2">
<measure number="1">
<attributes>
<divisions>1</divisions>
<time symbol="common">
<beats>4</beats>
<beat-type>4</beat-type>
</time>
<clef>
<sign>G</sign>
<line>2</line>
</clef>
</attributes>
<note>
<pitch>
<step>C</step>
<octave>5</octave>
</pitch>
<duration>1</duration>
<voice>1</voice>
<type>quarter</type>
</note>
<note>
<pitch>
<step>D</step>
<octave>5</octave>
</pitch>
<duration>1</duration>
<voice>1</voice>
<type>quarter</type>
</note>
<note>
<pitch>
<step>E</step>
<octave>5</octave>
</pitch>
<duration>1</duration>
<voice>1</voice>
<type>quarter</type>
</note>
<note>
<pitch>
<step>F</step>
<octave>5</octave>
</pitch>
<duration>1</duration>
<voice>1</voice>
<type>quarter</type>
</note>
</measure>
<measure number="2">
<note>
<pitch>
<step>G</step>
<octave>5</octave>
</pitch>
<duration>4</duration>
<voice>1</voice>
<type>whole</type>
</note>
</measure>
</part>
</score-partwise>
2 changes: 1 addition & 1 deletion tests/test_xml_files/markup.xml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?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">
Expand Down
13 changes: 13 additions & 0 deletions tests/test_xml_files/markup_mark.ly
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
\version "2.18.2"

% Markup not attached to a note (here \mark) belongs to the bar it is written in
% Note: emitting both <rehearsal> and <words> for one \mark is a known defect (#121)

\score {
\relative {
a'1^\markup intenso |
\mark \markup Coda
a1 |
a1
}
}
Loading
Loading