Skip to content

Commit

Permalink
Merge branch '49-support-italic-lyrics' into 'stable'
Browse files Browse the repository at this point in the history
Resolve "Support italic lyrics"

Closes frescobaldi#49

See merge request bgeorge/python-ly!57
  • Loading branch information
bryantgeorge committed Jan 6, 2020
2 parents a9977c0 + 456630f commit ef75d1c
Show file tree
Hide file tree
Showing 6 changed files with 275 additions and 13 deletions.
9 changes: 6 additions & 3 deletions ly/musicxml/create_musicxml.py
Original file line number Diff line number Diff line change
Expand Up @@ -758,17 +758,20 @@ def add_metron_dir(self, unit, beats, dots):
def add_sound_dir(self, midi_tempo):
soundnode = etree.SubElement(self.direction, "sound", tempo=str(midi_tempo))

def add_lyric(self, txt, syll, nr, ext=False):
def add_lyric(self, txt, syll, nr, args=[]):
""" Add lyric element. """
lyricnode = etree.SubElement(self.current_note, "lyric", number=str(nr))
syllnode = etree.SubElement(lyricnode, "syllabic")
syllnode.text = syll
txtnode = etree.SubElement(lyricnode, "text")
arg_dict = {}
if "italic" in args:
arg_dict["font-style"] = "italic"
txtnode = etree.SubElement(lyricnode, "text", arg_dict)
txtnode.text = txt
# For lyrics which were not parsed correctly (https://lxml.de/tutorial.html)
if txt == "ERROR":
lyricnode.insert(0, etree.Comment("Lyric text was not parsed correctly, so it was marked with ERROR"))
if ext:
if "extend" in args:
etree.SubElement(lyricnode, "extend")

##
Expand Down
4 changes: 3 additions & 1 deletion ly/musicxml/ly2xml_mediator.py
Original file line number Diff line number Diff line change
Expand Up @@ -1069,7 +1069,7 @@ def set_partmidi(self, midi):
def new_lyric_nr(self, num):
self.lyric_nr = num

def new_lyrics_text(self, txt):
def new_lyrics_text(self, txt, italic=False):
if not txt:
txt = "ERROR"
eprint("Warning: Lyric text not readable, marked with ERROR!")
Expand All @@ -1083,6 +1083,8 @@ def new_lyrics_text(self, txt):
self.lyric = [txt, 'single', self.lyric_nr]
else:
self.lyric = [txt, 'single', self.lyric_nr]
if italic:
self.lyric.append('italic')
self.insert_into.barlist.append(self.lyric)
self.lyric_syll = False

Expand Down
63 changes: 58 additions & 5 deletions ly/musicxml/lymus2musxml.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,11 @@ def __init__(self):
self.voice_sep_length = 0
self.voice_sep_first_meas = False
self.sims_and_seqs = []
self.override_key = ''
self.override_key_word_count = 0
self.override_dict = {}
self.revert_key = ''
self.revert_key_word_count = 0
self.ottava = False
self.with_contxt = None
self.schm_assignm = None
Expand Down Expand Up @@ -1375,7 +1379,10 @@ def String(self, string):
See check_for_barline() and generate_location_dicts() for more on how `\bar "..."` is implemented
"""
if self.alt_mode == 'lyric' and self.look_behind(string, ly.music.items.LyricText):
self.mediator.new_lyrics_text(string.tokens[0])
if 'LyricText_font-shape' in self.override_dict and self.override_dict['LyricText_font-shape'] == 'italic':
self.mediator.new_lyrics_text(string.tokens[0], True)
else:
self.mediator.new_lyrics_text(string.tokens[0])

def LyricsTo(self, lyrics_to):
r"""A \lyricsto expression. """
Expand All @@ -1390,7 +1397,10 @@ def LyricText(self, lyrics_text):
in which case, wait to allow that String to be the lyric text (see String() above)
"""
if lyrics_text.token or not self.look_ahead(lyrics_text, ly.music.items.String):
self.mediator.new_lyrics_text(lyrics_text.token)
if 'LyricText_font-shape' in self.override_dict and self.override_dict['LyricText_font-shape'] == 'italic':
self.mediator.new_lyrics_text(lyrics_text.token, True)
else:
self.mediator.new_lyrics_text(lyrics_text.token)

def LyricItem(self, lyrics_item):
"""Another lyric item (skip, extender, hyphen or tie)."""
Expand Down Expand Up @@ -1425,10 +1435,53 @@ def LyricMode(self, lyricmode):
def Override(self, override):
r"""An \override command."""
self.override_key = ''
self.override_key_word_count = len(override) - 1

def Revert(self, revert):
r"""A \revert command."""
self.revert_key = ''
self.revert_key_word_count = len(revert)

def handle_override_revert_items(self, item):
r"""
Handle items which come after \override or \revert.
For override:
If the item is not the final one, it should be appended to the key.
The final item should be the value assigned to the key in override_dict.
For revert:
All items compose the key, and once complete, that key should be popped from override_dict.
"""
# Override item
if self.look_behind(item, ly.music.items.Override):
# First word in key
if len(self.override_key) == 0:
self.override_key += item.token
# Secondary word in key (separated by _)
elif self.override_key.count('_') < self.override_key_word_count - 1:
self.override_key += '_' + item.token
# Key is complete, value is assigned in dictionary
else:
self.override_dict[self.override_key] = str(item.token)
# Revert item
elif self.look_behind(item, ly.music.items.Revert):
# First word in key
if len(self.revert_key) == 0:
self.revert_key += item.token
# Secondary word in key (separated by _)
elif self.revert_key.count('_') < self.revert_key_word_count - 1:
self.revert_key += '_' + item.token
# Key is complete, value with revert_key is popped from dictionary
if self.revert_key.count('_') == self.revert_key_word_count - 1:
try:
self.override_dict.pop(self.revert_key)
except KeyError as ke:
eprint("Warning: Revert could not find override to remove!")

def PathItem(self, item):
r"""An item in the path of an \override or \revert command."""
self.override_key += item.token
self.handle_override_revert_items(item)

def Scheme(self, scheme):
"""A Scheme expression inside LilyPond."""
Expand All @@ -1439,8 +1492,8 @@ def SchemeItem(self, item):
if self.ottava:
self.mediator.new_ottava(item.token)
self.ottava = False
elif self.look_behind(item, ly.music.items.Override):
self.override_dict[self.override_key] = item.token
elif self.look_behind(item, ly.music.items.Override) or self.look_behind(item, ly.music.items.Revert):
self.handle_override_revert_items(item)
elif self.schm_assignm:
self.mediator.set_by_property(self.schm_assignm, item.token)
elif self.look_behind(item, ly.music.items.Set):
Expand Down
11 changes: 7 additions & 4 deletions ly/musicxml/xml_objs.py
Original file line number Diff line number Diff line change
Expand Up @@ -239,11 +239,14 @@ def new_xml_note(self, obj):
self.gener_xml_mus(obj) # Notations must be added before lyrics to have a valid XML
if obj.lyric:
for l in obj.lyric:
# Allows a lyric to have the extend tag if necessary
args = []
# Allows a lyric to be italicized
if "italic" in l:
args.append("italic")
# Allows a lyric to have the extend tag
if "extend" in l:
self.musxml.add_lyric(l[0], l[1], l[2], "extend")
else:
self.musxml.add_lyric(l[0], l[1], l[2])
args.append("extend")
self.musxml.add_lyric(l[0], l[1], l[2], args)

def new_xml_rest(self, obj):
"""Create rest specific xml-nodes."""
Expand Down
35 changes: 35 additions & 0 deletions tests/test_xml_files/italics.ly
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
\version "2.18.2"

Stanza = \lyricmode {
Font style
\override LyricText #'font-shape = #'italic
is su -- per cool
\revert LyricText #'font-shape
but not here
}

\language "english"

keyTime = {
\time 4/4
\key c \major
}

Soprano = \relative c'' {
\keyTime
c4 c c c
c c c c
c1 \bar "|."
}

\score
{
<<
\new Staff = "treble" \with {}
<<
\clef "treble"
\new Voice = "SopranoVoice" \Soprano
\lyricsto SopranoVoice \new Lyrics \Stanza
>>
>>
}
166 changes: 166 additions & 0 deletions tests/test_xml_files/italics.musicxml
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
<?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>2019-12-20</encoding-date>
</encoding>
</identification>
<part-list>
<score-part id="P1">
<part-name />
</score-part>
</part-list>
<part id="P1">
<measure number="1">
<attributes>
<divisions>1</divisions>
<key>
<fifths>0</fifths>
<mode>major</mode>
</key>
<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>
<lyric number="1">
<syllabic>single</syllabic>
<text>Font</text>
</lyric>
</note>
<note>
<pitch>
<step>C</step>
<octave>5</octave>
</pitch>
<duration>1</duration>
<voice>1</voice>
<type>quarter</type>
<lyric number="1">
<syllabic>single</syllabic>
<text>style</text>
</lyric>
</note>
<note>
<pitch>
<step>C</step>
<octave>5</octave>
</pitch>
<duration>1</duration>
<voice>1</voice>
<type>quarter</type>
<lyric number="1">
<syllabic>single</syllabic>
<text font-style="italic">is</text>
</lyric>
</note>
<note>
<pitch>
<step>C</step>
<octave>5</octave>
</pitch>
<duration>1</duration>
<voice>1</voice>
<type>quarter</type>
<lyric number="1">
<syllabic>begin</syllabic>
<text font-style="italic">su</text>
</lyric>
</note>
<backup>
<duration>4</duration>
</backup>
</measure>
<measure number="2">
<note>
<pitch>
<step>C</step>
<octave>5</octave>
</pitch>
<duration>1</duration>
<voice>1</voice>
<type>quarter</type>
<lyric number="1">
<syllabic>end</syllabic>
<text font-style="italic">per</text>
</lyric>
</note>
<note>
<pitch>
<step>C</step>
<octave>5</octave>
</pitch>
<duration>1</duration>
<voice>1</voice>
<type>quarter</type>
<lyric number="1">
<syllabic>single</syllabic>
<text font-style="italic">cool</text>
</lyric>
</note>
<note>
<pitch>
<step>C</step>
<octave>5</octave>
</pitch>
<duration>1</duration>
<voice>1</voice>
<type>quarter</type>
<lyric number="1">
<syllabic>single</syllabic>
<text>but</text>
</lyric>
</note>
<note>
<pitch>
<step>C</step>
<octave>5</octave>
</pitch>
<duration>1</duration>
<voice>1</voice>
<type>quarter</type>
<lyric number="1">
<syllabic>single</syllabic>
<text>not</text>
</lyric>
</note>
</measure>
<measure number="3">
<note>
<pitch>
<step>C</step>
<octave>5</octave>
</pitch>
<duration>4</duration>
<voice>1</voice>
<type>whole</type>
<lyric number="1">
<syllabic>single</syllabic>
<text>here</text>
</lyric>
</note>
<backup>
<duration>4</duration>
</backup>
<attributes />
<barline location="right">
<bar-style>light-heavy</bar-style>
</barline>
</measure>
</part>
</score-partwise>

0 comments on commit ef75d1c

Please sign in to comment.