From 6b44d51560274b78fcb3d3936c7ae480e192c791 Mon Sep 17 00:00:00 2001
From: Reinhold Kainhofer
Date: Thu, 30 Aug 2007 00:48:58 +0200
Subject: [PATCH] Implement the conversion of lyrics from MusicXML to lilypond.
---
python/musicexp.py | 20 +++++++++++++-
python/musicxml.py | 60 ++++++++++++++++++++++++++++++++++++++++-
scripts/musicxml2ly.py | 68 +++++++++++++++++++++++++++++++++++++++++------
3 files changed, 136 insertions(+), 12 deletions(-)
diff --git a/python/musicexp.py b/python/musicexp.py
index 38f772c..abe3125 100644
--- a/python/musicexp.py
+++ b/python/musicexp.py
@@ -413,7 +413,25 @@ class SequentialMusic (NestedMusic):
for e in self.elements:
e.set_start (start)
start += e.get_length()
-
+
+class Lyrics:
+ def __init__ (self):
+ self.lyrics_syllables = []
+
+ def print_ly (self, printer):
+ printer.dump ("\lyricmode {")
+ for l in self.lyrics_syllables:
+ printer.dump ( "%s " % l )
+ printer.dump ("}")
+
+ def ly_expression (self):
+ lstr = "\lyricmode {\n "
+ for l in self.lyrics_syllables:
+ lstr += l + " "
+ lstr += "\n}"
+ return lstr
+
+
class EventChord(NestedMusic):
def get_length (self):
l = Rational (0)
diff --git a/python/musicxml.py b/python/musicxml.py
index 2b7d342..d394697 100644
--- a/python/musicxml.py
+++ b/python/musicxml.py
@@ -299,16 +299,53 @@ class Part_list (Music_xml_node):
print "Opps, couldn't find instrument for ID=", id
return "Grand Piano"
-class Measure(Music_xml_node):
+class Measure (Music_xml_node):
def get_notes (self):
return self.get_typed_children (get_class (u'note'))
-
+class Syllabic (Music_xml_node):
+ def continued (self):
+ text = self.get_text()
+ return (text == "begin") or (text == "middle")
+class Text (Music_xml_node):
+ pass
+
+class Lyric (Music_xml_node):
+ def get_number (self):
+ if hasattr (self, 'number'):
+ return self.number
+ else:
+ return -1
+
+ def lyric_to_text (self):
+ continued = False
+ syllabic = self.get_maybe_exist_typed_child (Syllabic)
+ if syllabic:
+ continued = syllabic.continued ()
+ text = self.get_maybe_exist_typed_child (Text)
+
+ if text:
+ text = text.get_text()
+ if text == "-" and continued:
+ return "--"
+ elif text == "_" and continued:
+ return "__"
+ elif continued and text:
+ return text + " --"
+ elif continued:
+ return "--"
+ elif text:
+ return text
+ else:
+ return ""
+
class Musicxml_voice:
def __init__ (self):
self._elements = []
self._staves = {}
self._start_staff = None
+ self._lyrics = []
+ self._has_lyrics = False
def add_element (self, e):
self._elements.append (e)
@@ -320,9 +357,25 @@ class Musicxml_voice:
self._start_staff = name
self._staves[name] = True
+ lyrics = e.get_typed_children (Lyric)
+ if not self._has_lyrics:
+ self.has_lyrics = len (lyrics) > 0
+
+ for l in lyrics:
+ nr = l.get_number()
+ if (nr > 0) and not (nr in self._lyrics):
+ self._lyrics.append (nr)
+
def insert (self, idx, e):
self._elements.insert (idx, e)
+ def get_lyrics_numbers (self):
+ if (len (self._lyrics) == 0) and self._has_lyrics:
+ #only happens if none of the tags has a number attribute
+ return [1]
+ else:
+ return self._lyrics
+
class Part (Music_xml_node):
@@ -533,6 +586,7 @@ class_dict = {
'duration': Duration,
'grace': Grace,
'identification': Identification,
+ 'lyric': Lyric,
'measure': Measure,
'notations': Notations,
'note': Note,
@@ -541,6 +595,8 @@ class_dict = {
'pitch': Pitch,
'rest': Rest,
'slur': Slur,
+ 'syllabic': Syllabic,
+ 'text': Text,
'time-modification': Time_modification,
'type': Type,
'work': Work,
diff --git a/scripts/musicxml2ly.py b/scripts/musicxml2ly.py
index 45ffbb8..ed57e29 100644
--- a/scripts/musicxml2ly.py
+++ b/scripts/musicxml2ly.py
@@ -423,7 +423,7 @@ class LilyPondVoiceBuilder:
self.elements.append (music)
self.begin_moment = self.end_moment
self.end_moment = self.begin_moment + duration
-
+
# Insert all pending dynamics right after the note/rest:
if duration > Rational (0):
for d in self.pending_dynamics:
@@ -478,6 +478,10 @@ class LilyPondVoiceBuilder:
def musicxml_voice_to_lily_voice (voice):
tuplet_events = []
modes_found = {}
+ lyrics = {}
+
+ for k in voice.get_lyrics_numbers ():
+ lyrics[k] = []
voice_builder = LilyPondVoiceBuilder()
@@ -616,6 +620,22 @@ def musicxml_voice_to_lily_voice (voice):
if ev:
ev_chord.append (ev)
+ # Extract the lyrics
+ note_lyrics_processed = []
+ note_lyrics_elements = n.get_typed_children (musicxml.Lyric)
+ for l in note_lyrics_elements:
+ if l.get_number () < 0:
+ for k in lyrics.keys ():
+ lyrics[k].append (l.lyric_to_text ())
+ note_lyrics_processed.append (k)
+ else:
+ lyrics[l.number].append(l.lyric_to_text ())
+ note_lyrics_processed.append (l.number)
+ for lnr in lyrics.keys ():
+ if not lnr in note_lyrics_processed:
+ lyrics[lnr].append ("\"\"")
+
+
mxl_beams = [b for b in n.get_named_children ('beam')
if (b.get_type () in ('begin', 'end')
and b.is_primary ())]
@@ -637,7 +657,7 @@ def musicxml_voice_to_lily_voice (voice):
ly_voice = group_tuplets (voice_builder.elements, tuplet_events)
- seq_music = musicexp.SequentialMusic()
+ seq_music = musicexp.SequentialMusic ()
if 'drummode' in modes_found.keys ():
## \key barfs in drummode.
@@ -645,7 +665,10 @@ def musicxml_voice_to_lily_voice (voice):
if not isinstance(e, musicexp.KeySignatureChange)]
seq_music.elements = ly_voice
-
+ lyrics_dict = {}
+ for k in lyrics.keys ():
+ lyrics_dict[k] = musicexp.Lyrics ()
+ lyrics_dict[k].lyrics_syllables = lyrics[k]
if len (modes_found) > 1:
@@ -658,7 +681,7 @@ def musicxml_voice_to_lily_voice (voice):
v.mode = mode
return_value = v
- return return_value
+ return (return_value, lyrics_dict)
def musicxml_id_to_lily (id):
@@ -702,6 +725,7 @@ def get_all_voices (parts):
part_ly_voices = {}
for n, v in name_voice.items ():
progress ("Converting to LilyPond expressions...")
+ # musicxml_voice_to_lily_voice returns (lily_voice, {nr->lyrics, nr->lyrics})
part_ly_voices[n] = (musicxml_voice_to_lily_voice (v), v)
all_ly_voices[p] = part_ly_voices
@@ -751,6 +775,10 @@ def music_xml_voice_name_to_lily_name (part, name):
str = "Part%sVoice%s" % (part.id, name)
return musicxml_id_to_lily (str)
+def music_xml_lyrics_name_to_lily_name (part, name, lyricsnr):
+ str = "Part%sVoice%sLyrics%s" % (part.id, name, lyricsnr)
+ return musicxml_id_to_lily (str)
+
def print_voice_definitions (printer, part_list, voices):
part_dict={}
for (part, nv_dict) in voices.items():
@@ -758,11 +786,17 @@ def print_voice_definitions (printer, part_list, voices):
for part in part_list:
(part, nv_dict) = part_dict.get (part.id, (None, {}))
- for (name, (voice, mxlvoice)) in nv_dict.items ():
+ for (name, ((voice, lyrics), mxlvoice)) in nv_dict.items ():
k = music_xml_voice_name_to_lily_name (part, name)
printer.dump ('%s = ' % k)
voice.print_ly (printer)
printer.newline()
+
+ for l in lyrics.keys ():
+ lname = music_xml_lyrics_name_to_lily_name (part, name, l)
+ printer.dump ('%s = ' %lname )
+ lyrics[l].print_ly (printer)
+ printer.newline()
def uniq_list (l):
@@ -780,6 +814,7 @@ def print_score_setup (printer, part_list, voices):
print 'unknown part in part-list:', part_name
continue
+ # TODO: Apparently this is broken! There is always only one staff...
nv_dict = voices.get (part)
staves = reduce (lambda x,y: x+ y,
[mxlvoice._staves.keys ()
@@ -793,15 +828,23 @@ def print_score_setup (printer, part_list, voices):
printer.newline ()
for s in staves:
- staff_voices = [music_xml_voice_name_to_lily_name (part, voice_name)
+ staff_voices = [(music_xml_voice_name_to_lily_name (part, voice_name), voice_name, v)
for (voice_name, (v, mxlvoice)) in nv_dict.items ()
if mxlvoice._start_staff == s]
printer ('\\context Staff = "%s" << ' % s)
printer.newline ()
- for v in staff_voices:
+ for (v, voice_name, (music, lyrics)) in staff_voices:
printer ('\\context Voice = "%s" \\%s' % (v,v))
printer.newline ()
+
+ # Assign the lyrics to that voice
+ for l in lyrics.keys ():
+ ll = music_xml_lyrics_name_to_lily_name (part, voice_name, l)
+ printer ('\\new Lyrics \\lyricsto "%s" \\%s' % (v, ll))
+ printer.newline()
+ printer.newline()
+
printer ('>>')
printer.newline ()
@@ -812,9 +855,16 @@ def print_score_setup (printer, part_list, voices):
printer ('\\new Staff <<')
printer.newline ()
for (n,v) in nv_dict.items ():
+ ((music, lyrics), voice) = v
+ nn = music_xml_voice_name_to_lily_name (part, n)
+ printer ('\\context Voice = "%s" \\%s' % (nn,nn))
+
+ # Assign the lyrics to that voice
+ for l in lyrics.keys ():
+ ll = music_xml_lyrics_name_to_lily_name (part, n, l)
+ printer ('\\new Lyrics \\lyricsto "%s" \\%s' % (nn, ll))
+ printer.newline()
- n = music_xml_voice_name_to_lily_name (part, n)
- printer ('\\context Voice = "%s" \\%s' % (n,n))
printer ('>>')
printer.newline ()
--
1.5.2.3