]> git.donarmstrong.com Git - lilypond.git/blob - scripts/musicxml2ly.py
MusicXML: Move printing out header information into its own class
[lilypond.git] / scripts / musicxml2ly.py
1 #!@TARGET_PYTHON@
2
3 import optparse
4 import sys
5 import re
6 import os
7 import string
8 from gettext import gettext as _
9
10 """
11 @relocate-preamble@
12 """
13
14 import lilylib as ly
15
16 import musicxml
17 import musicexp
18
19 from rational import Rational
20
21
22 def progress (str):
23     sys.stderr.write (str + '\n')
24     sys.stderr.flush ()
25
26 def error_message (str):
27     sys.stderr.write (str + '\n')
28     sys.stderr.flush ()
29
30 # score information is contained in the <work>, <identification> or <movement-title> tags
31 # extract those into a hash, indexed by proper lilypond header attributes
32 def extract_score_information (tree):
33     header = musicexp.Header ()
34     def set_if_exists (field, value):
35         if value:
36             header.set_field (field, musicxml.escape_ly_output_string (value))
37
38     work = tree.get_maybe_exist_named_child ('work')
39     if work:
40         set_if_exists ('title', work.get_work_title ())
41         set_if_exists ('worknumber', work.get_work_number ())
42         set_if_exists ('opus', work.get_opus ())
43     else:
44         movement_title = tree.get_maybe_exist_named_child ('movement-title')
45         if movement_title:
46             set_if_exists ('title', movement_title.get_text ())
47     
48     identifications = tree.get_named_children ('identification')
49     for ids in identifications:
50         set_if_exists ('copyright', ids.get_rights ())
51         set_if_exists ('composer', ids.get_composer ())
52         set_if_exists ('arranger', ids.get_arranger ())
53         set_if_exists ('editor', ids.get_editor ())
54         set_if_exists ('poet', ids.get_poet ())
55             
56         set_if_exists ('tagline', ids.get_encoding_software ())
57         set_if_exists ('encodingsoftware', ids.get_encoding_software ())
58         set_if_exists ('encodingdate', ids.get_encoding_date ())
59         set_if_exists ('encoder', ids.get_encoding_person ())
60         set_if_exists ('encodingdescription', ids.get_encoding_description ())
61
62     return header
63
64
65
66 def musicxml_duration_to_lily (mxl_note):
67     d = musicexp.Duration ()
68     # if the note has no Type child, then that method spits out a warning and 
69     # returns 0, i.e. a whole note
70     d.duration_log = mxl_note.get_duration_log ()
71
72     d.dots = len (mxl_note.get_typed_children (musicxml.Dot))
73     # Grace notes by specification have duration 0, so no time modification 
74     # factor is possible. It even messes up the output with *0/1
75     if not mxl_note.get_maybe_exist_typed_child (musicxml.Grace):
76         d.factor = mxl_note._duration / d.get_length ()
77
78     return d         
79
80 def group_tuplets (music_list, events):
81
82
83     """Collect Musics from
84     MUSIC_LIST demarcated by EVENTS_LIST in TimeScaledMusic objects.
85     """
86
87     
88     indices = []
89
90     j = 0
91     for (ev_chord, tuplet_elt, fraction) in events:
92         while (j < len (music_list)):
93             if music_list[j] == ev_chord:
94                 break
95             j += 1
96         if tuplet_elt.type == 'start':
97             indices.append ((j, None, fraction))
98         elif tuplet_elt.type == 'stop':
99             indices[-1] = (indices[-1][0], j, indices[-1][2])
100
101     new_list = []
102     last = 0
103     for (i1, i2, frac) in indices:
104         if i1 >= i2:
105             continue
106
107         new_list.extend (music_list[last:i1])
108         seq = musicexp.SequentialMusic ()
109         last = i2 + 1
110         seq.elements = music_list[i1:last]
111
112         tsm = musicexp.TimeScaledMusic ()
113         tsm.element = seq
114
115         tsm.numerator = frac[0]
116         tsm.denominator  = frac[1]
117
118         new_list.append (tsm)
119
120     new_list.extend (music_list[last:])
121     return new_list
122
123
124 def musicxml_clef_to_lily (attributes):
125     change = musicexp.ClefChange ()
126     (change.type, change.position, change.octave) = attributes.get_clef_information ()
127     return change
128     
129 def musicxml_time_to_lily (attributes):
130     (beats, type) = attributes.get_time_signature ()
131
132     change = musicexp.TimeSignatureChange()
133     change.fraction = (beats, type)
134     
135     return change
136
137 def musicxml_key_to_lily (attributes):
138     start_pitch  = musicexp.Pitch ()
139     (fifths, mode) = attributes.get_key_signature () 
140     try:
141         (n,a) = {
142             'major' : (0,0),
143             'minor' : (5,0),
144             }[mode]
145         start_pitch.step = n
146         start_pitch.alteration = a
147     except  KeyError:
148         error_message ('unknown mode %s' % mode)
149
150     fifth = musicexp.Pitch()
151     fifth.step = 4
152     if fifths < 0:
153         fifths *= -1
154         fifth.step *= -1
155         fifth.normalize ()
156     
157     for x in range (fifths):
158         start_pitch = start_pitch.transposed (fifth)
159
160     start_pitch.octave = 0
161
162     change = musicexp.KeySignatureChange()
163     change.mode = mode
164     change.tonic = start_pitch
165     return change
166     
167 def musicxml_attributes_to_lily (attrs):
168     elts = []
169     attr_dispatch =  {
170         'clef': musicxml_clef_to_lily,
171         'time': musicxml_time_to_lily,
172         'key': musicxml_key_to_lily
173     }
174     for (k, func) in attr_dispatch.items ():
175         children = attrs.get_named_children (k)
176
177         ## ugh: you get clefs spread over staves for piano
178         if children:
179             elts.append (func (attrs))
180     
181     return elts
182
183 spanner_event_dict = {
184     'slur' : musicexp.SlurEvent,
185     'beam' : musicexp.BeamEvent,
186     'glissando' : musicexp.GlissandoEvent,
187     'pedal' : musicexp.PedalEvent,
188     'wavy-line' : musicexp.TrillSpanEvent,
189     'octave-shift' : musicexp.OctaveShiftEvent,
190     'wedge' : musicexp.HairpinEvent
191 }
192 spanner_type_dict = {
193     'start': -1,
194     'begin': -1,
195     'crescendo': -1,
196     'decreschendo': -1,
197     'diminuendo': -1,
198     'continue': 0,
199     'up': -1,
200     'down': -1,
201     'stop': 1,
202     'end' : 1
203 }
204
205 def musicxml_spanner_to_lily_event (mxl_event):
206     ev = None
207     
208     name = mxl_event.get_name()
209     func = spanner_event_dict.get (name)
210     if func:
211         ev = func()
212     else:
213         error_message ('unknown span event %s' % mxl_event)
214
215
216     type = mxl_event.get_type ()
217     span_direction = spanner_type_dict.get (type)
218     # really check for None, because some types will be translated to 0, which
219     # would otherwise also lead to the unknown span warning
220     if span_direction != None:
221         ev.span_direction = span_direction
222     else:
223         error_message ('unknown span type %s for %s' % (type, name))
224
225     ev.set_span_type (type)
226     ev.line_type = getattr (mxl_event, 'line-type', 'solid')
227
228     # assign the size, which is used for octave-shift, etc.
229     ev.size = mxl_event.get_size ()
230
231     return ev
232
233 def musicxml_direction_to_indicator (direction):
234     return { "above": 1, "upright": 1, "below": -1, "downright": -1 }.get (direction, '')
235
236 def musicxml_fermata_to_lily_event (mxl_event):
237     ev = musicexp.ArticulationEvent ()
238     ev.type = "fermata"
239     if hasattr (mxl_event, 'type'):
240       dir = musicxml_direction_to_indicator (mxl_event.type)
241       if dir:
242         ev.force_direction = dir
243     return ev
244
245 def musicxml_tremolo_to_lily_event (mxl_event):
246     if mxl_event.get_name () != "tremolo": 
247         return
248     ev = musicexp.TremoloEvent ()
249     ev.bars = mxl_event.get_text ()
250     return ev
251
252 def musicxml_bend_to_lily_event (mxl_event):
253     if mxl_event.get_name () != "bend":
254         return
255     ev = musicexp.BendEvent ()
256     ev.alter = mxl_event.bend_alter ()
257     return ev
258
259
260 short_articulations_dict = {
261   "staccato": ".",
262   "tenuto": "-",
263   "stopped": "+",
264   "staccatissimo": "|",
265   "accent": ">",
266   "strong-accent": "^",
267   #"portato": "_", # does not exist in MusicXML
268     #"fingering": "", # fingering is special cased, as get_text() will be the event's name
269 }
270 # TODO: Some translations are missing!
271 articulations_dict = { 
272     ##### ORNAMENTS
273     "trill-mark": "trill", 
274     "turn": "turn", 
275     #"delayed-turn": "?", 
276     "inverted-turn": "reverseturn", 
277     #"shake": "?", 
278     #"wavy-line": "?", 
279     "mordent": "mordent",
280     "inverted-mordent": "prall",
281     #"schleifer": "?" 
282     ##### TECHNICALS
283     "up-bow": "upbow", 
284     "down-bow": "downbow", 
285     "harmonic": "flageolet", 
286     #"open-string": "", 
287     #"thumb-position": "", 
288     #"pluck": "", 
289     #"double-tongue": "", 
290     #"triple-tongue": "", 
291     #"snap-pizzicato": "", 
292     #"fret": "", 
293     #"string": "", 
294     #"hammer-on": "", 
295     #"pull-off": "", 
296     #"bend": "bendAfter #%s", # bend is special-cased, as we need to process the bend-alter subelement!
297     #"tap": "", 
298     #"heel": "", 
299     #"toe": "", 
300     #"fingernails": ""
301     ##### ARTICULATIONS
302     #"detached-legato": "", 
303     #"spiccato": "", 
304     #"scoop": "", 
305     #"plop": "", 
306     #"doit": "", 
307     #"falloff": "",
308     "breath-mark": "breathe", 
309     #"caesura": "caesura", 
310     #"stress": "", 
311     #"unstress": ""
312 }
313 articulation_spanners = [ "wavy-line" ]
314
315 def musicxml_articulation_to_lily_event (mxl_event):
316     # wavy-line elements are treated as trill spanners, not as articulation ornaments
317     if mxl_event.get_name () in articulation_spanners:
318         return musicxml_spanner_to_lily_event (mxl_event)
319
320     # special case, because of the bend-alter subelement
321     if mxl_event.get_name() == "bend":
322         return musicxml_bend_to_lily_event (mxl_event)
323
324     # If we can write a shorthand, use them!
325     if mxl_event.get_name() == "fingering":
326         ev = musicexp.ShortArticulationEvent ()
327         tp = mxl_event.get_text()
328     # In all other cases, use the dicts to translate the xml tag name to a proper lilypond command
329     elif short_articulations_dict.get (mxl_event.get_name ()):
330         ev = musicexp.ShortArticulationEvent ()
331         tp = short_articulations_dict.get (mxl_event.get_name ())
332     else:
333         ev = musicexp.ArticulationEvent ()
334         tp = articulations_dict.get (mxl_event.get_name ())
335
336     if not tp:
337         return
338     
339     ev.type = tp
340
341     # Some articulations use the type attribute, other the placement...
342     dir = None
343     if hasattr (mxl_event, 'type'):
344         dir = musicxml_direction_to_indicator (mxl_event.type)
345     if hasattr (mxl_event, 'placement'):
346         dir = musicxml_direction_to_indicator (mxl_event.placement)
347     # \breathe cannot have any direction modifier (^, _, -)!
348     if dir and tp != "breathe":
349         ev.force_direction = dir
350     return ev
351
352
353 def musicxml_dynamics_to_lily_event (dynentry):
354     dynamics_available = ( "p", "pp", "ppp", "pppp", "ppppp", "pppppp",
355         "f", "ff", "fff", "ffff", "fffff", "ffffff",
356         "mp", "mf", "sf", "sfp", "sfpp", "fp",
357         "rf", "rfz", "sfz", "sffz", "fz" )
358     if not dynentry.get_name() in dynamics_available:
359         return
360     event = musicexp.DynamicsEvent ()
361     event.type = dynentry.get_name ()
362     return event
363
364
365 direction_spanners = [ 'octave-shift', 'pedal', 'wedge' ]
366
367 def musicxml_direction_to_lily (n):
368     # TODO: Handle the <staff> element!
369     res = []
370     dirtype_children = []
371     for dt in n.get_typed_children (musicxml.DirType):
372         dirtype_children += dt.get_all_children ()
373
374     for entry in dirtype_children:
375         if entry.get_name () == "dynamics":
376             for dynentry in entry.get_all_children ():
377                 ev = musicxml_dynamics_to_lily_event (dynentry)
378                 if ev:
379                     res.append (ev)
380
381         # octave shifts. pedal marks, hairpins etc. are spanners:
382         if entry.get_name() in direction_spanners:
383             event = musicxml_spanner_to_lily_event (entry)
384             if event:
385                 res.append (event)
386
387
388     return res
389
390 instrument_drumtype_dict = {
391     'Acoustic Snare Drum': 'acousticsnare',
392     'Side Stick': 'sidestick',
393     'Open Triangle': 'opentriangle',
394     'Mute Triangle': 'mutetriangle',
395     'Tambourine': 'tambourine'
396 }
397
398 def musicxml_note_to_lily_main_event (n):
399     pitch  = None
400     duration = None
401         
402     mxl_pitch = n.get_maybe_exist_typed_child (musicxml.Pitch)
403     event = None
404     if mxl_pitch:
405         pitch = musicxml_pitch_to_lily (mxl_pitch)
406         event = musicexp.NoteEvent()
407         event.pitch = pitch
408
409         acc = n.get_maybe_exist_named_child ('accidental')
410         if acc:
411             # let's not force accs everywhere. 
412             event.cautionary = acc.editorial
413         
414     elif n.get_maybe_exist_typed_child (musicxml.Rest):
415         # rests can have display-octave and display-step, which are
416         # treated like an ordinary note pitch
417         rest = n.get_maybe_exist_typed_child (musicxml.Rest)
418         event = musicexp.RestEvent()
419         pitch = musicxml_restdisplay_to_lily (rest)
420         event.pitch = pitch
421     elif n.instrument_name:
422         event = musicexp.NoteEvent ()
423         drum_type = instrument_drumtype_dict.get (n.instrument_name)
424         if drum_type:
425             event.drum_type = drum_type
426         else:
427             n.message ("drum %s type unknow, please add to instrument_drumtype_dict" % n.instrument_name)
428             event.drum_type = 'acousticsnare'
429     
430     if not event:
431         n.message ("cannot find suitable event")
432
433     event.duration = musicxml_duration_to_lily (n)
434     return event
435
436
437 ## TODO
438 class NegativeSkip:
439     def __init__ (self, here, dest):
440         self.here = here
441         self.dest = dest
442
443 class LilyPondVoiceBuilder:
444     def __init__ (self):
445         self.elements = []
446         self.pending_dynamics = []
447         self.end_moment = Rational (0)
448         self.begin_moment = Rational (0)
449         self.pending_multibar = Rational (0)
450
451     def _insert_multibar (self):
452         r = musicexp.MultiMeasureRest ()
453         r.duration = musicexp.Duration()
454         r.duration.duration_log = 0
455         r.duration.factor = self.pending_multibar
456         self.elements.append (r)
457         self.begin_moment = self.end_moment
458         self.end_moment = self.begin_moment + self.pending_multibar
459         self.pending_multibar = Rational (0)
460         
461     def add_multibar_rest (self, duration):
462         self.pending_multibar += duration
463
464     def set_duration (self, duration):
465         self.end_moment = self.begin_moment + duration
466     def current_duration (self):
467         return self.end_moment - self.begin_moment
468         
469     def add_music (self, music, duration):
470         assert isinstance (music, musicexp.Music)
471         if self.pending_multibar > Rational (0):
472             self._insert_multibar ()
473
474         self.elements.append (music)
475         self.begin_moment = self.end_moment
476         self.set_duration (duration)
477         
478         # Insert all pending dynamics right after the note/rest:
479         if duration > Rational (0):
480             for d in self.pending_dynamics:
481                 self.elements.append (d)
482             self.pending_dynamics = []
483
484     # Insert some music command that does not affect the position in the measure
485     def add_command (self, command):
486         assert isinstance (command, musicexp.Music)
487         if self.pending_multibar > Rational (0):
488             self._insert_multibar ()
489         self.elements.append (command)
490
491     def add_dynamics (self, dynamic):
492         # store the dynamic item(s) until we encounter the next note/rest:
493         self.pending_dynamics.append (dynamic)
494
495     def add_bar_check (self, number):
496         b = musicexp.BarCheck ()
497         b.bar_number = number
498         self.add_music (b, Rational (0))
499
500     def jumpto (self, moment):
501         current_end = self.end_moment + self.pending_multibar
502         diff = moment - current_end
503         
504         if diff < Rational (0):
505             error_message ('Negative skip %s' % diff)
506             diff = Rational (0)
507
508         if diff > Rational (0):
509             skip = musicexp.SkipEvent()
510             skip.duration.duration_log = 0
511             skip.duration.factor = diff
512
513             evc = musicexp.EventChord ()
514             evc.elements.append (skip)
515             self.add_music (evc, diff)
516                 
517     def last_event_chord (self, starting_at):
518
519         value = None
520
521         # if the position matches, find the last EventChord, do not cross a bar line!
522         at = len( self.elements ) - 1
523         while (at >= 0 and
524                not isinstance (self.elements[at], musicexp.EventChord) and
525                not isinstance (self.elements[at], musicexp.BarCheck)):
526             at -= 1
527
528         if (self.elements
529             and at >= 0
530             and isinstance (self.elements[at], musicexp.EventChord)
531             and self.begin_moment == starting_at):
532             value = self.elements[at]
533         else:
534             self.jumpto (starting_at)
535             value = None
536         return value
537         
538     def correct_negative_skip (self, goto):
539         self.end_moment = goto
540         self.begin_moment = goto
541         evc = musicexp.EventChord ()
542         self.elements.append (evc)
543         
544 def musicxml_voice_to_lily_voice (voice):
545     tuplet_events = []
546     modes_found = {}
547     lyrics = {}
548         
549     for k in voice.get_lyrics_numbers ():
550         lyrics[k] = []
551
552     voice_builder = LilyPondVoiceBuilder()
553
554     for n in voice._elements:
555         if n.get_name () == 'forward':
556             continue
557
558         if isinstance (n, musicxml.Direction):
559             for a in musicxml_direction_to_lily (n):
560                 if a.wait_for_note ():
561                     voice_builder.add_dynamics (a)
562                 else:
563                     voice_builder.add_command (a)
564             continue
565         
566         if not n.get_maybe_exist_named_child ('chord'):
567             try:
568                 voice_builder.jumpto (n._when)
569             except NegativeSkip, neg:
570                 voice_builder.correct_negative_skip (n._when)
571                 n.message ("Negative skip? from %s to %s, diff %s" % (neg.here, neg.dest, neg.dest - neg.here))
572             
573         if isinstance (n, musicxml.Attributes):
574             if n.is_first () and n._measure_position == Rational (0):
575                 try:
576                     number = int (n.get_parent ().number)
577                 except ValueError:
578                     number = 0
579                 
580                 voice_builder.add_bar_check (number)
581             for a in musicxml_attributes_to_lily (n):
582                 voice_builder.add_music (a, Rational (0))
583             continue
584
585         if not n.__class__.__name__ == 'Note':
586             error_message ('not a Note or Attributes? %s' % n)
587             continue
588
589         rest = n.get_maybe_exist_typed_child (musicxml.Rest)
590         if (rest
591             and rest.is_whole_measure ()):
592
593             voice_builder.add_multibar_rest (n._duration)
594             continue
595
596         if n.is_first () and n._measure_position == Rational (0):
597             try: 
598                 num = int (n.get_parent ().number)
599             except ValueError:
600                 num = 0
601             voice_builder.add_bar_check (num)
602         
603         main_event = musicxml_note_to_lily_main_event (n)
604
605         if hasattr (main_event, 'drum_type') and main_event.drum_type:
606             modes_found['drummode'] = True
607
608
609         ev_chord = voice_builder.last_event_chord (n._when)
610         if not ev_chord: 
611             ev_chord = musicexp.EventChord()
612             voice_builder.add_music (ev_chord, n._duration)
613         # When a note/chord has grace notes (duration==0), the duration of the 
614         # event chord is not yet known, but the event chord was already added
615         # with duration 0. The following correct this when we hit the real note!
616         if voice_builder.current_duration () == 0 and n._duration > 0:
617             voice_builder.set_duration (n._duration)
618         if n.get_maybe_exist_typed_child (musicxml.Grace):
619             ev_chord.append_grace (main_event)
620         else:
621             ev_chord.append (main_event)
622         
623         notations = n.get_maybe_exist_typed_child (musicxml.Notations)
624         tuplet_event = None
625         span_events = []
626         
627         # The <notation> element can have the following children (+ means implemented, ~ partially, - not):
628         # +tied | +slur | +tuplet | glissando | slide | 
629         #    ornaments | technical | articulations | dynamics |
630         #    +fermata | arpeggiate | non-arpeggiate | 
631         #    accidental-mark | other-notation
632         if notations:
633             if notations.get_tuplet():
634                 tuplet_event = notations.get_tuplet()
635                 mod = n.get_maybe_exist_typed_child (musicxml.Time_modification)
636                 frac = (1,1)
637                 if mod:
638                     frac = mod.get_fraction ()
639                 
640                 tuplet_events.append ((ev_chord, tuplet_event, frac))
641
642             slurs = [s for s in notations.get_named_children ('slur')
643                 if s.get_type () in ('start','stop')]
644             if slurs:
645                 if len (slurs) > 1:
646                     error_message ('more than 1 slur?')
647
648                 lily_ev = musicxml_spanner_to_lily_event (slurs[0])
649                 ev_chord.append (lily_ev)
650
651             mxl_tie = notations.get_tie ()
652             if mxl_tie and mxl_tie.type == 'start':
653                 ev_chord.append (musicexp.TieEvent ())
654                 
655             fermatas = notations.get_named_children ('fermata')
656             for a in fermatas:
657                 ev = musicxml_fermata_to_lily_event (a)
658                 if ev: 
659                     ev_chord.append (ev)
660
661             arpeggiate = notations.get_named_children ('arpeggiate')
662             for a in arpeggiate:
663                 ev_chord.append (musicexp.ArpeggioEvent ())
664
665             glissandos = notations.get_named_children ('glissando')
666             for a in glissandos:
667                 ev = musicxml_spanner_to_lily_event (a)
668                 if ev:
669                     ev_chord.append (ev)
670                 
671             # Articulations can contain the following child elements:
672             #         accent | strong-accent | staccato | tenuto |
673             #         detached-legato | staccatissimo | spiccato |
674             #         scoop | plop | doit | falloff | breath-mark | 
675             #         caesura | stress | unstress
676             # Technical can contain the following child elements:
677             #         up-bow | down-bow | harmonic | open-string |
678             #         thumb-position | fingering | pluck | double-tongue |
679             #         triple-tongue | stopped | snap-pizzicato | fret |
680             #         string | hammer-on | pull-off | bend | tap | heel |
681             #         toe | fingernails | other-technical
682             # Ornaments can contain the following child elements:
683             #         trill-mark | turn | delayed-turn | inverted-turn |
684             #         shake | wavy-line | mordent | inverted-mordent | 
685             #         schleifer | tremolo | other-ornament, accidental-mark
686             ornaments = notations.get_named_children ('ornaments')
687             for a in ornaments:
688                 for ch in a.get_named_children ('tremolo'):
689                     ev = musicxml_tremolo_to_lily_event (ch)
690                     if ev: 
691                         ev_chord.append (ev)
692
693             ornaments += notations.get_named_children ('articulations')
694             ornaments += notations.get_named_children ('technical')
695
696             for a in ornaments:
697                 for ch in a.get_all_children ():
698                     ev = musicxml_articulation_to_lily_event (ch)
699                     if ev: 
700                         ev_chord.append (ev)
701
702             dynamics = notations.get_named_children ('dynamics')
703             for a in dynamics:
704                 for ch in a.get_all_children ():
705                     ev = musicxml_dynamics_to_lily_event (ch)
706                     if ev:
707                         ev_chord.append (ev)
708
709         # Extract the lyrics
710         note_lyrics_processed = []
711         note_lyrics_elements = n.get_typed_children (musicxml.Lyric)
712         for l in note_lyrics_elements:
713             if l.get_number () < 0:
714                 for k in lyrics.keys ():
715                     lyrics[k].append (l.lyric_to_text ())
716                     note_lyrics_processed.append (k)
717             else:
718                 lyrics[l.number].append(l.lyric_to_text ())
719                 note_lyrics_processed.append (l.number)
720         for lnr in lyrics.keys ():
721             if not lnr in note_lyrics_processed:
722                 lyrics[lnr].append ("\skip4")
723
724
725         mxl_beams = [b for b in n.get_named_children ('beam')
726                      if (b.get_type () in ('begin', 'end')
727                          and b.is_primary ())] 
728         if mxl_beams:
729             beam_ev = musicxml_spanner_to_lily_event (mxl_beams[0])
730             if beam_ev:
731                 ev_chord.append (beam_ev)
732             
733         if tuplet_event:
734             mod = n.get_maybe_exist_typed_child (musicxml.Time_modification)
735             frac = (1,1)
736             if mod:
737                 frac = mod.get_fraction ()
738                 
739             tuplet_events.append ((ev_chord, tuplet_event, frac))
740
741     ## force trailing mm rests to be written out.   
742     voice_builder.add_music (musicexp.EventChord (), Rational (0))
743     
744     ly_voice = group_tuplets (voice_builder.elements, tuplet_events)
745
746     seq_music = musicexp.SequentialMusic ()
747
748     if 'drummode' in modes_found.keys ():
749         ## \key <pitch> barfs in drummode.
750         ly_voice = [e for e in ly_voice
751                     if not isinstance(e, musicexp.KeySignatureChange)]
752     
753     seq_music.elements = ly_voice
754     lyrics_dict = {}
755     for k in lyrics.keys ():
756         lyrics_dict[k] = musicexp.Lyrics ()
757         lyrics_dict[k].lyrics_syllables = lyrics[k]
758     
759     
760     if len (modes_found) > 1:
761        error_message ('Too many modes found %s' % modes_found.keys ())
762
763     return_value = seq_music
764     for mode in modes_found.keys ():
765         v = musicexp.ModeChangingMusicWrapper()
766         v.element = return_value
767         v.mode = mode
768         return_value = v
769     
770     return (return_value, lyrics_dict)
771
772
773 def musicxml_id_to_lily (id):
774     digits = ['Zero', 'One', 'Two', 'Three', 'Four', 'Five',
775               'Six', 'Seven', 'Eight', 'Nine', 'Ten']
776     
777     for digit in digits:
778         d = digits.index (digit)
779         id = re.sub ('%d' % d, digit, id)
780
781     id = re.sub  ('[^a-zA-Z]', 'X', id)
782     return id
783
784
785 def musicxml_pitch_to_lily (mxl_pitch):
786     p = musicexp.Pitch()
787     p.alteration = mxl_pitch.get_alteration ()
788     p.step = (ord (mxl_pitch.get_step ()) - ord ('A') + 7 - 2) % 7
789     p.octave = mxl_pitch.get_octave () - 4
790     return p
791
792 def musicxml_restdisplay_to_lily (mxl_rest):
793     p = None
794     step = mxl_rest.get_step ()
795     if step:
796         p = musicexp.Pitch()
797         p.step = (ord (step) - ord ('A') + 7 - 2) % 7
798     octave = mxl_rest.get_octave ()
799     if octave and p:
800         p.octave = octave - 4
801     return p
802
803 def voices_in_part (part):
804     """Return a Name -> Voice dictionary for PART"""
805     part.interpret ()
806     part.extract_voices ()
807     voice_dict = part.get_voices ()
808
809     return voice_dict
810
811 def voices_in_part_in_parts (parts):
812     """return a Part -> Name -> Voice dictionary"""
813     return dict([(p, voices_in_part (p)) for p in parts])
814
815
816 def get_all_voices (parts):
817     all_voices = voices_in_part_in_parts (parts)
818
819     all_ly_voices = {}
820     for p, name_voice in all_voices.items ():
821
822         part_ly_voices = {}
823         for n, v in name_voice.items ():
824             progress ("Converting to LilyPond expressions...")
825             # musicxml_voice_to_lily_voice returns (lily_voice, {nr->lyrics, nr->lyrics})
826             part_ly_voices[n] = (musicxml_voice_to_lily_voice (v), v)
827
828         all_ly_voices[p] = part_ly_voices
829         
830     return all_ly_voices
831
832
833 def option_parser ():
834     p = ly.get_option_parser(usage=_ ("musicxml2ly FILE.xml"),
835                              version=('''%prog (LilyPond) @TOPLEVEL_VERSION@\n\n'''
836                                       +
837 _ ("""This program is free software.  It is covered by the GNU General Public
838 License and you are welcome to change it and/or distribute copies of it
839 under certain conditions.  Invoke as `%s --warranty' for more
840 information.""") % 'lilypond'
841 + """
842 Copyright (c) 2005--2007 by
843     Han-Wen Nienhuys <hanwen@xs4all.nl> and
844     Jan Nieuwenhuizen <janneke@gnu.org>
845 """),
846                              description=_ ("Convert %s to LilyPond input.") % 'MusicXML' + "\n")
847     p.add_option ('-v', '--verbose',
848                   action="store_true",
849                   dest='verbose',
850                   help=_ ("be verbose"))
851
852     p.add_option ('', '--lxml',
853                   action="store_true",
854                   default=False,
855                   dest="use_lxml",
856                   help=_ ("Use lxml.etree; uses less memory and cpu time."))
857     
858     p.add_option ('-o', '--output',
859                   metavar=_ ("FILE"),
860                   action="store",
861                   default=None,
862                   type='string',
863                   dest='output_name',
864                   help=_ ("set output filename to FILE"))
865     p.add_option_group ('bugs',
866                         description=(_ ("Report bugs via")
867                                      + ''' http://post.gmane.org/post.php'''
868                                      '''?group=gmane.comp.gnu.lilypond.bugs\n'''))
869     return p
870
871 def music_xml_voice_name_to_lily_name (part, name):
872     str = "Part%sVoice%s" % (part.id, name)
873     return musicxml_id_to_lily (str) 
874
875 def music_xml_lyrics_name_to_lily_name (part, name, lyricsnr):
876     str = "Part%sVoice%sLyrics%s" % (part.id, name, lyricsnr)
877     return musicxml_id_to_lily (str) 
878
879 def print_voice_definitions (printer, part_list, voices):
880     part_dict={}
881     for (part, nv_dict) in voices.items():
882         part_dict[part.id] = (part, nv_dict)
883
884     for part in part_list:
885         (part, nv_dict) = part_dict.get (part.id, (None, {}))
886         for (name, ((voice, lyrics), mxlvoice)) in nv_dict.items ():
887             k = music_xml_voice_name_to_lily_name (part, name)
888             printer.dump ('%s = ' % k)
889             voice.print_ly (printer)
890             printer.newline()
891             
892             for l in lyrics.keys ():
893                 lname = music_xml_lyrics_name_to_lily_name (part, name, l)
894                 printer.dump ('%s = ' %lname )
895                 lyrics[l].print_ly (printer)
896                 printer.newline()
897
898             
899 def uniq_list (l):
900     return dict ([(elt,1) for elt in l]).keys ()
901     
902 def print_score_setup (printer, part_list, voices):
903     part_dict = dict ([(p.id, p) for p in voices.keys ()]) 
904
905     printer ('<<')
906     printer.newline ()
907     for part_definition in part_list:
908         part_name = part_definition.id
909         part = part_dict.get (part_name)
910         if not part:
911             error_message ('unknown part in part-list: %s' % part_name)
912             continue
913
914         nv_dict = voices.get (part)
915         staves = reduce (lambda x,y: x+ y,
916                 [mxlvoice._staves.keys ()
917                  for (v, mxlvoice) in nv_dict.values ()],
918                 [])
919
920         if len (staves) > 1:
921             staves = uniq_list (staves)
922             staves.sort ()
923             printer ('\\context PianoStaff << ')
924             printer.newline ()
925             
926             for s in staves:
927                 staff_voices = [(music_xml_voice_name_to_lily_name (part, voice_name), voice_name, v)
928                         for (voice_name, (v, mxlvoice)) in nv_dict.items ()
929                         if mxlvoice._start_staff == s]
930                 
931                 printer ('\\context Staff = "%s" << ' % s)
932                 printer.newline ()
933                 for (v, voice_name, (music, lyrics)) in staff_voices:
934                     printer ('\\context Voice = "%s"  \\%s' % (v,v))
935                     printer.newline ()
936                     
937                     # Assign the lyrics to that voice
938                     for l in lyrics.keys ():
939                         ll = music_xml_lyrics_name_to_lily_name (part, voice_name, l)
940                         printer ('\\new Lyrics \\lyricsto "%s" \\%s' % (v, ll))
941                         printer.newline()
942                         printer.newline()
943                     
944                 printer ('>>')
945                 printer.newline ()
946                 
947             printer ('>>')
948             printer.newline ()
949             
950         else:
951             printer ('\\new Staff <<')
952             printer.newline ()
953             for (n,v) in nv_dict.items ():
954                 ((music, lyrics), voice) = v
955                 nn = music_xml_voice_name_to_lily_name (part, n) 
956                 printer ('\\context Voice = "%s"  \\%s' % (nn,nn))
957
958                 # Assign the lyrics to that voice
959                 for l in lyrics.keys ():
960                     ll = music_xml_lyrics_name_to_lily_name (part, n, l)
961                     printer ('\\new Lyrics \\lyricsto "%s" \\%s' % (nn, ll))
962                     printer.newline()
963
964             printer ('>>')
965             printer.newline ()
966             
967     printer ('>>')
968     printer.newline ()
969
970 def print_ly_preamble (printer, filename):
971     printer.dump_version ()
972     printer.print_verbatim ('%% automatically converted from %s\n' % filename)
973
974 def read_musicxml (filename, use_lxml):
975     if use_lxml:
976         import lxml.etree
977         
978         tree = lxml.etree.parse (filename)
979         mxl_tree = musicxml.lxml_demarshal_node (tree.getroot ())
980         return mxl_tree
981     else:
982         from xml.dom import minidom, Node
983         
984         doc = minidom.parse(filename)
985         node = doc.documentElement
986         return musicxml.minidom_demarshal_node (node)
987
988     return None
989
990
991 def convert (filename, options):
992     progress ("Reading MusicXML from %s ..." % filename)
993     
994     tree = read_musicxml (filename, options.use_lxml)
995
996     part_list = []
997     id_instrument_map = {}
998     if tree.get_maybe_exist_typed_child (musicxml.Part_list):
999         mxl_pl = tree.get_maybe_exist_typed_child (musicxml.Part_list)
1000         part_list = mxl_pl.get_named_children ("score-part")
1001         
1002     # score information is contained in the <work>, <identification> or <movement-title> tags
1003     score_information = extract_score_information (tree)
1004     parts = tree.get_typed_children (musicxml.Part)
1005     voices = get_all_voices (parts)
1006
1007     if not options.output_name:
1008         options.output_name = os.path.basename (filename) 
1009         options.output_name = os.path.splitext (options.output_name)[0]
1010     elif re.match (".*\.ly", options.output_name):
1011         options.output_name = os.path.splitext (options.output_name)[0]
1012
1013
1014     defs_ly_name = options.output_name + '-defs.ly'
1015     driver_ly_name = options.output_name + '.ly'
1016
1017     printer = musicexp.Output_printer()
1018     progress ("Output to `%s'" % defs_ly_name)
1019     printer.set_file (open (defs_ly_name, 'w'))
1020
1021     print_ly_preamble (printer, filename)
1022     score_information.print_ly (printer)
1023     print_voice_definitions (printer, part_list, voices)
1024     
1025     printer.close ()
1026     
1027     
1028     progress ("Output to `%s'" % driver_ly_name)
1029     printer = musicexp.Output_printer()
1030     printer.set_file (open (driver_ly_name, 'w'))
1031     print_ly_preamble (printer, filename)
1032     printer.dump (r'\include "%s"' % os.path.basename (defs_ly_name))
1033     print_score_setup (printer, part_list, voices)
1034     printer.newline ()
1035
1036     return voices
1037
1038 def get_existing_filename_with_extension (filename, ext):
1039     if os.path.exists (filename):
1040         return filename
1041     newfilename = filename + ".xml"
1042     if os.path.exists (newfilename):
1043         return newfilename;
1044     newfilename = filename + "xml"
1045     if os.path.exists (newfilename):
1046         return newfilename;
1047     return ''
1048
1049 def main ():
1050     opt_parser = option_parser()
1051
1052     (options, args) = opt_parser.parse_args ()
1053     if not args:
1054         opt_parser.print_usage()
1055         sys.exit (2)
1056     
1057     # Allow the user to leave out the .xml or xml on the filename
1058     filename = get_existing_filename_with_extension (args[0], "xml")
1059     if filename and os.path.exists (filename):
1060         voices = convert (filename, options)
1061     else:
1062         progress ("Unable to find input file %s" % args[0])
1063
1064 if __name__ == '__main__':
1065     main()