]> git.donarmstrong.com Git - lilypond.git/commitdiff
Merge branch 'master' of ssh+git://hanwen@git.sv.gnu.org/srv/git/lilypond
authorHan-Wen Nienhuys <hanwen@xs4all.nl>
Sat, 1 Sep 2007 03:08:23 +0000 (00:08 -0300)
committerHan-Wen Nienhuys <hanwen@xs4all.nl>
Sat, 1 Sep 2007 03:08:23 +0000 (00:08 -0300)
input/regression/beam-cross-staff-script.ly [new file with mode: 0644]
python/musicexp.py
python/musicxml.py
scripts/musicxml2ly.py

diff --git a/input/regression/beam-cross-staff-script.ly b/input/regression/beam-cross-staff-script.ly
new file mode 100644 (file)
index 0000000..0c5294e
--- /dev/null
@@ -0,0 +1,28 @@
+\header {
+
+  texidoc = "scripts don't trigger beam formatting.  If this
+does happen, we can have a cyclic dependency on Y-positions of
+staves."
+
+}
+
+
+\version "2.11.30"
+
+\new PianoStaff <<
+   \new Staff = RH {
+      \time 1/4
+      c''16 [
+      c''16
+      \change Staff = LH
+      c''16 \tenuto _ \markup { foo }
+      \change Staff = RH
+      c''16 ]
+   }
+   \new Staff = LH {
+      s4
+   }
+>>
+
+%%% END %%%
+
index b543dbc2e6056d5267406d485c13137d4dc25d15..abe3125955437bd3e7352372c3a5f518aac9c95e 100644 (file)
@@ -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)
@@ -481,13 +499,13 @@ class SlurEvent (SpanEvent):
     def ly_expression (self):
         return {-1: '(',
             0:'',
-            1:')'}[self.span_direction]
+            1:')'}.get (self.span_direction, '')
 
 class BeamEvent (SpanEvent):
     def ly_expression (self):
         return {-1: '[',
             0:'',
-            1:']'}[self.span_direction]
+            1:']'}.get (self.span_direction, '')
 
 class ArpeggioEvent(Event):
     def ly_expression (self):
@@ -503,11 +521,7 @@ class HairpinEvent (Event):
     def __init__ (self, type):
         self.type = type
     def hairpin_to_ly (self):
-        val = ''
-        tp = { 0: '\!', 1: '\<', -1: '\>' }.get (self.type)
-        if tp:
-            val += tp
-        return val
+        return { 0: '\!', 1: '\<', -1: '\>' }.get (self.type, '')
     
     def ly_expression (self):
         return self.hairpin_to_ly ()
@@ -549,11 +563,7 @@ class ArticulationEvent (Event):
         self.force_direction = None
 
     def direction_mod (self):
-        dirstr = { 1: '^', -1: '_', 0: '-' }.get (self.force_direction)
-        if dirstr:
-            return dirstr
-        else:
-            return ''
+        return { 1: '^', -1: '_', 0: '-' }.get (self.force_direction, '')
 
     def ly_expression (self):
         return '%s\\%s' % (self.direction_mod (), self.type)
@@ -679,7 +689,7 @@ class ClefChange (Music):
         }
     
     def lisp_expression (self):
-        (glyph, pos, c0) = self.clef_dict [self.type]
+        (glyph, pos, c0) = self.clef_dict.get (self.type)
         clefsetting = """
         (make-music 'SequentialMusic
         'elements (list
index 132ce5a752021d2798d60f0c86425873cb37b628..0430661da98cb7a0fe52e58580d198b7ccb4e021 100644 (file)
@@ -1,4 +1,5 @@
 import new
+import string
 from rational import *
 
 class Xml_node:
@@ -45,7 +46,7 @@ class Xml_node:
             return [c for c in self._children if isinstance(c, klass)]
 
     def get_named_children (self, nm):
-       return self.get_typed_children (class_dict.get (nm))
+       return self.get_typed_children (get_class (nm))
 
     def get_named_child (self, nm):
        return self.get_maybe_exist_named_child (nm)
@@ -57,7 +58,7 @@ class Xml_node:
        return self._children
 
     def get_maybe_exist_named_child (self, name):
-       return self.get_maybe_exist_typed_child (class_dict[name])
+       return self.get_maybe_exist_typed_child (get_class (name))
 
     def get_maybe_exist_typed_child (self, klass):
        cn = self.get_typed_children (klass)
@@ -82,6 +83,73 @@ class Music_xml_node (Xml_node):
        self.duration = Rational (0)
        self.start = Rational (0)
 
+class Work (Xml_node):
+    def get_work_information (self, tag):
+        wt = self.get_maybe_exist_named_child (tag)
+        if wt:
+            return wt.get_text ()
+        else:
+            return ''
+      
+    def get_work_title (self):
+        return self.get_work_information ('work-title')
+    def get_work_number (self):
+        return self.get_work_information ('work-number')
+    def get_opus (self):
+        return self.get_work_information ('opus')
+
+class Identification (Xml_node):
+    def get_rights (self):
+        rights = self.get_maybe_exist_named_child ('rights')
+        if rights:
+            return rights.get_text ()
+        else:
+            return ''
+
+    def get_creator (self, type):
+        creators = self.get_named_children ('creator')
+        # return the first creator tag that has type 'editor'
+        for i in creators:
+            if hasattr (i, 'type') and i.type == type:
+                return i.get_text ()
+            else:
+                return ''
+
+    def get_composer (self):
+        c = self.get_creator ('composer')
+        if c:
+            return c
+        creators = self.get_named_children ('creator')
+        # return the first creator tag that has no type at all
+        for i in creators:
+            if not hasattr (i, 'type'):
+                return i.get_text ()
+        return c
+    def get_arranger (self):
+        return self.get_creator ('arranger')
+    def get_editor (self):
+        return self.get_creator ('editor')
+    def get_poet (self):
+        return self.get_creator ('poet')
+    
+    def get_encoding_information (self, type):
+        enc = self.get_named_children ('encoding')
+        if enc:
+            children = enc[0].get_named_children (type)
+            if children:
+                return children[0].get_text ()
+        else:
+            return ''
+      
+    def get_encoding_software (self):
+        return self.get_encoding_information ('software')
+    def get_encoding_date (self):
+        return self.get_encoding_information ('encoding-date')
+    def get_encoding_person (self):
+        return self.get_encoding_information ('encoder')
+    def get_encoding_description (self):
+        return self.get_encoding_information ('encoding-description')
+
 
 class Duration (Music_xml_node):
     def get_length (self):
@@ -93,17 +161,17 @@ class Hash_comment (Music_xml_node):
 
 class Pitch (Music_xml_node):
     def get_step (self):
-       ch = self.get_unique_typed_child (class_dict[u'step'])
+       ch = self.get_unique_typed_child (get_class (u'step'))
        step = ch.get_text ().strip ()
        return step
     def get_octave (self):
-       ch = self.get_unique_typed_child (class_dict[u'octave'])
+       ch = self.get_unique_typed_child (get_class (u'octave'))
 
        step = ch.get_text ().strip ()
        return int (step)
 
     def get_alteration (self):
-       ch = self.get_maybe_exist_typed_child (class_dict[u'alter'])
+       ch = self.get_maybe_exist_typed_child (get_class (u'alter'))
        alter = 0
        if ch:
            alter = int (ch.get_text ().strip ())
@@ -135,7 +203,7 @@ class Attributes (Measure_element):
            self._dict[c.get_name()] = c
 
     def get_named_attribute (self, name):
-       return self._dict[name]
+       return self._dict.get (name)
 
     def get_measure_length (self):
         (n,d) = self.get_time_signature ()
@@ -183,7 +251,7 @@ class Note (Measure_element):
         self.instrument_name = ''
         
     def get_duration_log (self):
-       ch = self.get_maybe_exist_typed_child (class_dict[u'type'])
+       ch = self.get_maybe_exist_typed_child (get_class (u'type'))
 
        if ch:
            log = ch.get_text ().strip()
@@ -194,7 +262,7 @@ class Note (Measure_element):
                     '32nd': 5,
                     'breve': -1,
                     'long': -2,
-                    'whole': 0} [log]
+                    'whole': 0}.get (log)
        else:
            return 0
 
@@ -202,7 +270,7 @@ class Note (Measure_element):
        return 1
 
     def get_pitches (self):
-       return self.get_typed_children (class_dict[u'pitch'])
+       return self.get_typed_children (get_class (u'pitch'))
 
 class Part_list (Music_xml_node):
     def __init__ (self):
@@ -225,22 +293,64 @@ class Part_list (Music_xml_node):
         if not self._id_instrument_name_dict:
             self.generate_id_instrument_dict()
 
-        try:
-            return self._id_instrument_name_dict[id]
-        except KeyError:
+        instrument_name = self._id_instrument_name_dict.get (id)
+        if instrument_name:
+            return instrument_name
+        else:
             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 (class_dict[u'note'])
+       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()
+            # We need to convert soft hyphens to -, otherwise the ascii codec as well
+            # as lilypond will barf on that character
+            text = string.replace( text, u'\xad', '-' )
+        
+        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)
@@ -252,9 +362,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 <lyric> tags has a number attribute
+            return [1]
+        else:
+            return self._lyrics
+
 
 
 class Part (Music_xml_node):
@@ -294,7 +420,7 @@ class Part (Music_xml_node):
                     attributes_object = n
                     
                    factor = Rational (1,
-                                      int (attributes_dict['divisions'].get_text ()))
+                                      int (attributes_dict.get ('divisions').get_text ()))
 
                 
                if (n.get_maybe_exist_typed_child (Duration)):
@@ -355,7 +481,7 @@ class Part (Music_xml_node):
 
        start_attr = None
        for n in elements:
-           voice_id = n.get_maybe_exist_typed_child (class_dict['voice'])
+           voice_id = n.get_maybe_exist_typed_child (get_class ('voice'))
 
             # TODO: If the first element of a voice is a dynamics entry,
             #       then voice_id is not yet set! Thus it will currently be ignored
@@ -400,8 +526,8 @@ class Notations (Music_xml_node):
 
 class Time_modification(Music_xml_node):
     def get_fraction (self):
-       b = self.get_maybe_exist_typed_child (class_dict['actual-notes'])
-       a = self.get_maybe_exist_typed_child (class_dict['normal-notes'])
+       b = self.get_maybe_exist_named_child ('actual-notes')
+       a = self.get_maybe_exist_named_child ('normal-notes')
        return (int(a.get_text ()), int (b.get_text ()))
 
 class Accidental (Music_xml_node):
@@ -430,9 +556,6 @@ class Chord (Music_xml_node):
 class Dot (Music_xml_node):
     pass
 
-class Alter (Music_xml_node):
-    pass
-
 class Rest (Music_xml_node):
     def __init__ (self):
         Music_xml_node.__init__ (self)
@@ -440,11 +563,6 @@ class Rest (Music_xml_node):
     def is_whole_measure (self):
         return self._is_whole_measure
 
-class Mode (Music_xml_node):
-    pass
-class Tied (Music_xml_node):
-    pass
-
 class Type (Music_xml_node):
     pass
 class Grace (Music_xml_node):
@@ -452,76 +570,41 @@ class Grace (Music_xml_node):
 class Staff (Music_xml_node):
     pass
 
-class Instrument (Music_xml_node):
-    pass
-
-class Fermata (Music_xml_node):
-    pass
-class Dynamics (Music_xml_node):
-    pass
-class Articulations (Music_xml_node):
-    pass
-class Accent (Music_xml_node):
-    pass
-class Staccato (Music_xml_node):
-    pass
-class Tenuto (Music_xml_node):
-    pass
-class Tremolo (Music_xml_node):
-    pass
-class Technical (Music_xml_node):
-    pass
-class Ornaments (Music_xml_node):
-    pass
-
-
 class Direction (Music_xml_node):
     pass
 class DirType (Music_xml_node):
     pass
-class Wedge (Music_xml_node):
-    pass
 
 
 ## need this, not all classes are instantiated
-## for every input file.
+## for every input file. Only add those classes, that are either directly
+## used by class name or extend Music_xml_node in some way!
 class_dict = {
        '#comment': Hash_comment,
        'accidental': Accidental,
-       'alter': Alter,
        'attributes': Attributes,
        'beam' : Beam,
        'chord': Chord,
        'dot': Dot,
+       'direction': Direction,
+        'direction-type': DirType,
        'duration': Duration,
        'grace': Grace,
-        'instrument': Instrument, 
-       'mode' : Mode,
+        'identification': Identification,
+        'lyric': Lyric,
        'measure': Measure,
        'notations': Notations,
        'note': Note,
        'part': Part,
+       'part-list': Part_list,
        'pitch': Pitch,
-       'rest':Rest,
+       'rest': Rest,
        'slur': Slur,
-       'tied': Tied,
+        'syllabic': Syllabic,
+        'text': Text,
        'time-modification': Time_modification,
-       'tuplet': Tuplet,
        'type': Type,
-       'part-list': Part_list,
-       'staff': Staff,
-        'fermata': Fermata,
-        'articulations': Articulations,
-        'accent': Accent,
-        'staccato': Staccato,
-        'tenuto': Tenuto,
-        'tremolo': Tremolo,
-        'technical': Technical,
-        'ornaments': Ornaments,
-        'direction': Direction,
-        'direction-type': DirType,
-        'dynamics': Dynamics,
-        'wedge': Wedge
+        'work': Work,
 }
 
 def name2class_name (name):
@@ -532,9 +615,10 @@ def name2class_name (name):
     return str (name)
 
 def get_class (name):
-    try:
-        return class_dict[name]
-    except KeyError:
+    classname = class_dict.get (name)
+    if classname:
+        return classname
+    else:
        class_name = name2class_name (name)
        klass = new.classobj (class_name, (Music_xml_node,) , {})
        class_dict[name] = klass
index 373dd5d1f10e25e78f84dea438388df5c6d4a91e..ed57e29edb80f47b0bba66c229c42c55ce013613 100644 (file)
@@ -24,6 +24,59 @@ def progress (str):
     sys.stderr.flush ()
     
 
+# score information is contained in the <work>, <identification> or <movement-title> tags
+# extract those into a hash, indexed by proper lilypond header attributes
+def extract_score_information (tree):
+    score_information = {}
+    work = tree.get_maybe_exist_named_child ('work')
+    if work:
+        if work.get_work_title ():
+            score_information['title'] = work.get_work_title ()
+        if work.get_work_number ():
+            score_information['worknumber'] = work.get_work_number ()
+        if work.get_opus ():
+            score_information['opus'] = work.get_opus ()
+    else:
+        movement_title = tree.get_maybe_exist_named_child ('movement-title')
+        if movement_title:
+            score_information['title'] = movement_title.get_text ()
+    
+    identifications = tree.get_named_children ('identification')
+    for ids in identifications:
+        if ids.get_rights ():
+            score_information['copyright'] = ids.get_rights ()
+        if ids.get_composer ():
+            score_information['composer'] = ids.get_composer ()
+        if ids.get_arranger ():
+            score_information['arranger'] = ids.get_arranger ()
+        if ids.get_editor ():
+            score_information['editor'] = ids.get_editor ()
+        if ids.get_poet ():
+            score_information['poet'] = ids.get_poet ()
+            
+        if ids.get_encoding_software ():
+            score_information['tagline'] = ids.get_encoding_software ()
+            score_information['encodingsoftware'] = ids.get_encoding_software ()
+        if ids.get_encoding_date ():
+            score_information['encodingdate'] = ids.get_encoding_date ()
+        if ids.get_encoding_person ():
+            score_information['encoder'] = ids.get_encoding_person ()
+        if ids.get_encoding_description ():
+            score_information['encodingdescription'] = ids.get_encoding_description ()
+
+    return score_information
+
+def print_ly_information (printer, score_information):
+    printer.dump ('\header {')
+    printer.newline ()
+    for k in score_information.keys ():
+        printer.dump ('%s = "%s"' % (k, score_information[k]))
+        printer.newline ()
+    printer.dump ('}')
+    printer.newline ()
+    printer.newline ()
+    
+
 def musicxml_duration_to_lily (mxl_note):
     d = musicexp.Duration ()
     if mxl_note.get_maybe_exist_typed_child (musicxml.Type):
@@ -49,7 +102,7 @@ def group_tuplets (music_list, events):
     j = 0
     for (ev_chord, tuplet_elt, fraction) in events:
         while (j < len (music_list)):
-            if music_list[j]== ev_chord:
+            if music_list[j] == ev_chord:
                 break
             j += 1
         if tuplet_elt.type == 'start':
@@ -154,26 +207,23 @@ def musicxml_spanner_to_lily_event (mxl_event):
     ev = None
     
     name = mxl_event.get_name()
-    try:
-        func = spanner_event_dict[name]
+    func = spanner_event_dict.get (name)
+    if func:
         ev = func()
-    except KeyError:
+    else:
         print 'unknown span event ', mxl_event
 
-    try:
-        key = mxl_event.get_type ()
-        ev.span_direction = spanner_type_dict[key]
-    except KeyError:
+    key = mxl_event.get_type ()
+    span_direction = spanner_type_dict.get (key)
+    if span_direction:
+        ev.span_direction = span_direction
+    else:
         print 'unknown span type', key, 'for', name
 
     return ev
 
 def musicxml_direction_to_indicator (direction):
-    val = { "above": 1, "upright": 1, "below": -1, "downright": -1 }.get (direction)
-    if val:
-        return val
-    else:
-        return ''
+    return { "above": 1, "upright": 1, "below": -1, "downright": -1 }.get (direction, '')
 
 def musicxml_fermata_to_lily_event (mxl_event):
     ev = musicexp.ArticulationEvent ()
@@ -201,12 +251,12 @@ articulations_dict = {
     #"shake": "?", 
     #"wavy-line": "?", 
     "mordent": "mordent",
-    #"inverted-mordent": "?", 
+    "inverted-mordent": "downmordent", 
     #"schleifer": "?" 
     ##### TECHNICALS
     "up-bow": "upbow", 
     "down-bow": "downbow", 
-    #"harmonic": "", 
+    "harmonic": "flageolet", 
     #"open-string": "", 
     #"thumb-position": "", 
     #"fingering": "", 
@@ -256,7 +306,8 @@ def musicxml_articulation_to_lily_event(mxl_event):
         dir = musicxml_direction_to_indicator (mxl_event.type)
     if hasattr (mxl_event, 'placement'):
         dir = musicxml_direction_to_indicator (mxl_event.placement)
-    if dir:
+    # \breathe cannot have any direction modifyer (^, _, -)!
+    if dir and tp != "breathe":
         ev.force_direction = dir
     return ev
 
@@ -286,6 +337,8 @@ def musicxml_direction_to_lily( n ):
                 wedgetype = entry.type;
                 wedgetypeval = {"crescendo" : 1, "decrescendo" : -1, 
                                 "diminuendo" : -1, "stop" : 0 }.get (wedgetype)
+                # Really check for != None, becaus otherwise 0 will also cause 
+                # the code to be executed!
                 if wedgetypeval != None:
                     event = musicexp.HairpinEvent (wedgetypeval)
                     res.append (event)
@@ -320,9 +373,10 @@ def musicxml_note_to_lily_main_event (n):
         event = musicexp.RestEvent()
     elif n.instrument_name:
         event = musicexp.NoteEvent ()
-        try:
-            event.drum_type = instrument_drumtype_dict[n.instrument_name]
-        except KeyError:
+        drum_type = instrument_drumtype_dict.get (n.instrument_name)
+        if drum_type:
+            event.drum_type = drum_type
+        else:
             n.message ("drum %s type unknow, please add to instrument_drumtype_dict" % n.instrument_name)
             event.drum_type = 'acousticsnare'
     
@@ -369,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:
@@ -424,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()
 
@@ -475,11 +533,8 @@ def musicxml_voice_to_lily_voice (voice):
         
         main_event = musicxml_note_to_lily_main_event (n)
 
-        try:
-            if main_event.drum_type:
-                modes_found['drummode'] = True
-        except AttributeError:
-            pass
+        if hasattr (main_event, 'drum_type') and main_event.drum_type:
+            modes_found['drummode'] = True
 
 
         ev_chord = voice_builder.last_event_chord (n._when)
@@ -565,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 ())] 
@@ -586,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 <pitch> barfs in drummode.
@@ -594,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:
@@ -607,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):
@@ -651,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
@@ -700,18 +775,28 @@ 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():
         part_dict[part.id] = (part, nv_dict)
 
     for part in part_list:
-        (part, nv_dict) = part_dict[part.id]
-        for (name, (voice, mxlvoice)) in nv_dict.items ():
+        (part, nv_dict) = part_dict.get (part.id, (None, {}))
+        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):
@@ -724,13 +809,13 @@ def print_score_setup (printer, part_list, voices):
     printer.newline ()
     for part_definition in part_list:
         part_name = part_definition.id
-        try:
-            part = part_dict[part_name]
-        except KeyError:
+        part = part_dict.get (part_name)
+        if not part:
             print 'unknown part in part-list:', part_name
             continue
 
-        nv_dict = voices[part]
+        # 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 ()
                  for (v, mxlvoice) in nv_dict.values ()],
@@ -743,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 ()
                 
@@ -762,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 ()
             
@@ -803,6 +903,8 @@ def convert (filename, options):
         mxl_pl = tree.get_maybe_exist_typed_child (musicxml.Part_list)
         part_list = mxl_pl.get_named_children ("score-part")
         
+    # score information is contained in the <work>, <identification> or <movement-title> tags
+    score_information = extract_score_information (tree)
     parts = tree.get_typed_children (musicxml.Part)
     voices = get_all_voices (parts)
 
@@ -819,6 +921,7 @@ def convert (filename, options):
     printer.set_file (open (defs_ly_name, 'w'))
 
     print_ly_preamble (printer, filename)
+    print_ly_information (printer, score_information)
     print_voice_definitions (printer, part_list, voices)
     
     printer.close ()