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