]> git.donarmstrong.com Git - lilypond.git/blob - scripts/musicxml2ly.py
Merge branch 'master' into dev/texi2html
[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 import codecs
9 import zipfile
10 import StringIO
11
12 """
13 @relocate-preamble@
14 """
15
16 import lilylib as ly
17 _ = ly._
18
19 import musicxml
20 import musicexp
21
22 from rational import Rational
23
24 # Store command-line options in a global variable, so we can access them everythwere
25 options = None
26
27 class Conversion_Settings:
28     def __init__(self):
29        self.ignore_beaming = False
30
31 conversion_settings = Conversion_Settings ()
32 # Use a global variable to store the setting needed inside a \layout block.
33 # whenever we need to change a setting or add/remove an engraver, we can access 
34 # this layout and add the corresponding settings
35 layout_information = musicexp.Layout ()
36
37 def progress (str):
38     ly.stderr_write (str + '\n')
39     sys.stderr.flush ()
40
41 def error_message (str):
42     ly.stderr_write (str + '\n')
43     sys.stderr.flush ()
44
45 needed_additional_definitions = []
46 additional_definitions = {
47   "snappizzicato": """#(define-markup-command (snappizzicato layout props) ()
48   (interpret-markup layout props
49     (markup #:stencil
50       (ly:stencil-translate-axis
51         (ly:stencil-add
52           (make-circle-stencil 0.7 0.1 #f)
53           (ly:make-stencil
54             (list 'draw-line 0.1 0 0.1 0 1)
55             '(-0.1 . 0.1) '(0.1 . 1)))
56         0.7 X))))""",
57   "eyeglasses": """eyeglassesps = #"0.15 setlinewidth
58       -0.9 0 translate
59       1.1 1.1 scale
60       1.2 0.7 moveto
61       0.7 0.7 0.5 0 361 arc
62       stroke
63       2.20 0.70 0.50 0 361 arc
64       stroke
65       1.45 0.85 0.30 0 180 arc
66       stroke
67       0.20 0.70 moveto
68       0.80 2.00 lineto
69       0.92 2.26 1.30 2.40 1.15 1.70 curveto
70       stroke
71       2.70 0.70 moveto
72       3.30 2.00 lineto
73       3.42 2.26 3.80 2.40 3.65 1.70 curveto
74       stroke"
75 eyeglasses =  \markup { \with-dimensions #'(0 . 4.4) #'(0 . 2.5) \postscript #eyeglassesps }"""
76 }
77
78 def round_to_two_digits (val):
79     return round (val * 100) / 100
80
81 def extract_paper_information (tree):
82     paper = musicexp.Paper ()
83     defaults = tree.get_maybe_exist_named_child ('defaults')
84     if not defaults:
85         return None
86     tenths = -1
87     scaling = defaults.get_maybe_exist_named_child ('scaling')
88     if scaling:
89         mm = scaling.get_named_child ('millimeters')
90         mm = string.atof (mm.get_text ())
91         tn = scaling.get_maybe_exist_named_child ('tenths')
92         tn = string.atof (tn.get_text ())
93         tenths = mm / tn
94         paper.global_staff_size = mm * 72.27 / 25.4
95     # We need the scaling (i.e. the size of staff tenths for everything!
96     if tenths < 0:
97         return None
98
99     def from_tenths (txt):
100         return round_to_two_digits (string.atof (txt) * tenths / 10)
101     def set_paper_variable (varname, parent, element_name):
102         el = parent.get_maybe_exist_named_child (element_name)
103         if el: # Convert to cm from tenths
104             setattr (paper, varname, from_tenths (el.get_text ()))
105
106     pagelayout = defaults.get_maybe_exist_named_child ('page-layout')
107     if pagelayout:
108         # TODO: How can one have different margins for even and odd pages???
109         set_paper_variable ("page_height", pagelayout, 'page-height')
110         set_paper_variable ("page_width", pagelayout, 'page-width')
111
112         pmargins = pagelayout.get_named_children ('page-margins')
113         for pm in pmargins:
114             set_paper_variable ("left_margin", pm, 'left-margin')
115             set_paper_variable ("right_margin", pm, 'right-margin')
116             set_paper_variable ("bottom_margin", pm, 'bottom-margin')
117             set_paper_variable ("top_margin", pm, 'top-margin')
118
119     systemlayout = defaults.get_maybe_exist_named_child ('system-layout')
120     if systemlayout:
121         sl = systemlayout.get_maybe_exist_named_child ('system-margins')
122         if sl:
123             set_paper_variable ("system_left_margin", sl, 'left-margin')
124             set_paper_variable ("system_right_margin", sl, 'right-margin')
125         set_paper_variable ("system_distance", systemlayout, 'system-distance')
126         set_paper_variable ("top_system_distance", systemlayout, 'top-system-distance')
127
128     stafflayout = defaults.get_named_children ('staff-layout')
129     for sl in stafflayout:
130         nr = getattr (sl, 'number', 1)
131         dist = sl.get_named_child ('staff-distance')
132         #TODO: the staff distance needs to be set in the Staff context!!!
133
134     # TODO: Finish appearance?, music-font?, word-font?, lyric-font*, lyric-language*
135     appearance = defaults.get_named_child ('appearance')
136     if appearance:
137         lws = appearance.get_named_children ('line-width')
138         for lw in lws:
139             # Possible types are: beam, bracket, dashes,
140             #    enclosure, ending, extend, heavy barline, leger,
141             #    light barline, octave shift, pedal, slur middle, slur tip,
142             #    staff, stem, tie middle, tie tip, tuplet bracket, and wedge
143             tp = lw.type
144             w = from_tenths (lw.get_text  ())
145             # TODO: Do something with these values!
146         nss = appearance.get_named_children ('note-size')
147         for ns in nss:
148             # Possible types are: cue, grace and large
149             tp = ns.type
150             sz = from_tenths (ns.get_text ())
151             # TODO: Do something with these values!
152         # <other-appearance> elements have no specified meaning
153
154     rawmusicfont = defaults.get_named_child ('music-font')
155     if rawmusicfont:
156         # TODO: Convert the font
157         pass
158     rawwordfont = defaults.get_named_child ('word-font')
159     if rawwordfont:
160         # TODO: Convert the font
161         pass
162     rawlyricsfonts = defaults.get_named_children ('lyric-font')
163     for lyricsfont in rawlyricsfonts:
164         # TODO: Convert the font
165         pass
166
167     return paper
168
169
170
171 # score information is contained in the <work>, <identification> or <movement-title> tags
172 # extract those into a hash, indexed by proper lilypond header attributes
173 def extract_score_information (tree):
174     header = musicexp.Header ()
175     def set_if_exists (field, value):
176         if value:
177             header.set_field (field, musicxml.escape_ly_output_string (value))
178
179     work = tree.get_maybe_exist_named_child ('work')
180     if work:
181         set_if_exists ('title', work.get_work_title ())
182         set_if_exists ('worknumber', work.get_work_number ())
183         set_if_exists ('opus', work.get_opus ())
184     else:
185         movement_title = tree.get_maybe_exist_named_child ('movement-title')
186         if movement_title:
187             set_if_exists ('title', movement_title.get_text ())
188     
189     identifications = tree.get_named_children ('identification')
190     for ids in identifications:
191         set_if_exists ('copyright', ids.get_rights ())
192         set_if_exists ('composer', ids.get_composer ())
193         set_if_exists ('arranger', ids.get_arranger ())
194         set_if_exists ('editor', ids.get_editor ())
195         set_if_exists ('poet', ids.get_poet ())
196             
197         set_if_exists ('tagline', ids.get_encoding_software ())
198         set_if_exists ('encodingsoftware', ids.get_encoding_software ())
199         set_if_exists ('encodingdate', ids.get_encoding_date ())
200         set_if_exists ('encoder', ids.get_encoding_person ())
201         set_if_exists ('encodingdescription', ids.get_encoding_description ())
202
203         # Finally, apply the required compatibility modes
204         # Some applications created wrong MusicXML files, so we need to 
205         # apply some compatibility mode, e.g. ignoring some features/tags
206         # in those files
207         software = ids.get_encoding_software_list ()
208
209         # Case 1: "Sibelius 5.1" with the "Dolet 3.4 for Sibelius" plugin
210         #         is missing all beam ends => ignore all beaming information
211         if "Dolet 3.4 for Sibelius" in software:
212             conversion_settings.ignore_beaming = True
213             progress (_ ("Encountered file created by Dolet 3.4 for Sibelius, containing wrong beaming information. All beaming information in the MusicXML file will be ignored"))
214         # TODO: Check for other unsupported features
215
216     return header
217
218 class PartGroupInfo:
219     def __init__ (self):
220         self.start = {}
221         self.end = {}
222     def is_empty (self):
223         return len (self.start) + len (self.end) == 0
224     def add_start (self, g):
225         self.start[getattr (g, 'number', "1")] = g
226     def add_end (self, g):
227         self.end[getattr (g, 'number', "1")] = g
228     def print_ly (self, printer):
229         error_message (_ ("Unprocessed PartGroupInfo %s encountered") % self)
230     def ly_expression (self):
231         error_message (_ ("Unprocessed PartGroupInfo %s encountered") % self)
232         return ''
233
234 def staff_attributes_to_string_tunings (mxl_attr):
235     details = mxl_attr.get_maybe_exist_named_child ('staff-details')
236     if not details:
237         return []
238     lines = 6
239     staff_lines = details.get_maybe_exist_named_child ('staff-lines')
240     if staff_lines:
241         lines = string.atoi (staff_lines.get_text ())
242
243     tunings = [0]*lines
244     staff_tunings = details.get_named_children ('staff-tuning')
245     for i in staff_tunings:
246         p = musicexp.Pitch()
247         line = 0
248         try:
249             line = string.atoi (i.line) - 1
250         except ValueError:
251             pass
252         tunings[line] = p
253
254         step = i.get_named_child (u'tuning-step')
255         step = step.get_text ().strip ()
256         p.step = musicxml_step_to_lily (step)
257
258         octave = i.get_named_child (u'tuning-octave')
259         octave = octave.get_text ().strip ()
260         p.octave = int (octave) - 4
261
262         alter = i.get_named_child (u'tuning-alter')
263         if alter:
264             p.alteration = int (alter.get_text ().strip ())
265     # lilypond seems to use the opposite ordering than MusicXML...
266     tunings.reverse ()
267
268     return tunings
269
270
271 def staff_attributes_to_lily_staff (mxl_attr):
272     if not mxl_attr:
273         return musicexp.Staff ()
274
275     (staff_id, attributes) = mxl_attr.items ()[0]
276
277     # distinguish by clef:
278     # percussion (percussion and rhythmic), tab, and everything else
279     clef_sign = None
280     clef = attributes.get_maybe_exist_named_child ('clef')
281     if clef:
282         sign = clef.get_maybe_exist_named_child ('sign')
283         if sign:
284             clef_sign = {"percussion": "percussion", "TAB": "tab"}.get (sign.get_text (), None)
285
286     lines = 5
287     details = attributes.get_named_children ('staff-details')
288     for d in details:
289         staff_lines = d.get_maybe_exist_named_child ('staff-lines')
290         if staff_lines:
291             lines = string.atoi (staff_lines.get_text ())
292
293     staff = None
294     if clef_sign == "percussion" and lines == 1:
295         staff = musicexp.RhythmicStaff ()
296     elif clef_sign == "percussion":
297         staff = musicexp.DrumStaff ()
298         # staff.drum_style_table = ???
299     elif clef_sign == "tab":
300         staff = musicexp.TabStaff ()
301         staff.string_tunings = staff_attributes_to_string_tunings (attributes)
302         # staff.tablature_format = ???
303     else:
304         # TODO: Handle case with lines <> 5!
305         staff = musicexp.Staff ()
306
307     return staff
308
309
310 def extract_score_structure (part_list, staffinfo):
311     structure = musicexp.StaffGroup (None)
312     if not part_list:
313         return structure
314
315     def read_score_part (el):
316         if not isinstance (el, musicxml.Score_part):
317             return
318         # Depending on the attributes of the first measure, we create different
319         # types of staves (Staff, RhythmicStaff, DrumStaff, TabStaff, etc.)
320         staff = staff_attributes_to_lily_staff (staffinfo.get (el.id, None))
321         if not staff:
322             return None
323         staff.id = el.id
324         partname = el.get_maybe_exist_named_child ('part-name')
325         # Finale gives unnamed parts the name "MusicXML Part" automatically!
326         if partname and partname.get_text() != "MusicXML Part":
327             staff.instrument_name = partname.get_text ()
328         if el.get_maybe_exist_named_child ('part-abbreviation'):
329             staff.short_instrument_name = el.get_maybe_exist_named_child ('part-abbreviation').get_text ()
330         # TODO: Read in the MIDI device / instrument
331         return staff
332
333     def read_score_group (el):
334         if not isinstance (el, musicxml.Part_group):
335             return
336         group = musicexp.StaffGroup ()
337         if hasattr (el, 'number'):
338             id = el.number
339             group.id = id
340             #currentgroups_dict[id] = group
341             #currentgroups.append (id)
342         if el.get_maybe_exist_named_child ('group-name'):
343             group.instrument_name = el.get_maybe_exist_named_child ('group-name').get_text ()
344         if el.get_maybe_exist_named_child ('group-abbreviation'):
345             group.short_instrument_name = el.get_maybe_exist_named_child ('group-abbreviation').get_text ()
346         if el.get_maybe_exist_named_child ('group-symbol'):
347             group.symbol = el.get_maybe_exist_named_child ('group-symbol').get_text ()
348         if el.get_maybe_exist_named_child ('group-barline'):
349             group.spanbar = el.get_maybe_exist_named_child ('group-barline').get_text ()
350         return group
351
352
353     parts_groups = part_list.get_all_children ()
354
355     # the start/end group tags are not necessarily ordered correctly and groups
356     # might even overlap, so we can't go through the children sequentially!
357
358     # 1) Replace all Score_part objects by their corresponding Staff objects,
359     #    also collect all group start/stop points into one PartGroupInfo object
360     staves = []
361     group_info = PartGroupInfo ()
362     for el in parts_groups:
363         if isinstance (el, musicxml.Score_part):
364             if not group_info.is_empty ():
365                 staves.append (group_info)
366                 group_info = PartGroupInfo ()
367             staff = read_score_part (el)
368             if staff:
369                 staves.append (staff)
370         elif isinstance (el, musicxml.Part_group):
371             if el.type == "start":
372                 group_info.add_start (el)
373             elif el.type == "stop":
374                 group_info.add_end (el)
375     if not group_info.is_empty ():
376         staves.append (group_info)
377
378     # 2) Now, detect the groups:
379     group_starts = []
380     pos = 0
381     while pos < len (staves):
382         el = staves[pos]
383         if isinstance (el, PartGroupInfo):
384             prev_start = 0
385             if len (group_starts) > 0:
386                 prev_start = group_starts[-1]
387             elif len (el.end) > 0: # no group to end here
388                 el.end = {}
389             if len (el.end) > 0: # closes an existing group
390                 ends = el.end.keys ()
391                 prev_started = staves[prev_start].start.keys ()
392                 grpid = None
393                 intersection = filter(lambda x:x in ends, prev_started)
394                 if len (intersection) > 0:
395                     grpid = intersection[0]
396                 else:
397                     # Close the last started group
398                     grpid = staves[prev_start].start.keys () [0]
399                     # Find the corresponding closing tag and remove it!
400                     j = pos + 1
401                     foundclosing = False
402                     while j < len (staves) and not foundclosing:
403                         if isinstance (staves[j], PartGroupInfo) and staves[j].end.has_key (grpid):
404                             foundclosing = True
405                             del staves[j].end[grpid]
406                             if staves[j].is_empty ():
407                                 del staves[j]
408                         j += 1
409                 grpobj = staves[prev_start].start[grpid]
410                 group = read_score_group (grpobj)
411                 # remove the id from both the start and end
412                 if el.end.has_key (grpid):
413                     del el.end[grpid]
414                 del staves[prev_start].start[grpid]
415                 if el.is_empty ():
416                     del staves[pos]
417                 # replace the staves with the whole group
418                 for j in staves[(prev_start + 1):pos]:
419                     if j.is_group:
420                         j.stafftype = "InnerStaffGroup"
421                     group.append_staff (j)
422                 del staves[(prev_start + 1):pos]
423                 staves.insert (prev_start + 1, group)
424                 # reset pos so that we continue at the correct position
425                 pos = prev_start
426                 # remove an empty start group
427                 if staves[prev_start].is_empty ():
428                     del staves[prev_start]
429                     group_starts.remove (prev_start)
430                     pos -= 1
431             elif len (el.start) > 0: # starts new part groups
432                 group_starts.append (pos)
433         pos += 1
434
435     if len (staves) == 1:
436         return staves[0]
437     for i in staves:
438         structure.append_staff (i)
439     return structure
440
441
442 def musicxml_duration_to_lily (mxl_note):
443     d = musicexp.Duration ()
444     # if the note has no Type child, then that method spits out a warning and 
445     # returns 0, i.e. a whole note
446     d.duration_log = mxl_note.get_duration_log ()
447
448     d.dots = len (mxl_note.get_typed_children (musicxml.Dot))
449     # Grace notes by specification have duration 0, so no time modification 
450     # factor is possible. It even messes up the output with *0/1
451     if not mxl_note.get_maybe_exist_typed_child (musicxml.Grace):
452         d.factor = mxl_note._duration / d.get_length ()
453
454     return d
455
456 def rational_to_lily_duration (rational_len):
457     d = musicexp.Duration ()
458
459     rational_len.normalize_self ()
460     d_log = {1: 0, 2: 1, 4:2, 8:3, 16:4, 32:5, 64:6, 128:7, 256:8, 512:9}.get (rational_len.denominator (), -1)
461
462     # Duration of the form 1/2^n or 3/2^n can be converted to a simple lilypond duration
463     if (d_log >= 0 and rational_len.numerator() in (1,3,5,7) ):
464         # account for the dots!
465         d.dots = (rational_len.numerator()-1)/2
466         d.duration_log = d_log - d.dots
467     elif (d_log >= 0):
468         d.duration_log = d_log
469         d.factor = Rational (rational_len.numerator ())
470     else:
471         error_message (_ ("Encountered rational duration with denominator %s, "
472                        "unable to convert to lilypond duration") %
473                        rational_len.denominator ())
474         # TODO: Test the above error message
475         return None
476
477     return d
478
479 def musicxml_partial_to_lily (partial_len):
480     if partial_len > 0:
481         p = musicexp.Partial ()
482         p.partial = rational_to_lily_duration (partial_len)
483         return p
484     else:
485         return Null
486
487 # Detect repeats and alternative endings in the chord event list (music_list)
488 # and convert them to the corresponding musicexp objects, containing nested
489 # music
490 def group_repeats (music_list):
491     repeat_replaced = True
492     music_start = 0
493     i = 0
494     # Walk through the list of expressions, looking for repeat structure
495     # (repeat start/end, corresponding endings). If we find one, try to find the
496     # last event of the repeat, replace the whole structure and start over again.
497     # For nested repeats, as soon as we encounter another starting repeat bar,
498     # treat that one first, and start over for the outer repeat.
499     while repeat_replaced and i < 100:
500         i += 1
501         repeat_start = -1  # position of repeat start / end
502         repeat_end = -1 # position of repeat start / end
503         repeat_times = 0
504         ending_start = -1 # position of current ending start
505         endings = [] # list of already finished endings
506         pos = 0
507         last = len (music_list) - 1
508         repeat_replaced = False
509         final_marker = 0
510         while pos < len (music_list) and not repeat_replaced:
511             e = music_list[pos]
512             repeat_finished = False
513             if isinstance (e, RepeatMarker):
514                 if not repeat_times and e.times:
515                     repeat_times = e.times
516                 if e.direction == -1:
517                     if repeat_end >= 0:
518                         repeat_finished = True
519                     else:
520                         repeat_start = pos
521                         repeat_end = -1
522                         ending_start = -1
523                         endings = []
524                 elif e.direction == 1:
525                     if repeat_start < 0:
526                         repeat_start = 0
527                     if repeat_end < 0:
528                         repeat_end = pos
529                     final_marker = pos
530             elif isinstance (e, EndingMarker):
531                 if e.direction == -1:
532                     if repeat_start < 0:
533                         repeat_start = 0
534                     if repeat_end < 0:
535                         repeat_end = pos
536                     ending_start = pos
537                 elif e.direction == 1:
538                     if ending_start < 0:
539                         ending_start = 0
540                     endings.append ([ending_start, pos])
541                     ending_start = -1
542                     final_marker = pos
543             elif not isinstance (e, musicexp.BarLine):
544                 # As soon as we encounter an element when repeat start and end
545                 # is set and we are not inside an alternative ending,
546                 # this whole repeat structure is finished => replace it
547                 if repeat_start >= 0 and repeat_end > 0 and ending_start < 0:
548                     repeat_finished = True
549
550             # Finish off all repeats without explicit ending bar (e.g. when
551             # we convert only one page of a multi-page score with repeats)
552             if pos == last and repeat_start >= 0:
553                 repeat_finished = True
554                 final_marker = pos
555                 if repeat_end < 0:
556                     repeat_end = pos
557                 if ending_start >= 0:
558                     endings.append ([ending_start, pos])
559                     ending_start = -1
560
561             if repeat_finished:
562                 # We found the whole structure replace it!
563                 r = musicexp.RepeatedMusic ()
564                 if repeat_times <= 0:
565                     repeat_times = 2
566                 r.repeat_count = repeat_times
567                 # don't erase the first element for "implicit" repeats (i.e. no
568                 # starting repeat bars at the very beginning)
569                 start = repeat_start+1
570                 if repeat_start == music_start:
571                     start = music_start
572                 r.set_music (music_list[start:repeat_end])
573                 for (start, end) in endings:
574                     s = musicexp.SequentialMusic ()
575                     s.elements = music_list[start+1:end]
576                     r.add_ending (s)
577                 del music_list[repeat_start:final_marker+1]
578                 music_list.insert (repeat_start, r)
579                 repeat_replaced = True
580             pos += 1
581         # TODO: Implement repeats until the end without explicit ending bar
582     return music_list
583
584
585
586 def group_tuplets (music_list, events):
587
588
589     """Collect Musics from
590     MUSIC_LIST demarcated by EVENTS_LIST in TimeScaledMusic objects.
591     """
592
593     
594     indices = []
595
596     j = 0
597     for (ev_chord, tuplet_elt, fraction) in events:
598         while (j < len (music_list)):
599             if music_list[j] == ev_chord:
600                 break
601             j += 1
602         if tuplet_elt.type == 'start':
603             indices.append ((j, None, fraction))
604         elif tuplet_elt.type == 'stop':
605             indices[-1] = (indices[-1][0], j, indices[-1][2])
606
607     new_list = []
608     last = 0
609     for (i1, i2, frac) in indices:
610         if i1 >= i2:
611             continue
612
613         new_list.extend (music_list[last:i1])
614         seq = musicexp.SequentialMusic ()
615         last = i2 + 1
616         seq.elements = music_list[i1:last]
617
618         tsm = musicexp.TimeScaledMusic ()
619         tsm.element = seq
620
621         tsm.numerator = frac[0]
622         tsm.denominator  = frac[1]
623
624         new_list.append (tsm)
625
626     new_list.extend (music_list[last:])
627     return new_list
628
629
630 def musicxml_clef_to_lily (attributes):
631     change = musicexp.ClefChange ()
632     (change.type, change.position, change.octave) = attributes.get_clef_information ()
633     return change
634     
635 def musicxml_time_to_lily (attributes):
636     (beats, type) = attributes.get_time_signature ()
637
638     change = musicexp.TimeSignatureChange()
639     change.fraction = (beats, type)
640     
641     return change
642
643 def musicxml_key_to_lily (attributes):
644     start_pitch  = musicexp.Pitch ()
645     (fifths, mode) = attributes.get_key_signature () 
646     try:
647         (n,a) = {
648             'major' : (0,0),
649             'minor' : (5,0),
650             }[mode]
651         start_pitch.step = n
652         start_pitch.alteration = a
653     except  KeyError:
654         error_message (_ ("unknown mode %s, expecting 'major' or 'minor'") % mode)
655
656     fifth = musicexp.Pitch()
657     fifth.step = 4
658     if fifths < 0:
659         fifths *= -1
660         fifth.step *= -1
661         fifth.normalize ()
662     
663     for x in range (fifths):
664         start_pitch = start_pitch.transposed (fifth)
665
666     start_pitch.octave = 0
667
668     change = musicexp.KeySignatureChange()
669     change.mode = mode
670     change.tonic = start_pitch
671     return change
672     
673 def musicxml_attributes_to_lily (attrs):
674     elts = []
675     attr_dispatch =  {
676         'clef': musicxml_clef_to_lily,
677         'time': musicxml_time_to_lily,
678         'key': musicxml_key_to_lily
679     }
680     for (k, func) in attr_dispatch.items ():
681         children = attrs.get_named_children (k)
682         if children:
683             elts.append (func (attrs))
684     
685     return elts
686
687 class Marker (musicexp.Music):
688     def __init__ (self):
689         self.direction = 0
690         self.event = None
691     def print_ly (self, printer):
692         ly.stderr_write (_ ("Encountered unprocessed marker %s\n") % self)
693         pass
694     def ly_expression (self):
695         return ""
696 class RepeatMarker (Marker):
697     def __init__ (self):
698         Marker.__init__ (self)
699         self.times = 0
700 class EndingMarker (Marker):
701     pass
702
703 # Convert the <barline> element to musicxml.BarLine (for non-standard barlines)
704 # and to RepeatMarker and EndingMarker objects for repeat and
705 # alternatives start/stops
706 def musicxml_barline_to_lily (barline):
707     # retval contains all possible markers in the order:
708     # 0..bw_ending, 1..bw_repeat, 2..barline, 3..fw_repeat, 4..fw_ending
709     retval = {}
710     bartype_element = barline.get_maybe_exist_named_child ("bar-style")
711     repeat_element = barline.get_maybe_exist_named_child ("repeat")
712     ending_element = barline.get_maybe_exist_named_child ("ending")
713
714     bartype = None
715     if bartype_element:
716         bartype = bartype_element.get_text ()
717
718     if repeat_element and hasattr (repeat_element, 'direction'):
719         repeat = RepeatMarker ()
720         repeat.direction = {"forward": -1, "backward": 1}.get (repeat_element.direction, 0)
721
722         if ( (repeat_element.direction == "forward" and bartype == "heavy-light") or
723              (repeat_element.direction == "backward" and bartype == "light-heavy") ):
724             bartype = None
725         if hasattr (repeat_element, 'times'):
726             try:
727                 repeat.times = int (repeat_element.times)
728             except ValueError:
729                 repeat.times = 2
730         repeat.event = barline
731         if repeat.direction == -1:
732             retval[3] = repeat
733         else:
734             retval[1] = repeat
735
736     if ending_element and hasattr (ending_element, 'type'):
737         ending = EndingMarker ()
738         ending.direction = {"start": -1, "stop": 1, "discontinue": 1}.get (ending_element.type, 0)
739         ending.event = barline
740         if ending.direction == -1:
741             retval[4] = ending
742         else:
743             retval[0] = ending
744
745     if bartype:
746         b = musicexp.BarLine ()
747         b.type = bartype
748         retval[2] = b
749
750     return retval.values ()
751
752 spanner_event_dict = {
753     'beam' : musicexp.BeamEvent,
754     'dashes' : musicexp.TextSpannerEvent,
755     'bracket' : musicexp.BracketSpannerEvent,
756     'glissando' : musicexp.GlissandoEvent,
757     'octave-shift' : musicexp.OctaveShiftEvent,
758     'pedal' : musicexp.PedalEvent,
759     'slide' : musicexp.GlissandoEvent,
760     'slur' : musicexp.SlurEvent,
761     'wavy-line' : musicexp.TrillSpanEvent,
762     'wedge' : musicexp.HairpinEvent
763 }
764 spanner_type_dict = {
765     'start': -1,
766     'begin': -1,
767     'crescendo': -1,
768     'decreschendo': -1,
769     'diminuendo': -1,
770     'continue': 0,
771     'change': 0,
772     'up': -1,
773     'down': -1,
774     'stop': 1,
775     'end' : 1
776 }
777
778 def musicxml_spanner_to_lily_event (mxl_event):
779     ev = None
780     
781     name = mxl_event.get_name()
782     func = spanner_event_dict.get (name)
783     if func:
784         ev = func()
785     else:
786         error_message (_ ('unknown span event %s') % mxl_event)
787
788
789     type = mxl_event.get_type ()
790     span_direction = spanner_type_dict.get (type)
791     # really check for None, because some types will be translated to 0, which
792     # would otherwise also lead to the unknown span warning
793     if span_direction != None:
794         ev.span_direction = span_direction
795     else:
796         error_message (_ ('unknown span type %s for %s') % (type, name))
797
798     ev.set_span_type (type)
799     ev.line_type = getattr (mxl_event, 'line-type', 'solid')
800
801     # assign the size, which is used for octave-shift, etc.
802     ev.size = mxl_event.get_size ()
803
804     return ev
805
806 def musicxml_direction_to_indicator (direction):
807     return { "above": 1, "upright": 1, "up": 1, "below": -1, "downright": -1, "down": -1, "inverted": -1 }.get (direction, 0)
808
809 def musicxml_fermata_to_lily_event (mxl_event):
810     ev = musicexp.ArticulationEvent ()
811     txt = mxl_event.get_text ()
812     # The contents of the element defined the shape, possible are normal, angled and square
813     ev.type = { "angled": "shortfermata", "square": "longfermata" }.get (txt, "fermata")
814     if hasattr (mxl_event, 'type'):
815       dir = musicxml_direction_to_indicator (mxl_event.type)
816       if dir and options.convert_directions:
817         ev.force_direction = dir
818     return ev
819
820 def musicxml_arpeggiate_to_lily_event (mxl_event):
821     ev = musicexp.ArpeggioEvent ()
822     ev.direction = musicxml_direction_to_indicator (getattr (mxl_event, 'direction', None))
823     return ev
824
825 def musicxml_nonarpeggiate_to_lily_event (mxl_event):
826     ev = musicexp.ArpeggioEvent ()
827     ev.non_arpeggiate = True
828     ev.direction = musicxml_direction_to_indicator (getattr (mxl_event, 'direction', None))
829     return ev
830
831 def musicxml_tremolo_to_lily_event (mxl_event):
832     ev = musicexp.TremoloEvent ()
833     txt = mxl_event.get_text ()
834     if txt:
835       ev.bars = txt
836     else:
837       ev.bars = "3"
838     return ev
839
840 def musicxml_falloff_to_lily_event (mxl_event):
841     ev = musicexp.BendEvent ()
842     ev.alter = -4
843     return ev
844
845 def musicxml_doit_to_lily_event (mxl_event):
846     ev = musicexp.BendEvent ()
847     ev.alter = 4
848     return ev
849
850 def musicxml_bend_to_lily_event (mxl_event):
851     ev = musicexp.BendEvent ()
852     ev.alter = mxl_event.bend_alter ()
853     return ev
854
855 def musicxml_caesura_to_lily_event (mxl_event):
856     ev = musicexp.MarkupEvent ()
857     # FIXME: default to straight or curved caesura?
858     ev.contents = "\\musicglyph #\"scripts.caesura.straight\""
859     ev.force_direction = 1
860     return ev
861
862 def musicxml_fingering_event (mxl_event):
863     ev = musicexp.ShortArticulationEvent ()
864     ev.type = mxl_event.get_text ()
865     return ev
866
867 def musicxml_snappizzicato_event (mxl_event):
868     needed_additional_definitions.append ("snappizzicato")
869     ev = musicexp.MarkupEvent ()
870     ev.contents = "\\snappizzicato"
871     return ev
872
873 def musicxml_string_event (mxl_event):
874     ev = musicexp.NoDirectionArticulationEvent ()
875     ev.type = mxl_event.get_text ()
876     return ev
877
878 def musicxml_accidental_mark (mxl_event):
879     ev = musicexp.MarkupEvent ()
880     contents = { "sharp": "\\sharp",
881       "natural": "\\natural",
882       "flat": "\\flat",
883       "double-sharp": "\\doublesharp",
884       "sharp-sharp": "\\sharp\\sharp",
885       "flat-flat": "\\flat\\flat",
886       "flat-flat": "\\doubleflat",
887       "natural-sharp": "\\natural\\sharp",
888       "natural-flat": "\\natural\\flat",
889       "quarter-flat": "\\semiflat",
890       "quarter-sharp": "\\semisharp",
891       "three-quarters-flat": "\\sesquiflat",
892       "three-quarters-sharp": "\\sesquisharp",
893     }.get (mxl_event.get_text ())
894     if contents:
895         ev.contents = contents
896         return ev
897     else:
898         return None
899
900 # translate articulations, ornaments and other notations into ArticulationEvents
901 # possible values:
902 #   -) string  (ArticulationEvent with that name)
903 #   -) function (function(mxl_event) needs to return a full ArticulationEvent-derived object
904 #   -) (class, name)  (like string, only that a different class than ArticulationEvent is used)
905 # TODO: Some translations are missing!
906 articulations_dict = {
907     "accent": (musicexp.ShortArticulationEvent, ">"), # or "accent"
908     "accidental-mark": musicxml_accidental_mark,
909     "bend": musicxml_bend_to_lily_event,
910     "breath-mark": (musicexp.NoDirectionArticulationEvent, "breathe"),
911     "caesura": musicxml_caesura_to_lily_event,
912     #"delayed-turn": "?",
913     "detached-legato": (musicexp.ShortArticulationEvent, "_"), # or "portato"
914     "doit": musicxml_doit_to_lily_event,
915     #"double-tongue": "?",
916     "down-bow": "downbow",
917     "falloff": musicxml_falloff_to_lily_event,
918     "fingering": musicxml_fingering_event,
919     #"fingernails": "?",
920     #"fret": "?",
921     #"hammer-on": "?",
922     "harmonic": "flageolet",
923     #"heel": "?",
924     "inverted-mordent": "prall",
925     "inverted-turn": "reverseturn",
926     "mordent": "mordent",
927     "open-string": "open",
928     #"plop": "?",
929     #"pluck": "?",
930     #"pull-off": "?",
931     #"schleifer": "?",
932     #"scoop": "?",
933     #"shake": "?",
934     "snap-pizzicato": musicxml_snappizzicato_event,
935     #"spiccato": "?",
936     "staccatissimo": (musicexp.ShortArticulationEvent, "|"), # or "staccatissimo"
937     "staccato": (musicexp.ShortArticulationEvent, "."), # or "staccato"
938     "stopped": (musicexp.ShortArticulationEvent, "+"), # or "stopped"
939     #"stress": "?",
940     "string": musicxml_string_event,
941     "strong-accent": (musicexp.ShortArticulationEvent, "^"), # or "marcato"
942     #"tap": "?",
943     "tenuto": (musicexp.ShortArticulationEvent, "-"), # or "tenuto"
944     "thumb-position": "thumb",
945     #"toe": "?",
946     "turn": "turn",
947     "tremolo": musicxml_tremolo_to_lily_event,
948     "trill-mark": "trill",
949     #"triple-tongue": "?",
950     #"unstress": "?"
951     "up-bow": "upbow",
952     #"wavy-line": "?",
953 }
954 articulation_spanners = [ "wavy-line" ]
955
956 def musicxml_articulation_to_lily_event (mxl_event):
957     # wavy-line elements are treated as trill spanners, not as articulation ornaments
958     if mxl_event.get_name () in articulation_spanners:
959         return musicxml_spanner_to_lily_event (mxl_event)
960
961     tmp_tp = articulations_dict.get (mxl_event.get_name ())
962     if not tmp_tp:
963         return
964
965     if isinstance (tmp_tp, str):
966         ev = musicexp.ArticulationEvent ()
967         ev.type = tmp_tp
968     elif isinstance (tmp_tp, tuple):
969         ev = tmp_tp[0] ()
970         ev.type = tmp_tp[1]
971     else:
972         ev = tmp_tp (mxl_event)
973
974     # Some articulations use the type attribute, other the placement...
975     dir = None
976     if hasattr (mxl_event, 'type') and options.convert_directions:
977         dir = musicxml_direction_to_indicator (mxl_event.type)
978     if hasattr (mxl_event, 'placement') and options.convert_directions:
979         dir = musicxml_direction_to_indicator (mxl_event.placement)
980     if dir:
981         ev.force_direction = dir
982     return ev
983
984
985
986 def musicxml_dynamics_to_lily_event (dynentry):
987     dynamics_available = (
988         "ppppp", "pppp", "ppp", "pp", "p", "mp", "mf", 
989         "f", "ff", "fff", "ffff", "fp", "sf", "sff", "sp", "spp", "sfz", "rfz" )
990     dynamicsname = dynentry.get_name ()
991     if dynamicsname == "other-dynamics":
992         dynamicsname = dynentry.get_text ()
993     if not dynamicsname or dynamicsname=="#text":
994         return
995
996     if not dynamicsname in dynamics_available:
997         # Get rid of - in tag names (illegal in ly tags!)
998         dynamicstext = dynamicsname
999         dynamicsname = string.replace (dynamicsname, "-", "")
1000         additional_definitions[dynamicsname] = dynamicsname + \
1001               " = #(make-dynamic-script \"" + dynamicstext + "\")"
1002         needed_additional_definitions.append (dynamicsname)
1003     event = musicexp.DynamicsEvent ()
1004     event.type = dynamicsname
1005     return event
1006
1007 # Convert single-color two-byte strings to numbers 0.0 - 1.0
1008 def hexcolorval_to_nr (hex_val):
1009     try:
1010         v = int (hex_val, 16)
1011         if v == 255:
1012             v = 256
1013         return v / 256.
1014     except ValueError:
1015         return 0.
1016
1017 def hex_to_color (hex_val):
1018     res = re.match (r'#([0-9a-f][0-9a-f]|)([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])$', hex_val, re.IGNORECASE)
1019     if res:
1020         return map (lambda x: hexcolorval_to_nr (x), res.group (2,3,4))
1021     else:
1022         return None
1023
1024 def musicxml_words_to_lily_event (words):
1025     event = musicexp.TextEvent ()
1026     text = words.get_text ()
1027     text = re.sub ('^ *\n? *', '', text)
1028     text = re.sub (' *\n? *$', '', text)
1029     event.text = text
1030
1031     if hasattr (words, 'default-y') and options.convert_directions:
1032         offset = getattr (words, 'default-y')
1033         try:
1034             off = string.atoi (offset)
1035             if off > 0:
1036                 event.force_direction = 1
1037             else:
1038                 event.force_direction = -1
1039         except ValueError:
1040             event.force_direction = 0
1041
1042     if hasattr (words, 'font-weight'):
1043         font_weight = { "normal": '', "bold": '\\bold' }.get (getattr (words, 'font-weight'), '')
1044         if font_weight:
1045             event.markup += font_weight
1046
1047     if hasattr (words, 'font-size'):
1048         size = getattr (words, 'font-size')
1049         font_size = {
1050             "xx-small": '\\teeny',
1051             "x-small": '\\tiny',
1052             "small": '\\small',
1053             "medium": '',
1054             "large": '\\large',
1055             "x-large": '\\huge',
1056             "xx-large": '\\bigger\\huge'
1057         }.get (size, '')
1058         if font_size:
1059             event.markup += font_size
1060
1061     if hasattr (words, 'color'):
1062         color = getattr (words, 'color')
1063         rgb = hex_to_color (color)
1064         if rgb:
1065             event.markup += "\\with-color #(rgb-color %s %s %s)" % (rgb[0], rgb[1], rgb[2])
1066
1067     if hasattr (words, 'font-style'):
1068         font_style = { "italic": '\\italic' }.get (getattr (words, 'font-style'), '')
1069         if font_style:
1070             event.markup += font_style
1071
1072     # TODO: How should I best convert the font-family attribute?
1073
1074     # TODO: How can I represent the underline, overline and line-through
1075     #       attributes in Lilypond? Values of these attributes indicate
1076     #       the number of lines
1077
1078     return event
1079
1080
1081 # convert accordion-registration to lilypond.
1082 # Since lilypond does not have any built-in commands, we need to create
1083 # the markup commands manually and define our own variables.
1084 # Idea was taken from: http://lsr.dsi.unimi.it/LSR/Item?id=194
1085 def musicxml_accordion_to_markup (mxl_event):
1086     commandname = "accReg"
1087     command = ""
1088
1089     high = mxl_event.get_maybe_exist_named_child ('accordion-high')
1090     if high:
1091         commandname += "H"
1092         command += """\\combine
1093           \\raise #2.5 \\musicglyph #\"accordion.accDot\"
1094           """
1095     middle = mxl_event.get_maybe_exist_named_child ('accordion-middle')
1096     if middle:
1097         # By default, use one dot (when no or invalid content is given). The 
1098         # MusicXML spec is quiet about this case...
1099         txt = 1
1100         try:
1101           txt = string.atoi (middle.get_text ())
1102         except ValueError:
1103             pass
1104         if txt == 3:
1105             commandname += "MMM"
1106             command += """\\combine
1107           \\raise #1.5 \\musicglyph #\"accordion.accDot\"
1108           \\combine
1109           \\raise #1.5 \\translate #(cons 1 0) \\musicglyph #\"accordion.accDot\"
1110           \\combine
1111           \\raise #1.5 \\translate #(cons -1 0) \\musicglyph #\"accordion.accDot\"
1112           """
1113         elif txt == 2:
1114             commandname += "MM"
1115             command += """\\combine
1116           \\raise #1.5 \\translate #(cons 0.5 0) \\musicglyph #\"accordion.accDot\"
1117           \\combine
1118           \\raise #1.5 \\translate #(cons -0.5 0) \\musicglyph #\"accordion.accDot\"
1119           """
1120         elif not txt <= 0:
1121             commandname += "M"
1122             command += """\\combine
1123           \\raise #1.5 \\musicglyph #\"accordion.accDot\"
1124           """
1125     low = mxl_event.get_maybe_exist_named_child ('accordion-low')
1126     if low:
1127         commandname += "L"
1128         command += """\\combine
1129           \\raise #0.5 \musicglyph #\"accordion.accDot\"
1130           """
1131
1132     command += "\musicglyph #\"accordion.accDiscant\""
1133     command = "\\markup { \\normalsize %s }" % command
1134     # Define the newly built command \accReg[H][MMM][L]
1135     additional_definitions[commandname] = "%s = %s" % (commandname, command)
1136     needed_additional_definitions.append (commandname)
1137     return "\\%s" % commandname
1138
1139 def musicxml_accordion_to_ly (mxl_event):
1140     txt = musicxml_accordion_to_markup (mxl_event)
1141     if txt:
1142         ev = musicexp.MarkEvent (txt)
1143         return ev
1144     return
1145
1146
1147 def musicxml_rehearsal_to_ly_mark (mxl_event):
1148     text = mxl_event.get_text ()
1149     if not text:
1150         return
1151     # default is boxed rehearsal marks!
1152     encl = "box"
1153     if hasattr (mxl_event, 'enclosure'):
1154         encl = {"none": None, "square": "box", "circle": "circle" }.get (mxl_event.enclosure, None)
1155     if encl:
1156         text = "\\%s { %s }" % (encl, text)
1157     ev = musicexp.MarkEvent ("\\markup { %s }" % text)
1158     return ev
1159
1160 def musicxml_eyeglasses_to_ly (mxl_event):
1161     needed_additional_definitions.append ("eyeglasses")
1162     return musicexp.MarkEvent ("\\eyeglasses")
1163
1164 def next_non_hash_index (lst, pos):
1165     pos += 1
1166     while pos < len (lst) and isinstance (lst[pos], musicxml.Hash_text):
1167         pos += 1
1168     return pos
1169
1170 def musicxml_metronome_to_ly (mxl_event):
1171     children = mxl_event.get_all_children ()
1172     if not children:
1173         return
1174
1175     index = -1
1176     index = next_non_hash_index (children, index)
1177     if isinstance (children[index], musicxml.BeatUnit): 
1178         # first form of metronome-mark, using unit and beats/min or other unit
1179         ev = musicexp.TempoMark ()
1180         if hasattr (mxl_event, 'parentheses'):
1181             ev.set_parentheses (mxl_event.parentheses == "yes")
1182
1183         d = musicexp.Duration ()
1184         d.duration_log = musicxml.musicxml_duration_to_log (children[index].get_text ())
1185         index = next_non_hash_index (children, index)
1186         if isinstance (children[index], musicxml.BeatUnitDot):
1187             d.dots = 1
1188             index = next_non_hash_index (children, index)
1189         ev.set_base_duration (d)
1190         if isinstance (children[index], musicxml.BeatUnit):
1191             # Form "note = newnote"
1192             newd = musicexp.Duration ()
1193             newd.duration_log = musicxml.musicxml_duration_to_log (children[index].get_text ())
1194             index = next_non_hash_index (children, index)
1195             if isinstance (children[index], musicxml.BeatUnitDot):
1196                 newd.dots = 1
1197                 index = next_non_hash_index (children, index)
1198             ev.set_new_duration (newd)
1199         elif isinstance (children[index], musicxml.PerMinute):
1200             # Form "note = bpm"
1201             try:
1202                 beats = int (children[index].get_text ())
1203                 ev.set_beats_per_minute (beats)
1204             except ValueError:
1205                 pass
1206         else:
1207             error_message (_ ("Unknown metronome mark, ignoring"))
1208             return
1209         return ev
1210     else:
1211         #TODO: Implement the other (more complex) way for tempo marks!
1212         error_message (_ ("Metronome marks with complex relations (<metronome-note> in MusicXML) are not yet implemented."))
1213         return
1214
1215 # translate directions into Events, possible values:
1216 #   -) string  (MarkEvent with that command)
1217 #   -) function (function(mxl_event) needs to return a full Event-derived object
1218 #   -) (class, name)  (like string, only that a different class than MarkEvent is used)
1219 directions_dict = {
1220     'accordion-registration' : musicxml_accordion_to_ly,
1221     'coda' : (musicexp.MusicGlyphMarkEvent, "coda"),
1222 #     'damp' : ???
1223 #     'damp-all' : ???
1224     'eyeglasses': musicxml_eyeglasses_to_ly,
1225 #     'harp-pedals' : ???
1226 #     'image' : ???
1227     'metronome' : musicxml_metronome_to_ly,
1228     'rehearsal' : musicxml_rehearsal_to_ly_mark,
1229 #     'scordatura' : ???
1230     'segno' : (musicexp.MusicGlyphMarkEvent, "segno"),
1231     'words' : musicxml_words_to_lily_event,
1232 }
1233 directions_spanners = [ 'octave-shift', 'pedal', 'wedge', 'dashes', 'bracket' ]
1234
1235 def musicxml_direction_to_lily (n):
1236     # TODO: Handle the <staff> element!
1237     res = []
1238     # placement applies to all children!
1239     dir = None
1240     if hasattr (n, 'placement') and options.convert_directions:
1241         dir = musicxml_direction_to_indicator (n.placement)
1242     dirtype_children = []
1243     # TODO: The direction-type is used for grouping (e.g. dynamics with text), 
1244     #       so we can't simply flatten them out!
1245     for dt in n.get_typed_children (musicxml.DirType):
1246         dirtype_children += dt.get_all_children ()
1247
1248     for entry in dirtype_children:
1249         # backets, dashes, octave shifts. pedal marks, hairpins etc. are spanners:
1250         if entry.get_name() in directions_spanners:
1251             event = musicxml_spanner_to_lily_event (entry)
1252             if event:
1253                 res.append (event)
1254             continue
1255
1256         # now treat all the "simple" ones, that can be translated using the dict
1257         ev = None
1258         tmp_tp = directions_dict.get (entry.get_name (), None)
1259         if isinstance (tmp_tp, str): # string means MarkEvent
1260             ev = musicexp.MarkEvent (tmp_tp)
1261         elif isinstance (tmp_tp, tuple): # tuple means (EventClass, "text")
1262             ev = tmp_tp[0] (tmp_tp[1])
1263         elif tmp_tp:
1264             ev = tmp_tp (entry)
1265         if ev:
1266             # TODO: set the correct direction! Unfortunately, \mark in ly does
1267             #       not seem to support directions!
1268             res.append (ev)
1269             continue
1270
1271         if entry.get_name () == "dynamics":
1272             for dynentry in entry.get_all_children ():
1273                 ev = musicxml_dynamics_to_lily_event (dynentry)
1274                 if ev:
1275                     res.append (ev)
1276
1277     return res
1278
1279 def musicxml_frame_to_lily_event (frame):
1280     ev = musicexp.FretEvent ()
1281     ev.strings = frame.get_strings ()
1282     ev.frets = frame.get_frets ()
1283     #offset = frame.get_first_fret () - 1
1284     barre = []
1285     for fn in frame.get_named_children ('frame-note'):
1286         fret = fn.get_fret ()
1287         if fret <= 0:
1288             fret = "o"
1289         el = [ fn.get_string (), fret ]
1290         fingering = fn.get_fingering ()
1291         if fingering >= 0:
1292             el.append (fingering)
1293         ev.elements.append (el)
1294         b = fn.get_barre ()
1295         if b == 'start':
1296             barre[0] = el[0] # start string
1297             barre[2] = el[1] # fret
1298         elif b == 'stop':
1299             barre[1] = el[0] # end string
1300     if barre:
1301         ev.barre = barre
1302     return ev
1303
1304 def musicxml_harmony_to_lily (n):
1305     res = []
1306     for f in n.get_named_children ('frame'):
1307         ev = musicxml_frame_to_lily_event (f)
1308         if ev:
1309             res.append (ev)
1310     return res
1311
1312
1313 def musicxml_chordpitch_to_lily (mxl_cpitch):
1314     r = musicexp.ChordPitch ()
1315     r.alteration = mxl_cpitch.get_alteration ()
1316     r.step = musicxml_step_to_lily (mxl_cpitch.get_step ())
1317     return r
1318
1319 chordkind_dict = {
1320     'major': '5',
1321     'minor': 'm5',
1322     'augmented': 'aug5',
1323     'diminished': 'dim5',
1324         # Sevenths:
1325     'dominant': '7',
1326     'major-seventh': 'maj7',
1327     'minor-seventh': 'm7',
1328     'diminished-seventh': 'dim7',
1329     'augmented-seventh': 'aug7',
1330     'half-diminished': 'dim5m7',
1331     'major-minor': 'maj7m5',
1332         # Sixths:
1333     'major-sixth': '6',
1334     'minor-sixth': 'm6',
1335         # Ninths:
1336     'dominant-ninth': '9',
1337     'major-ninth': 'maj9',
1338     'minor-ninth': 'm9',
1339         # 11ths (usually as the basis for alteration):
1340     'dominant-11th': '11',
1341     'major-11th': 'maj11',
1342     'minor-11th': 'm11',
1343         # 13ths (usually as the basis for alteration):
1344     'dominant-13th': '13.11',
1345     'major-13th': 'maj13.11',
1346     'minor-13th': 'm13',
1347         # Suspended:
1348     'suspended-second': 'sus2',
1349     'suspended-fourth': 'sus4',
1350         # Functional sixths:
1351     # TODO
1352     #'Neapolitan': '???',
1353     #'Italian': '???',
1354     #'French': '???',
1355     #'German': '???',
1356         # Other:
1357     #'pedal': '???',(pedal-point bass)
1358     'power': '5^3',
1359     #'Tristan': '???',
1360     'other': '1',
1361     'none': None,
1362 }
1363
1364 def musicxml_chordkind_to_lily (kind):
1365     res = chordkind_dict.get (kind, None)
1366     # Check for None, since a major chord is converted to ''
1367     if res == None:
1368         error_message (_ ("Unable to convert chord type %s to lilypond.") % kind)
1369     return res
1370
1371 def musicxml_harmony_to_lily_chordname (n):
1372     res = []
1373     root = n.get_maybe_exist_named_child ('root')
1374     if root:
1375         ev = musicexp.ChordNameEvent ()
1376         ev.root = musicxml_chordpitch_to_lily (root)
1377         kind = n.get_maybe_exist_named_child ('kind')
1378         if kind:
1379             ev.kind = musicxml_chordkind_to_lily (kind.get_text ())
1380             if not ev.kind:
1381                 return res
1382         bass = n.get_maybe_exist_named_child ('bass')
1383         if bass:
1384             ev.bass = musicxml_chordpitch_to_lily (bass)
1385         inversion = n.get_maybe_exist_named_child ('inversion')
1386         if inversion:
1387             # TODO: Lilypond does not support inversions, does it?
1388
1389             # Mail from Carl Sorensen on lilypond-devel, June 11, 2008:
1390             # 4. LilyPond supports the first inversion in the form of added 
1391             # bass notes.  So the first inversion of C major would be c:/g.   
1392             # To get the second inversion of C major, you would need to do 
1393             # e:6-3-^5 or e:m6-^5.  However, both of these techniques 
1394             # require you to know the chord and calculate either the fifth 
1395             # pitch (for the first inversion) or the third pitch (for the 
1396             # second inversion) so they may not be helpful for musicxml2ly.
1397             inversion_count = string.atoi (inversion.get_text ())
1398             if inversion_count == 1:
1399               # TODO: Calculate the bass note for the inversion...
1400               pass
1401             pass
1402         for deg in n.get_named_children ('degree'):
1403             d = musicexp.ChordModification ()
1404             d.type = deg.get_type ()
1405             d.step = deg.get_value ()
1406             d.alteration = deg.get_alter ()
1407             ev.add_modification (d)
1408         #TODO: convert the user-symbols attribute: 
1409             #major: a triangle, like Unicode 25B3
1410             #minor: -, like Unicode 002D
1411             #augmented: +, like Unicode 002B
1412             #diminished: (degree), like Unicode 00B0
1413             #half-diminished: (o with slash), like Unicode 00F8
1414         if ev and ev.root:
1415             res.append (ev)
1416
1417     return res
1418
1419 def musicxml_figured_bass_note_to_lily (n):
1420     res = musicexp.FiguredBassNote ()
1421     suffix_dict = { 'sharp' : "+", 
1422                     'flat' : "-", 
1423                     'natural' : "!", 
1424                     'double-sharp' : "++", 
1425                     'flat-flat' : "--", 
1426                     'sharp-sharp' : "++", 
1427                     'slash' : "/" }
1428     prefix = n.get_maybe_exist_named_child ('prefix')
1429     if prefix:
1430         res.set_prefix (suffix_dict.get (prefix.get_text (), ""))
1431     fnumber = n.get_maybe_exist_named_child ('figure-number')
1432     if fnumber:
1433         res.set_number (fnumber.get_text ())
1434     suffix = n.get_maybe_exist_named_child ('suffix')
1435     if suffix:
1436         res.set_suffix (suffix_dict.get (suffix.get_text (), ""))
1437     if n.get_maybe_exist_named_child ('extend'):
1438         # TODO: Implement extender lines (unfortunately, in lilypond you have 
1439         #       to use \set useBassFigureExtenders = ##t, which turns them on
1440         #       globally, while MusicXML has a property for each note...
1441         #       I'm not sure there is a proper way to implement this cleanly
1442         #n.extend
1443         pass
1444     return res
1445
1446
1447
1448 def musicxml_figured_bass_to_lily (n):
1449     if not isinstance (n, musicxml.FiguredBass):
1450         return
1451     res = musicexp.FiguredBassEvent ()
1452     for i in n.get_named_children ('figure'):
1453         note = musicxml_figured_bass_note_to_lily (i)
1454         if note:
1455             res.append (note)
1456     dur = n.get_maybe_exist_named_child ('duration')
1457     if dur:
1458         # apply the duration to res
1459         length = Rational(int(dur.get_text()), n._divisions)*Rational(1,4)
1460         res.set_real_duration (length)
1461         duration = rational_to_lily_duration (length)
1462         if duration:
1463             res.set_duration (duration)
1464     if hasattr (n, 'parentheses') and n.parentheses == "yes":
1465         res.set_parentheses (True)
1466     return res
1467
1468 instrument_drumtype_dict = {
1469     'Acoustic Snare Drum': 'acousticsnare',
1470     'Side Stick': 'sidestick',
1471     'Open Triangle': 'opentriangle',
1472     'Mute Triangle': 'mutetriangle',
1473     'Tambourine': 'tambourine',
1474     'Bass Drum': 'bassdrum',
1475 }
1476
1477 def musicxml_note_to_lily_main_event (n):
1478     pitch  = None
1479     duration = None
1480     event = None
1481
1482     mxl_pitch = n.get_maybe_exist_typed_child (musicxml.Pitch)
1483     if mxl_pitch:
1484         pitch = musicxml_pitch_to_lily (mxl_pitch)
1485         event = musicexp.NoteEvent ()
1486         event.pitch = pitch
1487
1488         acc = n.get_maybe_exist_named_child ('accidental')
1489         if acc:
1490             # let's not force accs everywhere. 
1491             event.cautionary = acc.editorial
1492
1493     elif n.get_maybe_exist_typed_child (musicxml.Unpitched):
1494         # Unpitched elements have display-step and can also have
1495         # display-octave.
1496         unpitched = n.get_maybe_exist_typed_child (musicxml.Unpitched)
1497         event = musicexp.NoteEvent ()
1498         event.pitch = musicxml_unpitched_to_lily (unpitched)
1499         
1500     elif n.get_maybe_exist_typed_child (musicxml.Rest):
1501         # rests can have display-octave and display-step, which are
1502         # treated like an ordinary note pitch
1503         rest = n.get_maybe_exist_typed_child (musicxml.Rest)
1504         event = musicexp.RestEvent ()
1505         pitch = musicxml_restdisplay_to_lily (rest)
1506         event.pitch = pitch
1507
1508     elif n.instrument_name:
1509         event = musicexp.NoteEvent ()
1510         drum_type = instrument_drumtype_dict.get (n.instrument_name)
1511         if drum_type:
1512             event.drum_type = drum_type
1513         else:
1514             n.message (_ ("drum %s type unknown, please add to instrument_drumtype_dict") % n.instrument_name)
1515             event.drum_type = 'acousticsnare'
1516
1517     else:
1518         n.message (_ ("cannot find suitable event"))
1519
1520     if event:
1521         event.duration = musicxml_duration_to_lily (n)
1522
1523     return event
1524
1525
1526 ## TODO
1527 class NegativeSkip:
1528     def __init__ (self, here, dest):
1529         self.here = here
1530         self.dest = dest
1531
1532 class LilyPondVoiceBuilder:
1533     def __init__ (self):
1534         self.elements = []
1535         self.pending_dynamics = []
1536         self.end_moment = Rational (0)
1537         self.begin_moment = Rational (0)
1538         self.pending_multibar = Rational (0)
1539         self.ignore_skips = False
1540         self.has_relevant_elements = False
1541
1542     def _insert_multibar (self):
1543         r = musicexp.MultiMeasureRest ()
1544         r.duration = musicexp.Duration()
1545         r.duration.duration_log = 0
1546         r.duration.factor = self.pending_multibar
1547         self.elements.append (r)
1548         self.begin_moment = self.end_moment
1549         self.end_moment = self.begin_moment + self.pending_multibar
1550         self.pending_multibar = Rational (0)
1551         
1552     def add_multibar_rest (self, duration):
1553         self.pending_multibar += duration
1554
1555     def set_duration (self, duration):
1556         self.end_moment = self.begin_moment + duration
1557     def current_duration (self):
1558         return self.end_moment - self.begin_moment
1559         
1560     def add_music (self, music, duration):
1561         assert isinstance (music, musicexp.Music)
1562         if self.pending_multibar > Rational (0):
1563             self._insert_multibar ()
1564
1565         self.has_relevant_elements = True
1566         self.elements.append (music)
1567         self.begin_moment = self.end_moment
1568         self.set_duration (duration)
1569         
1570         # Insert all pending dynamics right after the note/rest:
1571         if isinstance (music, musicexp.ChordEvent) and self.pending_dynamics:
1572             for d in self.pending_dynamics:
1573                 music.append (d)
1574             self.pending_dynamics = []
1575
1576     # Insert some music command that does not affect the position in the measure
1577     def add_command (self, command):
1578         assert isinstance (command, musicexp.Music)
1579         if self.pending_multibar > Rational (0):
1580             self._insert_multibar ()
1581         self.has_relevant_elements = True
1582         self.elements.append (command)
1583     def add_barline (self, barline):
1584         # TODO: Implement merging of default barline and custom bar line
1585         self.add_music (barline, Rational (0))
1586     def add_partial (self, command):
1587         self.ignore_skips = True
1588         self.add_command (command)
1589
1590     def add_dynamics (self, dynamic):
1591         # store the dynamic item(s) until we encounter the next note/rest:
1592         self.pending_dynamics.append (dynamic)
1593
1594     def add_bar_check (self, number):
1595         # re/store has_relevant_elements, so that a barline alone does not
1596         # trigger output for figured bass, chord names
1597         has_relevant = self.has_relevant_elements
1598         b = musicexp.BarLine ()
1599         b.bar_number = number
1600         self.add_barline (b)
1601         self.has_relevant_elements = has_relevant
1602
1603     def jumpto (self, moment):
1604         current_end = self.end_moment + self.pending_multibar
1605         diff = moment - current_end
1606         
1607         if diff < Rational (0):
1608             error_message (_ ('Negative skip %s') % diff)
1609             diff = Rational (0)
1610
1611         if diff > Rational (0) and not (self.ignore_skips and moment == 0):
1612             skip = musicexp.SkipEvent()
1613             duration_factor = 1
1614             duration_log = {1: 0, 2: 1, 4:2, 8:3, 16:4, 32:5, 64:6, 128:7, 256:8, 512:9}.get (diff.denominator (), -1)
1615             duration_dots = 0
1616             if duration_log > 0: # denominator is a power of 2...
1617                 if diff.numerator () == 3:
1618                     duration_log -= 1
1619                     duration_dots = 1
1620                 else:
1621                     duration_factor = Rational (diff.numerator ())
1622             else:
1623                 # for skips of a whole or more, simply use s1*factor
1624                 duration_log = 0
1625                 duration_factor = diff
1626             skip.duration.duration_log = duration_log
1627             skip.duration.factor = duration_factor
1628             skip.duration.dots = duration_dots
1629
1630             evc = musicexp.ChordEvent ()
1631             evc.elements.append (skip)
1632             self.add_music (evc, diff)
1633
1634         if diff > Rational (0) and moment == 0:
1635             self.ignore_skips = False
1636
1637     def last_event_chord (self, starting_at):
1638
1639         value = None
1640
1641         # if the position matches, find the last ChordEvent, do not cross a bar line!
1642         at = len( self.elements ) - 1
1643         while (at >= 0 and
1644                not isinstance (self.elements[at], musicexp.ChordEvent) and
1645                not isinstance (self.elements[at], musicexp.BarLine)):
1646             at -= 1
1647
1648         if (self.elements
1649             and at >= 0
1650             and isinstance (self.elements[at], musicexp.ChordEvent)
1651             and self.begin_moment == starting_at):
1652             value = self.elements[at]
1653         else:
1654             self.jumpto (starting_at)
1655             value = None
1656         return value
1657         
1658     def correct_negative_skip (self, goto):
1659         self.end_moment = goto
1660         self.begin_moment = goto
1661         evc = musicexp.ChordEvent ()
1662         self.elements.append (evc)
1663
1664
1665 class VoiceData:
1666     def __init__ (self):
1667         self.voicename = None
1668         self.voicedata = None
1669         self.ly_voice = None
1670         self.figured_bass = None
1671         self.chordnames = None
1672         self.lyrics_dict = {}
1673         self.lyrics_order = []
1674
1675 def musicxml_step_to_lily (step):
1676     if step:
1677         return (ord (step) - ord ('A') + 7 - 2) % 7
1678     else:
1679         return None
1680
1681 def musicxml_voice_to_lily_voice (voice):
1682     tuplet_events = []
1683     modes_found = {}
1684     lyrics = {}
1685     return_value = VoiceData ()
1686     return_value.voicedata = voice
1687     
1688     # First pitch needed for relative mode (if selected in command-line options)
1689     first_pitch = None
1690
1691     # Needed for melismata detection (ignore lyrics on those notes!):
1692     inside_slur = False
1693     is_tied = False
1694     is_chord = False
1695     is_beamed = False
1696     ignore_lyrics = False
1697
1698     current_staff = None
1699     
1700     pending_figured_bass = []
1701     pending_chordnames = []
1702
1703     # Make sure that the keys in the dict don't get reordered, since
1704     # we need the correct ordering of the lyrics stanzas! By default,
1705     # a dict will reorder its keys
1706     return_value.lyrics_order = voice.get_lyrics_numbers ()
1707     for k in return_value.lyrics_order:
1708         lyrics[k] = []
1709
1710     voice_builder = LilyPondVoiceBuilder ()
1711     figured_bass_builder = LilyPondVoiceBuilder ()
1712     chordnames_builder = LilyPondVoiceBuilder ()
1713
1714     for n in voice._elements:
1715         if n.get_name () == 'forward':
1716             continue
1717         staff = n.get_maybe_exist_named_child ('staff')
1718         if staff:
1719             staff = staff.get_text ()
1720             if current_staff and staff <> current_staff and not n.get_maybe_exist_named_child ('chord'):
1721                 voice_builder.add_command (musicexp.StaffChange (staff))
1722             current_staff = staff
1723
1724         if isinstance (n, musicxml.Partial) and n.partial > 0:
1725             a = musicxml_partial_to_lily (n.partial)
1726             if a:
1727                 voice_builder.add_partial (a)
1728             continue
1729
1730         if isinstance (n, musicxml.Direction):
1731             for a in musicxml_direction_to_lily (n):
1732                 if a.wait_for_note ():
1733                     voice_builder.add_dynamics (a)
1734                 else:
1735                     voice_builder.add_command (a)
1736             continue
1737
1738         if isinstance (n, musicxml.Harmony):
1739             for a in musicxml_harmony_to_lily (n):
1740                 if a.wait_for_note ():
1741                     voice_builder.add_dynamics (a)
1742                 else:
1743                     voice_builder.add_command (a)
1744             for a in musicxml_harmony_to_lily_chordname (n):
1745                 pending_chordnames.append (a)
1746             continue
1747
1748         if isinstance (n, musicxml.FiguredBass):
1749             a = musicxml_figured_bass_to_lily (n)
1750             if a:
1751                 pending_figured_bass.append (a)
1752             continue
1753
1754         is_chord = n.get_maybe_exist_named_child ('chord')
1755         if not is_chord:
1756             try:
1757                 voice_builder.jumpto (n._when)
1758             except NegativeSkip, neg:
1759                 voice_builder.correct_negative_skip (n._when)
1760                 n.message (_ ("Negative skip found: from %s to %s, difference is %s") % (neg.here, neg.dest, neg.dest - neg.here))
1761             
1762         if isinstance (n, musicxml.Attributes):
1763             if n.is_first () and n._measure_position == Rational (0):
1764                 try:
1765                     number = int (n.get_parent ().number)
1766                 except ValueError:
1767                     number = 0
1768                 if number > 0:
1769                     voice_builder.add_bar_check (number)
1770                     figured_bass_builder.add_bar_check (number)
1771                     chordnames_builder.add_bar_check (number)
1772
1773             for a in musicxml_attributes_to_lily (n):
1774                 voice_builder.add_command (a)
1775             continue
1776
1777         if isinstance (n, musicxml.Barline):
1778             barlines = musicxml_barline_to_lily (n)
1779             for a in barlines:
1780                 if isinstance (a, musicexp.BarLine):
1781                     voice_builder.add_barline (a)
1782                 elif isinstance (a, RepeatMarker) or isinstance (a, EndingMarker):
1783                     voice_builder.add_command (a)
1784             continue
1785
1786         if not n.__class__.__name__ == 'Note':
1787             error_message (_ ('unexpected %s; expected %s or %s or %s') % (n, 'Note', 'Attributes', 'Barline'))
1788             continue
1789
1790         rest = n.get_maybe_exist_typed_child (musicxml.Rest)
1791         if (rest
1792             and rest.is_whole_measure ()):
1793
1794             voice_builder.add_multibar_rest (n._duration)
1795             continue
1796
1797         if n.is_first () and n._measure_position == Rational (0):
1798             try: 
1799                 num = int (n.get_parent ().number)
1800             except ValueError:
1801                 num = 0
1802             if num > 0:
1803                 voice_builder.add_bar_check (num)
1804                 figured_bass_builder.add_bar_check (num)
1805                 chordnames_builder.add_bar_check (num)
1806
1807         main_event = musicxml_note_to_lily_main_event (n)
1808         if main_event and not first_pitch:
1809             first_pitch = main_event.pitch
1810         # ignore lyrics for notes inside a slur, tie, chord or beam
1811         ignore_lyrics = inside_slur or is_tied or is_chord or is_beamed
1812
1813         if main_event and hasattr (main_event, 'drum_type') and main_event.drum_type:
1814             modes_found['drummode'] = True
1815
1816         ev_chord = voice_builder.last_event_chord (n._when)
1817         if not ev_chord: 
1818             ev_chord = musicexp.ChordEvent()
1819             voice_builder.add_music (ev_chord, n._duration)
1820
1821         grace = n.get_maybe_exist_typed_child (musicxml.Grace)
1822         if grace:
1823             grace_chord = None
1824             if n.get_maybe_exist_typed_child (musicxml.Chord) and ev_chord.grace_elements:
1825                 grace_chord = ev_chord.grace_elements.get_last_event_chord ()
1826             if not grace_chord:
1827                 grace_chord = musicexp.ChordEvent ()
1828                 ev_chord.append_grace (grace_chord)
1829             if hasattr (grace, 'slash'):
1830                 # TODO: use grace_type = "appoggiatura" for slurred grace notes
1831                 if grace.slash == "yes":
1832                     ev_chord.grace_type = "acciaccatura"
1833             # now that we have inserted the chord into the grace music, insert
1834             # everything into that chord instead of the ev_chord
1835             ev_chord = grace_chord
1836             ev_chord.append (main_event)
1837             ignore_lyrics = True
1838         else:
1839             ev_chord.append (main_event)
1840             # When a note/chord has grace notes (duration==0), the duration of the
1841             # event chord is not yet known, but the event chord was already added
1842             # with duration 0. The following correct this when we hit the real note!
1843             if voice_builder.current_duration () == 0 and n._duration > 0:
1844                 voice_builder.set_duration (n._duration)
1845         
1846         # if we have a figured bass, set its voice builder to the correct position
1847         # and insert the pending figures
1848         if pending_figured_bass:
1849             try:
1850                 figured_bass_builder.jumpto (n._when)
1851             except NegativeSkip, neg:
1852                 pass
1853             for fb in pending_figured_bass:
1854                 # if a duration is given, use that, otherwise the one of the note
1855                 dur = fb.real_duration
1856                 if not dur:
1857                     dur = ev_chord.get_length ()
1858                 if not fb.duration:
1859                     fb.duration = ev_chord.get_duration ()
1860                 figured_bass_builder.add_music (fb, dur)
1861             pending_figured_bass = []
1862         
1863         if pending_chordnames:
1864             try:
1865                 chordnames_builder.jumpto (n._when)
1866             except NegativeSkip, neg:
1867                 pass
1868             for cn in pending_chordnames:
1869                 # Assign the duration of the EventChord
1870                 cn.duration = ev_chord.get_duration ()
1871                 chordnames_builder.add_music (cn, ev_chord.get_length ())
1872             pending_chordnames = []
1873
1874
1875         notations_children = n.get_typed_children (musicxml.Notations)
1876         tuplet_event = None
1877         span_events = []
1878
1879         # The <notation> element can have the following children (+ means implemented, ~ partially, - not):
1880         # +tied | +slur | +tuplet | glissando | slide | 
1881         #    ornaments | technical | articulations | dynamics |
1882         #    +fermata | arpeggiate | non-arpeggiate | 
1883         #    accidental-mark | other-notation
1884         for notations in notations_children:
1885             for tuplet_event in notations.get_tuplets():
1886                 mod = n.get_maybe_exist_typed_child (musicxml.Time_modification)
1887                 frac = (1,1)
1888                 if mod:
1889                     frac = mod.get_fraction ()
1890                 
1891                 tuplet_events.append ((ev_chord, tuplet_event, frac))
1892
1893             slurs = [s for s in notations.get_named_children ('slur')
1894                 if s.get_type () in ('start','stop')]
1895             if slurs:
1896                 if len (slurs) > 1:
1897                     error_message (_ ('cannot have two simultaneous slurs'))
1898                 # record the slur status for the next note in the loop
1899                 if not grace:
1900                     if slurs[0].get_type () == 'start':
1901                         inside_slur = True
1902                     elif slurs[0].get_type () == 'stop':
1903                         inside_slur = False
1904                 lily_ev = musicxml_spanner_to_lily_event (slurs[0])
1905                 ev_chord.append (lily_ev)
1906
1907             if not grace:
1908                 mxl_tie = notations.get_tie ()
1909                 if mxl_tie and mxl_tie.type == 'start':
1910                     ev_chord.append (musicexp.TieEvent ())
1911                     is_tied = True
1912                 else:
1913                     is_tied = False
1914
1915             fermatas = notations.get_named_children ('fermata')
1916             for a in fermatas:
1917                 ev = musicxml_fermata_to_lily_event (a)
1918                 if ev: 
1919                     ev_chord.append (ev)
1920
1921             arpeggiate = notations.get_named_children ('arpeggiate')
1922             for a in arpeggiate:
1923                 ev = musicxml_arpeggiate_to_lily_event (a)
1924                 if ev:
1925                     ev_chord.append (ev)
1926
1927             arpeggiate = notations.get_named_children ('non-arpeggiate')
1928             for a in arpeggiate:
1929                 ev = musicxml_nonarpeggiate_to_lily_event (a)
1930                 if ev:
1931                     ev_chord.append (ev)
1932
1933             glissandos = notations.get_named_children ('glissando')
1934             glissandos += notations.get_named_children ('slide')
1935             for a in glissandos:
1936                 ev = musicxml_spanner_to_lily_event (a)
1937                 if ev:
1938                     ev_chord.append (ev)
1939
1940             # accidental-marks are direct children of <notation>!
1941             for a in notations.get_named_children ('accidental-mark'):
1942                 ev = musicxml_articulation_to_lily_event (a)
1943                 if ev:
1944                     ev_chord.append (ev)
1945
1946             # Articulations can contain the following child elements:
1947             #         accent | strong-accent | staccato | tenuto |
1948             #         detached-legato | staccatissimo | spiccato |
1949             #         scoop | plop | doit | falloff | breath-mark | 
1950             #         caesura | stress | unstress
1951             # Technical can contain the following child elements:
1952             #         up-bow | down-bow | harmonic | open-string |
1953             #         thumb-position | fingering | pluck | double-tongue |
1954             #         triple-tongue | stopped | snap-pizzicato | fret |
1955             #         string | hammer-on | pull-off | bend | tap | heel |
1956             #         toe | fingernails | other-technical
1957             # Ornaments can contain the following child elements:
1958             #         trill-mark | turn | delayed-turn | inverted-turn |
1959             #         shake | wavy-line | mordent | inverted-mordent | 
1960             #         schleifer | tremolo | other-ornament, accidental-mark
1961             ornaments = notations.get_named_children ('ornaments')
1962             ornaments += notations.get_named_children ('articulations')
1963             ornaments += notations.get_named_children ('technical')
1964
1965             for a in ornaments:
1966                 for ch in a.get_all_children ():
1967                     ev = musicxml_articulation_to_lily_event (ch)
1968                     if ev: 
1969                         ev_chord.append (ev)
1970
1971             dynamics = notations.get_named_children ('dynamics')
1972             for a in dynamics:
1973                 for ch in a.get_all_children ():
1974                     ev = musicxml_dynamics_to_lily_event (ch)
1975                     if ev:
1976                         ev_chord.append (ev)
1977
1978
1979         mxl_beams = [b for b in n.get_named_children ('beam')
1980                      if (b.get_type () in ('begin', 'end')
1981                          and b.is_primary ())] 
1982         if mxl_beams and not conversion_settings.ignore_beaming:
1983             beam_ev = musicxml_spanner_to_lily_event (mxl_beams[0])
1984             if beam_ev:
1985                 ev_chord.append (beam_ev)
1986                 if beam_ev.span_direction == -1: # beam and thus melisma starts here
1987                     is_beamed = True
1988                 elif beam_ev.span_direction == 1: # beam and thus melisma ends here
1989                     is_beamed = False
1990             
1991         if tuplet_event:
1992             mod = n.get_maybe_exist_typed_child (musicxml.Time_modification)
1993             frac = (1,1)
1994             if mod:
1995                 frac = mod.get_fraction ()
1996                 
1997             tuplet_events.append ((ev_chord, tuplet_event, frac))
1998
1999         # Extract the lyrics
2000         if not rest and not ignore_lyrics:
2001             note_lyrics_processed = []
2002             note_lyrics_elements = n.get_typed_children (musicxml.Lyric)
2003             for l in note_lyrics_elements:
2004                 if l.get_number () < 0:
2005                     for k in lyrics.keys ():
2006                         lyrics[k].append (l.lyric_to_text ())
2007                         note_lyrics_processed.append (k)
2008                 else:
2009                     lyrics[l.number].append(l.lyric_to_text ())
2010                     note_lyrics_processed.append (l.number)
2011             for lnr in lyrics.keys ():
2012                 if not lnr in note_lyrics_processed:
2013                     lyrics[lnr].append ("\skip4")
2014
2015     ## force trailing mm rests to be written out.   
2016     voice_builder.add_music (musicexp.ChordEvent (), Rational (0))
2017     
2018     ly_voice = group_tuplets (voice_builder.elements, tuplet_events)
2019     ly_voice = group_repeats (ly_voice)
2020
2021     seq_music = musicexp.SequentialMusic ()
2022
2023     if 'drummode' in modes_found.keys ():
2024         ## \key <pitch> barfs in drummode.
2025         ly_voice = [e for e in ly_voice
2026                     if not isinstance(e, musicexp.KeySignatureChange)]
2027     
2028     seq_music.elements = ly_voice
2029     for k in lyrics.keys ():
2030         return_value.lyrics_dict[k] = musicexp.Lyrics ()
2031         return_value.lyrics_dict[k].lyrics_syllables = lyrics[k]
2032     
2033     
2034     if len (modes_found) > 1:
2035        error_message (_ ('cannot simultaneously have more than one mode: %s') % modes_found.keys ())
2036        
2037     if options.relative:
2038         v = musicexp.RelativeMusic ()
2039         v.element = seq_music
2040         v.basepitch = first_pitch
2041         seq_music = v
2042
2043     return_value.ly_voice = seq_music
2044     for mode in modes_found.keys ():
2045         v = musicexp.ModeChangingMusicWrapper()
2046         v.element = seq_music
2047         v.mode = mode
2048         return_value.ly_voice = v
2049     
2050     # create \figuremode { figured bass elements }
2051     if figured_bass_builder.has_relevant_elements:
2052         fbass_music = musicexp.SequentialMusic ()
2053         fbass_music.elements = figured_bass_builder.elements
2054         v = musicexp.ModeChangingMusicWrapper()
2055         v.mode = 'figuremode'
2056         v.element = fbass_music
2057         return_value.figured_bass = v
2058     
2059     # create \chordmode { chords }
2060     if chordnames_builder.has_relevant_elements:
2061         cname_music = musicexp.SequentialMusic ()
2062         cname_music.elements = chordnames_builder.elements
2063         v = musicexp.ModeChangingMusicWrapper()
2064         v.mode = 'chordmode'
2065         v.element = cname_music
2066         return_value.chordnames = v
2067     
2068     return return_value
2069
2070 def musicxml_id_to_lily (id):
2071     digits = ['Zero', 'One', 'Two', 'Three', 'Four', 'Five',
2072               'Six', 'Seven', 'Eight', 'Nine', 'Ten']
2073     
2074     for digit in digits:
2075         d = digits.index (digit)
2076         id = re.sub ('%d' % d, digit, id)
2077
2078     id = re.sub  ('[^a-zA-Z]', 'X', id)
2079     return id
2080
2081 def musicxml_pitch_to_lily (mxl_pitch):
2082     p = musicexp.Pitch ()
2083     p.alteration = mxl_pitch.get_alteration ()
2084     p.step = musicxml_step_to_lily (mxl_pitch.get_step ())
2085     p.octave = mxl_pitch.get_octave () - 4
2086     return p
2087
2088 def musicxml_unpitched_to_lily (mxl_unpitched):
2089     p = None
2090     step = mxl_unpitched.get_step ()
2091     if step:
2092         p = musicexp.Pitch ()
2093         p.step = musicxml_step_to_lily (step)
2094     octave = mxl_unpitched.get_octave ()
2095     if octave and p:
2096         p.octave = octave - 4
2097     return p
2098
2099 def musicxml_restdisplay_to_lily (mxl_rest):
2100     p = None
2101     step = mxl_rest.get_step ()
2102     if step:
2103         p = musicexp.Pitch ()
2104         p.step = musicxml_step_to_lily (step)
2105     octave = mxl_rest.get_octave ()
2106     if octave and p:
2107         p.octave = octave - 4
2108     return p
2109
2110 def voices_in_part (part):
2111     """Return a Name -> Voice dictionary for PART"""
2112     part.interpret ()
2113     part.extract_voices ()
2114     voices = part.get_voices ()
2115     part_info = part.get_staff_attributes ()
2116
2117     return (voices, part_info)
2118
2119 def voices_in_part_in_parts (parts):
2120     """return a Part -> Name -> Voice dictionary"""
2121     return dict([(p.id, voices_in_part (p)) for p in parts])
2122
2123
2124 def get_all_voices (parts):
2125     all_voices = voices_in_part_in_parts (parts)
2126
2127     all_ly_voices = {}
2128     all_ly_staffinfo = {}
2129     for p, (name_voice, staff_info) in all_voices.items ():
2130
2131         part_ly_voices = {}
2132         for n, v in name_voice.items ():
2133             progress (_ ("Converting to LilyPond expressions..."))
2134             # musicxml_voice_to_lily_voice returns (lily_voice, {nr->lyrics, nr->lyrics})
2135             part_ly_voices[n] = musicxml_voice_to_lily_voice (v)
2136
2137         all_ly_voices[p] = part_ly_voices
2138         all_ly_staffinfo[p] = staff_info
2139
2140     return (all_ly_voices, all_ly_staffinfo)
2141
2142
2143 def option_parser ():
2144     p = ly.get_option_parser (usage = _ ("musicxml2ly [options] FILE.xml"),
2145                              description = _ ("Convert MusicXML from FILE.xml to LilyPond input. If the given filename is -, musicxml2ly reads from the command line.") + "\n",
2146                              add_help_option=False)
2147
2148     p.add_option("-h", "--help",
2149                  action="help",
2150                  help=_ ("show this help and exit"))
2151
2152     p.version = ('''%prog (LilyPond) @TOPLEVEL_VERSION@\n\n'''
2153                                       +
2154 _ ("""This program is free software.  It is covered by the GNU General Public
2155 License and you are welcome to change it and/or distribute copies of it
2156 under certain conditions.  Invoke as `%s --warranty' for more
2157 information.""") % 'lilypond'
2158 + """
2159 Copyright (c) 2005--2008 by
2160     Han-Wen Nienhuys <hanwen@xs4all.nl>,
2161     Jan Nieuwenhuizen <janneke@gnu.org> and
2162     Reinhold Kainhofer <reinhold@kainhofer.com>
2163 """)
2164     p.add_option("--version",
2165                  action="version",
2166                  help=_ ("show version number and exit"))
2167
2168     p.add_option ('-v', '--verbose',
2169                   action = "store_true",
2170                   dest = 'verbose',
2171                   help = _ ("be verbose"))
2172
2173     p.add_option ('', '--lxml',
2174                   action = "store_true",
2175                   default = False,
2176                   dest = "use_lxml",
2177                   help = _ ("use lxml.etree; uses less memory and cpu time"))
2178
2179     p.add_option ('-z', '--compressed',
2180                   action = "store_true",
2181                   dest = 'compressed',
2182                   default = False,
2183                   help = _ ("input file is a zip-compressed MusicXML file"))
2184
2185     p.add_option ('-r', '--relative',
2186                   action = "store_true",
2187                   default = True,
2188                   dest = "relative",
2189                   help = _ ("convert pitches in relative mode (default)"))
2190
2191     p.add_option ('-a', '--absolute',
2192                   action = "store_false",
2193                   dest = "relative",
2194                   help = _ ("convert pitches in absolute mode"))
2195
2196     p.add_option ('-l', '--language',
2197                   metavar = _ ("LANG"),
2198                   action = "store",
2199                   help = _ ("use a different language file 'LANG.ly' and corresponding pitch names, e.g. 'deutsch' for deutsch.ly"))
2200
2201     p.add_option ('--nd', '--no-articulation-directions', 
2202                   action = "store_false",
2203                   default = True,
2204                   dest = "convert_directions",
2205                   help = _ ("do not convert directions (^, _ or -) for articulations, dynamics, etc."))
2206
2207     p.add_option ('--no-beaming', 
2208                   action = "store_false",
2209                   default = True,
2210                   dest = "convert_beaming",
2211                   help = _ ("do not convert beaming information, use lilypond's automatic beaming instead"))
2212
2213     p.add_option ('-o', '--output',
2214                   metavar = _ ("FILE"),
2215                   action = "store",
2216                   default = None,
2217                   type = 'string',
2218                   dest = 'output_name',
2219                   help = _ ("set output filename to FILE, stdout if -"))
2220     p.add_option_group (ly.display_encode (_ ('Bugs')),
2221                         description = (_ ("Report bugs via")
2222                                      + ''' http://post.gmane.org/post.php'''
2223                                      '''?group=gmane.comp.gnu.lilypond.bugs\n'''))
2224     return p
2225
2226 def music_xml_voice_name_to_lily_name (part_id, name):
2227     str = "Part%sVoice%s" % (part_id, name)
2228     return musicxml_id_to_lily (str) 
2229
2230 def music_xml_lyrics_name_to_lily_name (part_id, name, lyricsnr):
2231     str = "Part%sVoice%sLyrics%s" % (part_id, name, lyricsnr)
2232     return musicxml_id_to_lily (str) 
2233
2234 def music_xml_figuredbass_name_to_lily_name (part_id, voicename):
2235     str = "Part%sVoice%sFiguredBass" % (part_id, voicename)
2236     return musicxml_id_to_lily (str) 
2237
2238 def music_xml_chordnames_name_to_lily_name (part_id, voicename):
2239     str = "Part%sVoice%sChords" % (part_id, voicename)
2240     return musicxml_id_to_lily (str) 
2241
2242 def print_voice_definitions (printer, part_list, voices):
2243     for part in part_list:
2244         part_id = part.id
2245         nv_dict = voices.get (part_id, {})
2246         for (name, voice) in nv_dict.items ():
2247             k = music_xml_voice_name_to_lily_name (part_id, name)
2248             printer.dump ('%s = ' % k)
2249             voice.ly_voice.print_ly (printer)
2250             printer.newline()
2251             if voice.chordnames:
2252                 cnname = music_xml_chordnames_name_to_lily_name (part_id, name)
2253                 printer.dump ('%s = ' % cnname )
2254                 voice.chordnames.print_ly (printer)
2255                 printer.newline()
2256             for l in voice.lyrics_order:
2257                 lname = music_xml_lyrics_name_to_lily_name (part_id, name, l)
2258                 printer.dump ('%s = ' % lname )
2259                 voice.lyrics_dict[l].print_ly (printer)
2260                 printer.newline()
2261             if voice.figured_bass:
2262                 fbname = music_xml_figuredbass_name_to_lily_name (part_id, name)
2263                 printer.dump ('%s = ' % fbname )
2264                 voice.figured_bass.print_ly (printer)
2265                 printer.newline()
2266
2267
2268 def uniq_list (l):
2269     return dict ([(elt,1) for elt in l]).keys ()
2270
2271 # format the information about the staff in the form 
2272 #     [staffid,
2273 #         [
2274 #            [voiceid1, [lyricsid11, lyricsid12,...], figuredbassid1],
2275 #            [voiceid2, [lyricsid21, lyricsid22,...], figuredbassid2],
2276 #            ...
2277 #         ]
2278 #     ]
2279 # raw_voices is of the form [(voicename, lyricsids, havefiguredbass)*]
2280 def format_staff_info (part_id, staff_id, raw_voices):
2281     voices = []
2282     for (v, lyricsids, figured_bass, chordnames) in raw_voices:
2283         voice_name = music_xml_voice_name_to_lily_name (part_id, v)
2284         voice_lyrics = [music_xml_lyrics_name_to_lily_name (part_id, v, l)
2285                    for l in lyricsids]
2286         figured_bass_name = ''
2287         if figured_bass:
2288             figured_bass_name = music_xml_figuredbass_name_to_lily_name (part_id, v)
2289         chordnames_name = ''
2290         if chordnames:
2291             chordnames_name = music_xml_chordnames_name_to_lily_name (part_id, v)
2292         voices.append ([voice_name, voice_lyrics, figured_bass_name, chordnames_name])
2293     return [staff_id, voices]
2294
2295 def update_score_setup (score_structure, part_list, voices):
2296
2297     for part_definition in part_list:
2298         part_id = part_definition.id
2299         nv_dict = voices.get (part_id)
2300         if not nv_dict:
2301             error_message (_ ('unknown part in part-list: %s') % part_id)
2302             continue
2303
2304         staves = reduce (lambda x,y: x+ y,
2305                 [voice.voicedata._staves.keys ()
2306                  for voice in nv_dict.values ()],
2307                 [])
2308         staves_info = []
2309         if len (staves) > 1:
2310             staves_info = []
2311             staves = uniq_list (staves)
2312             staves.sort ()
2313             for s in staves:
2314                 thisstaff_raw_voices = [(voice_name, voice.lyrics_order, voice.figured_bass, voice.chordnames) 
2315                     for (voice_name, voice) in nv_dict.items ()
2316                     if voice.voicedata._start_staff == s]
2317                 staves_info.append (format_staff_info (part_id, s, thisstaff_raw_voices))
2318         else:
2319             thisstaff_raw_voices = [(voice_name, voice.lyrics_order, voice.figured_bass, voice.chordnames) 
2320                 for (voice_name, voice) in nv_dict.items ()]
2321             staves_info.append (format_staff_info (part_id, None, thisstaff_raw_voices))
2322         score_structure.set_part_information (part_id, staves_info)
2323
2324 # Set global values in the \layout block, like auto-beaming etc.
2325 def update_layout_information ():
2326     if not conversion_settings.ignore_beaming and layout_information:
2327         layout_information.set_context_item ('Score', 'autoBeaming = ##f')
2328
2329 def print_ly_preamble (printer, filename):
2330     printer.dump_version ()
2331     printer.print_verbatim ('%% automatically converted from %s\n' % filename)
2332
2333 def print_ly_additional_definitions (printer, filename):
2334     if needed_additional_definitions:
2335         printer.newline ()
2336         printer.print_verbatim ('%% additional definitions required by the score:')
2337         printer.newline ()
2338     for a in set(needed_additional_definitions):
2339         printer.print_verbatim (additional_definitions.get (a, ''))
2340         printer.newline ()
2341     printer.newline ()
2342
2343 # Read in the tree from the given I/O object (either file or string) and 
2344 # demarshall it using the classes from the musicxml.py file
2345 def read_xml (io_object, use_lxml):
2346     if use_lxml:
2347         import lxml.etree
2348         tree = lxml.etree.parse (io_object)
2349         mxl_tree = musicxml.lxml_demarshal_node (tree.getroot ())
2350         return mxl_tree
2351     else:
2352         from xml.dom import minidom, Node
2353         doc = minidom.parse(io_object)
2354         node = doc.documentElement
2355         return musicxml.minidom_demarshal_node (node)
2356     return None
2357
2358
2359 def read_musicxml (filename, compressed, use_lxml):
2360     raw_string = None
2361     if compressed:
2362         if filename == "-":
2363              progress (_ ("Input is compressed, extracting raw MusicXML data from stdin") )
2364              z = zipfile.ZipFile (sys.stdin)
2365         else:
2366             progress (_ ("Input file %s is compressed, extracting raw MusicXML data") % filename)
2367             z = zipfile.ZipFile (filename, "r")
2368         container_xml = z.read ("META-INF/container.xml")
2369         if not container_xml:
2370             return None
2371         container = read_xml (StringIO.StringIO (container_xml), use_lxml)
2372         if not container:
2373             return None
2374         rootfiles = container.get_maybe_exist_named_child ('rootfiles')
2375         if not rootfiles:
2376             return None
2377         rootfile_list = rootfiles.get_named_children ('rootfile')
2378         mxml_file = None
2379         if len (rootfile_list) > 0:
2380             mxml_file = getattr (rootfile_list[0], 'full-path', None)
2381         if mxml_file:
2382             raw_string = z.read (mxml_file)
2383
2384     if raw_string:
2385         io_object = StringIO.StringIO (raw_string)
2386     elif filename == "-":
2387         io_object = sys.stdin
2388     else:
2389         io_object = filename
2390
2391     return read_xml (io_object, use_lxml)
2392
2393
2394 def convert (filename, options):
2395     if filename == "-":
2396         progress (_ ("Reading MusicXML from Standard input ...") )
2397     else:
2398         progress (_ ("Reading MusicXML from %s ...") % filename)
2399
2400     tree = read_musicxml (filename, options.compressed, options.use_lxml)
2401     score_information = extract_score_information (tree)
2402     paper_information = extract_paper_information (tree)
2403
2404     parts = tree.get_typed_children (musicxml.Part)
2405     (voices, staff_info) = get_all_voices (parts)
2406
2407     score_structure = None
2408     mxl_pl = tree.get_maybe_exist_typed_child (musicxml.Part_list)
2409     if mxl_pl:
2410         score_structure = extract_score_structure (mxl_pl, staff_info)
2411         part_list = mxl_pl.get_named_children ("score-part")
2412
2413     # score information is contained in the <work>, <identification> or <movement-title> tags
2414     update_score_setup (score_structure, part_list, voices)
2415     # After the conversion, update the list of settings for the \layout block
2416     update_layout_information ()
2417
2418     if not options.output_name:
2419         options.output_name = os.path.basename (filename) 
2420         options.output_name = os.path.splitext (options.output_name)[0]
2421     elif re.match (".*\.ly", options.output_name):
2422         options.output_name = os.path.splitext (options.output_name)[0]
2423
2424
2425     #defs_ly_name = options.output_name + '-defs.ly'
2426     if (options.output_name == "-"):
2427       output_ly_name = 'Standard output'
2428     else:
2429       output_ly_name = options.output_name + '.ly'
2430
2431     progress (_ ("Output to `%s'") % output_ly_name)
2432     printer = musicexp.Output_printer()
2433     #progress (_ ("Output to `%s'") % defs_ly_name)
2434     if (options.output_name == "-"):
2435       printer.set_file (codecs.getwriter ("utf-8")(sys.stdout))
2436     else:
2437       printer.set_file (codecs.open (output_ly_name, 'wb', encoding='utf-8'))
2438     print_ly_preamble (printer, filename)
2439     print_ly_additional_definitions (printer, filename)
2440     if score_information:
2441         score_information.print_ly (printer)
2442     if paper_information:
2443         paper_information.print_ly (printer)
2444     if layout_information:
2445         layout_information.print_ly (printer)
2446     print_voice_definitions (printer, part_list, voices)
2447     
2448     printer.newline ()
2449     printer.dump ("% The score definition")
2450     printer.newline ()
2451     score_structure.print_ly (printer)
2452     printer.newline ()
2453
2454     return voices
2455
2456 def get_existing_filename_with_extension (filename, ext):
2457     if os.path.exists (filename):
2458         return filename
2459     newfilename = filename + "." + ext
2460     if os.path.exists (newfilename):
2461         return newfilename;
2462     newfilename = filename + ext
2463     if os.path.exists (newfilename):
2464         return newfilename;
2465     return ''
2466
2467 def main ():
2468     opt_parser = option_parser()
2469
2470     global options
2471     (options, args) = opt_parser.parse_args ()
2472     if not args:
2473         opt_parser.print_usage()
2474         sys.exit (2)
2475
2476     if options.language:
2477         musicexp.set_pitch_language (options.language)
2478         needed_additional_definitions.append (options.language)
2479         additional_definitions[options.language] = "\\include \"%s.ly\"\n" % options.language
2480     conversion_settings.ignore_beaming = not options.convert_beaming
2481
2482     # Allow the user to leave out the .xml or xml on the filename
2483     if args[0]=="-": # Read from stdin
2484         filename="-"
2485     else:
2486         filename = get_existing_filename_with_extension (args[0], "xml")
2487         if not filename:
2488             filename = get_existing_filename_with_extension (args[0], "mxl")
2489             options.compressed = True
2490     if filename and (filename == "-" or os.path.exists (filename)):
2491         voices = convert (filename, options)
2492     else:
2493         progress (_ ("Unable to find input file %s") % args[0])
2494
2495 if __name__ == '__main__':
2496     main()