]> git.donarmstrong.com Git - lilypond.git/blob - scripts/musicxml2ly.py
Merge master into nested-bookparts
[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": '\\larger\\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_harp_pedals_to_ly (mxl_event):
1161     count = 0
1162     result = "\\harp-pedal #\""
1163     for t in mxl_event.get_named_children ('pedal-tuning'):
1164       alter = t.get_named_child ('pedal-alter')
1165       if alter:
1166         val = int (alter.get_text ().strip ())
1167         result += {1: "v", 0: "-", -1: "^"}.get (val, "")
1168       count += 1
1169       if count == 3:
1170         result += "|"
1171     ev = musicexp.MarkupEvent ()
1172     ev.contents = result + "\""
1173     return ev
1174
1175 def musicxml_eyeglasses_to_ly (mxl_event):
1176     needed_additional_definitions.append ("eyeglasses")
1177     return musicexp.MarkEvent ("\\eyeglasses")
1178
1179 def next_non_hash_index (lst, pos):
1180     pos += 1
1181     while pos < len (lst) and isinstance (lst[pos], musicxml.Hash_text):
1182         pos += 1
1183     return pos
1184
1185 def musicxml_metronome_to_ly (mxl_event):
1186     children = mxl_event.get_all_children ()
1187     if not children:
1188         return
1189
1190     index = -1
1191     index = next_non_hash_index (children, index)
1192     if isinstance (children[index], musicxml.BeatUnit): 
1193         # first form of metronome-mark, using unit and beats/min or other unit
1194         ev = musicexp.TempoMark ()
1195         if hasattr (mxl_event, 'parentheses'):
1196             ev.set_parentheses (mxl_event.parentheses == "yes")
1197
1198         d = musicexp.Duration ()
1199         d.duration_log = musicxml.musicxml_duration_to_log (children[index].get_text ())
1200         index = next_non_hash_index (children, index)
1201         if isinstance (children[index], musicxml.BeatUnitDot):
1202             d.dots = 1
1203             index = next_non_hash_index (children, index)
1204         ev.set_base_duration (d)
1205         if isinstance (children[index], musicxml.BeatUnit):
1206             # Form "note = newnote"
1207             newd = musicexp.Duration ()
1208             newd.duration_log = musicxml.musicxml_duration_to_log (children[index].get_text ())
1209             index = next_non_hash_index (children, index)
1210             if isinstance (children[index], musicxml.BeatUnitDot):
1211                 newd.dots = 1
1212                 index = next_non_hash_index (children, index)
1213             ev.set_new_duration (newd)
1214         elif isinstance (children[index], musicxml.PerMinute):
1215             # Form "note = bpm"
1216             try:
1217                 beats = int (children[index].get_text ())
1218                 ev.set_beats_per_minute (beats)
1219             except ValueError:
1220                 pass
1221         else:
1222             error_message (_ ("Unknown metronome mark, ignoring"))
1223             return
1224         return ev
1225     else:
1226         #TODO: Implement the other (more complex) way for tempo marks!
1227         error_message (_ ("Metronome marks with complex relations (<metronome-note> in MusicXML) are not yet implemented."))
1228         return
1229
1230 # translate directions into Events, possible values:
1231 #   -) string  (MarkEvent with that command)
1232 #   -) function (function(mxl_event) needs to return a full Event-derived object
1233 #   -) (class, name)  (like string, only that a different class than MarkEvent is used)
1234 directions_dict = {
1235     'accordion-registration' : musicxml_accordion_to_ly,
1236     'coda' : (musicexp.MusicGlyphMarkEvent, "coda"),
1237 #     'damp' : ???
1238 #     'damp-all' : ???
1239     'eyeglasses': musicxml_eyeglasses_to_ly,
1240     'harp-pedals' : musicxml_harp_pedals_to_ly,
1241 #     'image' : ???
1242     'metronome' : musicxml_metronome_to_ly,
1243     'rehearsal' : musicxml_rehearsal_to_ly_mark,
1244 #     'scordatura' : ???
1245     'segno' : (musicexp.MusicGlyphMarkEvent, "segno"),
1246     'words' : musicxml_words_to_lily_event,
1247 }
1248 directions_spanners = [ 'octave-shift', 'pedal', 'wedge', 'dashes', 'bracket' ]
1249
1250 def musicxml_direction_to_lily (n):
1251     # TODO: Handle the <staff> element!
1252     res = []
1253     # placement applies to all children!
1254     dir = None
1255     if hasattr (n, 'placement') and options.convert_directions:
1256         dir = musicxml_direction_to_indicator (n.placement)
1257     dirtype_children = []
1258     # TODO: The direction-type is used for grouping (e.g. dynamics with text), 
1259     #       so we can't simply flatten them out!
1260     for dt in n.get_typed_children (musicxml.DirType):
1261         dirtype_children += dt.get_all_children ()
1262
1263     for entry in dirtype_children:
1264         # backets, dashes, octave shifts. pedal marks, hairpins etc. are spanners:
1265         if entry.get_name() in directions_spanners:
1266             event = musicxml_spanner_to_lily_event (entry)
1267             if event:
1268                 res.append (event)
1269             continue
1270
1271         # now treat all the "simple" ones, that can be translated using the dict
1272         ev = None
1273         tmp_tp = directions_dict.get (entry.get_name (), None)
1274         if isinstance (tmp_tp, str): # string means MarkEvent
1275             ev = musicexp.MarkEvent (tmp_tp)
1276         elif isinstance (tmp_tp, tuple): # tuple means (EventClass, "text")
1277             ev = tmp_tp[0] (tmp_tp[1])
1278         elif tmp_tp:
1279             ev = tmp_tp (entry)
1280         if ev:
1281             # TODO: set the correct direction! Unfortunately, \mark in ly does
1282             #       not seem to support directions!
1283             res.append (ev)
1284             continue
1285
1286         if entry.get_name () == "dynamics":
1287             for dynentry in entry.get_all_children ():
1288                 ev = musicxml_dynamics_to_lily_event (dynentry)
1289                 if ev:
1290                     res.append (ev)
1291
1292     return res
1293
1294 def musicxml_frame_to_lily_event (frame):
1295     ev = musicexp.FretEvent ()
1296     ev.strings = frame.get_strings ()
1297     ev.frets = frame.get_frets ()
1298     #offset = frame.get_first_fret () - 1
1299     barre = []
1300     for fn in frame.get_named_children ('frame-note'):
1301         fret = fn.get_fret ()
1302         if fret <= 0:
1303             fret = "o"
1304         el = [ fn.get_string (), fret ]
1305         fingering = fn.get_fingering ()
1306         if fingering >= 0:
1307             el.append (fingering)
1308         ev.elements.append (el)
1309         b = fn.get_barre ()
1310         if b == 'start':
1311             barre[0] = el[0] # start string
1312             barre[2] = el[1] # fret
1313         elif b == 'stop':
1314             barre[1] = el[0] # end string
1315     if barre:
1316         ev.barre = barre
1317     return ev
1318
1319 def musicxml_harmony_to_lily (n):
1320     res = []
1321     for f in n.get_named_children ('frame'):
1322         ev = musicxml_frame_to_lily_event (f)
1323         if ev:
1324             res.append (ev)
1325     return res
1326
1327
1328 def musicxml_chordpitch_to_lily (mxl_cpitch):
1329     r = musicexp.ChordPitch ()
1330     r.alteration = mxl_cpitch.get_alteration ()
1331     r.step = musicxml_step_to_lily (mxl_cpitch.get_step ())
1332     return r
1333
1334 chordkind_dict = {
1335     'major': '5',
1336     'minor': 'm5',
1337     'augmented': 'aug5',
1338     'diminished': 'dim5',
1339         # Sevenths:
1340     'dominant': '7',
1341     'major-seventh': 'maj7',
1342     'minor-seventh': 'm7',
1343     'diminished-seventh': 'dim7',
1344     'augmented-seventh': 'aug7',
1345     'half-diminished': 'dim5m7',
1346     'major-minor': 'maj7m5',
1347         # Sixths:
1348     'major-sixth': '6',
1349     'minor-sixth': 'm6',
1350         # Ninths:
1351     'dominant-ninth': '9',
1352     'major-ninth': 'maj9',
1353     'minor-ninth': 'm9',
1354         # 11ths (usually as the basis for alteration):
1355     'dominant-11th': '11',
1356     'major-11th': 'maj11',
1357     'minor-11th': 'm11',
1358         # 13ths (usually as the basis for alteration):
1359     'dominant-13th': '13.11',
1360     'major-13th': 'maj13.11',
1361     'minor-13th': 'm13',
1362         # Suspended:
1363     'suspended-second': 'sus2',
1364     'suspended-fourth': 'sus4',
1365         # Functional sixths:
1366     # TODO
1367     #'Neapolitan': '???',
1368     #'Italian': '???',
1369     #'French': '???',
1370     #'German': '???',
1371         # Other:
1372     #'pedal': '???',(pedal-point bass)
1373     'power': '5^3',
1374     #'Tristan': '???',
1375     'other': '1',
1376     'none': None,
1377 }
1378
1379 def musicxml_chordkind_to_lily (kind):
1380     res = chordkind_dict.get (kind, None)
1381     # Check for None, since a major chord is converted to ''
1382     if res == None:
1383         error_message (_ ("Unable to convert chord type %s to lilypond.") % kind)
1384     return res
1385
1386 def musicxml_harmony_to_lily_chordname (n):
1387     res = []
1388     root = n.get_maybe_exist_named_child ('root')
1389     if root:
1390         ev = musicexp.ChordNameEvent ()
1391         ev.root = musicxml_chordpitch_to_lily (root)
1392         kind = n.get_maybe_exist_named_child ('kind')
1393         if kind:
1394             ev.kind = musicxml_chordkind_to_lily (kind.get_text ())
1395             if not ev.kind:
1396                 return res
1397         bass = n.get_maybe_exist_named_child ('bass')
1398         if bass:
1399             ev.bass = musicxml_chordpitch_to_lily (bass)
1400         inversion = n.get_maybe_exist_named_child ('inversion')
1401         if inversion:
1402             # TODO: Lilypond does not support inversions, does it?
1403
1404             # Mail from Carl Sorensen on lilypond-devel, June 11, 2008:
1405             # 4. LilyPond supports the first inversion in the form of added 
1406             # bass notes.  So the first inversion of C major would be c:/g.   
1407             # To get the second inversion of C major, you would need to do 
1408             # e:6-3-^5 or e:m6-^5.  However, both of these techniques 
1409             # require you to know the chord and calculate either the fifth 
1410             # pitch (for the first inversion) or the third pitch (for the 
1411             # second inversion) so they may not be helpful for musicxml2ly.
1412             inversion_count = string.atoi (inversion.get_text ())
1413             if inversion_count == 1:
1414               # TODO: Calculate the bass note for the inversion...
1415               pass
1416             pass
1417         for deg in n.get_named_children ('degree'):
1418             d = musicexp.ChordModification ()
1419             d.type = deg.get_type ()
1420             d.step = deg.get_value ()
1421             d.alteration = deg.get_alter ()
1422             ev.add_modification (d)
1423         #TODO: convert the user-symbols attribute: 
1424             #major: a triangle, like Unicode 25B3
1425             #minor: -, like Unicode 002D
1426             #augmented: +, like Unicode 002B
1427             #diminished: (degree), like Unicode 00B0
1428             #half-diminished: (o with slash), like Unicode 00F8
1429         if ev and ev.root:
1430             res.append (ev)
1431
1432     return res
1433
1434 def musicxml_figured_bass_note_to_lily (n):
1435     res = musicexp.FiguredBassNote ()
1436     suffix_dict = { 'sharp' : "+", 
1437                     'flat' : "-", 
1438                     'natural' : "!", 
1439                     'double-sharp' : "++", 
1440                     'flat-flat' : "--", 
1441                     'sharp-sharp' : "++", 
1442                     'slash' : "/" }
1443     prefix = n.get_maybe_exist_named_child ('prefix')
1444     if prefix:
1445         res.set_prefix (suffix_dict.get (prefix.get_text (), ""))
1446     fnumber = n.get_maybe_exist_named_child ('figure-number')
1447     if fnumber:
1448         res.set_number (fnumber.get_text ())
1449     suffix = n.get_maybe_exist_named_child ('suffix')
1450     if suffix:
1451         res.set_suffix (suffix_dict.get (suffix.get_text (), ""))
1452     if n.get_maybe_exist_named_child ('extend'):
1453         # TODO: Implement extender lines (unfortunately, in lilypond you have 
1454         #       to use \set useBassFigureExtenders = ##t, which turns them on
1455         #       globally, while MusicXML has a property for each note...
1456         #       I'm not sure there is a proper way to implement this cleanly
1457         #n.extend
1458         pass
1459     return res
1460
1461
1462
1463 def musicxml_figured_bass_to_lily (n):
1464     if not isinstance (n, musicxml.FiguredBass):
1465         return
1466     res = musicexp.FiguredBassEvent ()
1467     for i in n.get_named_children ('figure'):
1468         note = musicxml_figured_bass_note_to_lily (i)
1469         if note:
1470             res.append (note)
1471     dur = n.get_maybe_exist_named_child ('duration')
1472     if dur:
1473         # apply the duration to res
1474         length = Rational(int(dur.get_text()), n._divisions)*Rational(1,4)
1475         res.set_real_duration (length)
1476         duration = rational_to_lily_duration (length)
1477         if duration:
1478             res.set_duration (duration)
1479     if hasattr (n, 'parentheses') and n.parentheses == "yes":
1480         res.set_parentheses (True)
1481     return res
1482
1483 instrument_drumtype_dict = {
1484     'Acoustic Snare Drum': 'acousticsnare',
1485     'Side Stick': 'sidestick',
1486     'Open Triangle': 'opentriangle',
1487     'Mute Triangle': 'mutetriangle',
1488     'Tambourine': 'tambourine',
1489     'Bass Drum': 'bassdrum',
1490 }
1491
1492 def musicxml_note_to_lily_main_event (n):
1493     pitch  = None
1494     duration = None
1495     event = None
1496
1497     mxl_pitch = n.get_maybe_exist_typed_child (musicxml.Pitch)
1498     if mxl_pitch:
1499         pitch = musicxml_pitch_to_lily (mxl_pitch)
1500         event = musicexp.NoteEvent ()
1501         event.pitch = pitch
1502
1503         acc = n.get_maybe_exist_named_child ('accidental')
1504         if acc:
1505             # let's not force accs everywhere. 
1506             event.cautionary = acc.editorial
1507
1508     elif n.get_maybe_exist_typed_child (musicxml.Unpitched):
1509         # Unpitched elements have display-step and can also have
1510         # display-octave.
1511         unpitched = n.get_maybe_exist_typed_child (musicxml.Unpitched)
1512         event = musicexp.NoteEvent ()
1513         event.pitch = musicxml_unpitched_to_lily (unpitched)
1514         
1515     elif n.get_maybe_exist_typed_child (musicxml.Rest):
1516         # rests can have display-octave and display-step, which are
1517         # treated like an ordinary note pitch
1518         rest = n.get_maybe_exist_typed_child (musicxml.Rest)
1519         event = musicexp.RestEvent ()
1520         pitch = musicxml_restdisplay_to_lily (rest)
1521         event.pitch = pitch
1522
1523     elif n.instrument_name:
1524         event = musicexp.NoteEvent ()
1525         drum_type = instrument_drumtype_dict.get (n.instrument_name)
1526         if drum_type:
1527             event.drum_type = drum_type
1528         else:
1529             n.message (_ ("drum %s type unknown, please add to instrument_drumtype_dict") % n.instrument_name)
1530             event.drum_type = 'acousticsnare'
1531
1532     else:
1533         n.message (_ ("cannot find suitable event"))
1534
1535     if event:
1536         event.duration = musicxml_duration_to_lily (n)
1537
1538     return event
1539
1540
1541 ## TODO
1542 class NegativeSkip:
1543     def __init__ (self, here, dest):
1544         self.here = here
1545         self.dest = dest
1546
1547 class LilyPondVoiceBuilder:
1548     def __init__ (self):
1549         self.elements = []
1550         self.pending_dynamics = []
1551         self.end_moment = Rational (0)
1552         self.begin_moment = Rational (0)
1553         self.pending_multibar = Rational (0)
1554         self.ignore_skips = False
1555         self.has_relevant_elements = False
1556
1557     def _insert_multibar (self):
1558         r = musicexp.MultiMeasureRest ()
1559         r.duration = musicexp.Duration()
1560         r.duration.duration_log = 0
1561         r.duration.factor = self.pending_multibar
1562         self.elements.append (r)
1563         self.begin_moment = self.end_moment
1564         self.end_moment = self.begin_moment + self.pending_multibar
1565         self.pending_multibar = Rational (0)
1566         
1567     def add_multibar_rest (self, duration):
1568         self.pending_multibar += duration
1569
1570     def set_duration (self, duration):
1571         self.end_moment = self.begin_moment + duration
1572     def current_duration (self):
1573         return self.end_moment - self.begin_moment
1574         
1575     def add_music (self, music, duration):
1576         assert isinstance (music, musicexp.Music)
1577         if self.pending_multibar > Rational (0):
1578             self._insert_multibar ()
1579
1580         self.has_relevant_elements = True
1581         self.elements.append (music)
1582         self.begin_moment = self.end_moment
1583         self.set_duration (duration)
1584         
1585         # Insert all pending dynamics right after the note/rest:
1586         if isinstance (music, musicexp.ChordEvent) and self.pending_dynamics:
1587             for d in self.pending_dynamics:
1588                 music.append (d)
1589             self.pending_dynamics = []
1590
1591     # Insert some music command that does not affect the position in the measure
1592     def add_command (self, command):
1593         assert isinstance (command, musicexp.Music)
1594         if self.pending_multibar > Rational (0):
1595             self._insert_multibar ()
1596         self.has_relevant_elements = True
1597         self.elements.append (command)
1598     def add_barline (self, barline):
1599         # TODO: Implement merging of default barline and custom bar line
1600         self.add_music (barline, Rational (0))
1601     def add_partial (self, command):
1602         self.ignore_skips = True
1603         self.add_command (command)
1604
1605     def add_dynamics (self, dynamic):
1606         # store the dynamic item(s) until we encounter the next note/rest:
1607         self.pending_dynamics.append (dynamic)
1608
1609     def add_bar_check (self, number):
1610         # re/store has_relevant_elements, so that a barline alone does not
1611         # trigger output for figured bass, chord names
1612         has_relevant = self.has_relevant_elements
1613         b = musicexp.BarLine ()
1614         b.bar_number = number
1615         self.add_barline (b)
1616         self.has_relevant_elements = has_relevant
1617
1618     def jumpto (self, moment):
1619         current_end = self.end_moment + self.pending_multibar
1620         diff = moment - current_end
1621         
1622         if diff < Rational (0):
1623             error_message (_ ('Negative skip %s') % diff)
1624             diff = Rational (0)
1625
1626         if diff > Rational (0) and not (self.ignore_skips and moment == 0):
1627             skip = musicexp.SkipEvent()
1628             duration_factor = 1
1629             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)
1630             duration_dots = 0
1631             if duration_log > 0: # denominator is a power of 2...
1632                 if diff.numerator () == 3:
1633                     duration_log -= 1
1634                     duration_dots = 1
1635                 else:
1636                     duration_factor = Rational (diff.numerator ())
1637             else:
1638                 # for skips of a whole or more, simply use s1*factor
1639                 duration_log = 0
1640                 duration_factor = diff
1641             skip.duration.duration_log = duration_log
1642             skip.duration.factor = duration_factor
1643             skip.duration.dots = duration_dots
1644
1645             evc = musicexp.ChordEvent ()
1646             evc.elements.append (skip)
1647             self.add_music (evc, diff)
1648
1649         if diff > Rational (0) and moment == 0:
1650             self.ignore_skips = False
1651
1652     def last_event_chord (self, starting_at):
1653
1654         value = None
1655
1656         # if the position matches, find the last ChordEvent, do not cross a bar line!
1657         at = len( self.elements ) - 1
1658         while (at >= 0 and
1659                not isinstance (self.elements[at], musicexp.ChordEvent) and
1660                not isinstance (self.elements[at], musicexp.BarLine)):
1661             at -= 1
1662
1663         if (self.elements
1664             and at >= 0
1665             and isinstance (self.elements[at], musicexp.ChordEvent)
1666             and self.begin_moment == starting_at):
1667             value = self.elements[at]
1668         else:
1669             self.jumpto (starting_at)
1670             value = None
1671         return value
1672         
1673     def correct_negative_skip (self, goto):
1674         self.end_moment = goto
1675         self.begin_moment = goto
1676         evc = musicexp.ChordEvent ()
1677         self.elements.append (evc)
1678
1679
1680 class VoiceData:
1681     def __init__ (self):
1682         self.voicename = None
1683         self.voicedata = None
1684         self.ly_voice = None
1685         self.figured_bass = None
1686         self.chordnames = None
1687         self.lyrics_dict = {}
1688         self.lyrics_order = []
1689
1690 def musicxml_step_to_lily (step):
1691     if step:
1692         return (ord (step) - ord ('A') + 7 - 2) % 7
1693     else:
1694         return None
1695
1696 def musicxml_voice_to_lily_voice (voice):
1697     tuplet_events = []
1698     modes_found = {}
1699     lyrics = {}
1700     return_value = VoiceData ()
1701     return_value.voicedata = voice
1702     
1703     # First pitch needed for relative mode (if selected in command-line options)
1704     first_pitch = None
1705
1706     # Needed for melismata detection (ignore lyrics on those notes!):
1707     inside_slur = False
1708     is_tied = False
1709     is_chord = False
1710     is_beamed = False
1711     ignore_lyrics = False
1712
1713     current_staff = None
1714     
1715     pending_figured_bass = []
1716     pending_chordnames = []
1717
1718     # Make sure that the keys in the dict don't get reordered, since
1719     # we need the correct ordering of the lyrics stanzas! By default,
1720     # a dict will reorder its keys
1721     return_value.lyrics_order = voice.get_lyrics_numbers ()
1722     for k in return_value.lyrics_order:
1723         lyrics[k] = []
1724
1725     voice_builder = LilyPondVoiceBuilder ()
1726     figured_bass_builder = LilyPondVoiceBuilder ()
1727     chordnames_builder = LilyPondVoiceBuilder ()
1728
1729     for n in voice._elements:
1730         if n.get_name () == 'forward':
1731             continue
1732         staff = n.get_maybe_exist_named_child ('staff')
1733         if staff:
1734             staff = staff.get_text ()
1735             if current_staff and staff <> current_staff and not n.get_maybe_exist_named_child ('chord'):
1736                 voice_builder.add_command (musicexp.StaffChange (staff))
1737             current_staff = staff
1738
1739         if isinstance (n, musicxml.Partial) and n.partial > 0:
1740             a = musicxml_partial_to_lily (n.partial)
1741             if a:
1742                 voice_builder.add_partial (a)
1743             continue
1744
1745         if isinstance (n, musicxml.Direction):
1746             for a in musicxml_direction_to_lily (n):
1747                 if a.wait_for_note ():
1748                     voice_builder.add_dynamics (a)
1749                 else:
1750                     voice_builder.add_command (a)
1751             continue
1752
1753         if isinstance (n, musicxml.Harmony):
1754             for a in musicxml_harmony_to_lily (n):
1755                 if a.wait_for_note ():
1756                     voice_builder.add_dynamics (a)
1757                 else:
1758                     voice_builder.add_command (a)
1759             for a in musicxml_harmony_to_lily_chordname (n):
1760                 pending_chordnames.append (a)
1761             continue
1762
1763         if isinstance (n, musicxml.FiguredBass):
1764             a = musicxml_figured_bass_to_lily (n)
1765             if a:
1766                 pending_figured_bass.append (a)
1767             continue
1768
1769         is_chord = n.get_maybe_exist_named_child ('chord')
1770         if not is_chord:
1771             try:
1772                 voice_builder.jumpto (n._when)
1773             except NegativeSkip, neg:
1774                 voice_builder.correct_negative_skip (n._when)
1775                 n.message (_ ("Negative skip found: from %s to %s, difference is %s") % (neg.here, neg.dest, neg.dest - neg.here))
1776             
1777         if isinstance (n, musicxml.Attributes):
1778             if n.is_first () and n._measure_position == Rational (0):
1779                 try:
1780                     number = int (n.get_parent ().number)
1781                 except ValueError:
1782                     number = 0
1783                 if number > 0:
1784                     voice_builder.add_bar_check (number)
1785                     figured_bass_builder.add_bar_check (number)
1786                     chordnames_builder.add_bar_check (number)
1787
1788             for a in musicxml_attributes_to_lily (n):
1789                 voice_builder.add_command (a)
1790             continue
1791
1792         if isinstance (n, musicxml.Barline):
1793             barlines = musicxml_barline_to_lily (n)
1794             for a in barlines:
1795                 if isinstance (a, musicexp.BarLine):
1796                     voice_builder.add_barline (a)
1797                 elif isinstance (a, RepeatMarker) or isinstance (a, EndingMarker):
1798                     voice_builder.add_command (a)
1799             continue
1800
1801         if not n.__class__.__name__ == 'Note':
1802             error_message (_ ('unexpected %s; expected %s or %s or %s') % (n, 'Note', 'Attributes', 'Barline'))
1803             continue
1804
1805         rest = n.get_maybe_exist_typed_child (musicxml.Rest)
1806         if (rest
1807             and rest.is_whole_measure ()):
1808
1809             voice_builder.add_multibar_rest (n._duration)
1810             continue
1811
1812         if n.is_first () and n._measure_position == Rational (0):
1813             try: 
1814                 num = int (n.get_parent ().number)
1815             except ValueError:
1816                 num = 0
1817             if num > 0:
1818                 voice_builder.add_bar_check (num)
1819                 figured_bass_builder.add_bar_check (num)
1820                 chordnames_builder.add_bar_check (num)
1821
1822         main_event = musicxml_note_to_lily_main_event (n)
1823         if main_event and not first_pitch:
1824             first_pitch = main_event.pitch
1825         # ignore lyrics for notes inside a slur, tie, chord or beam
1826         ignore_lyrics = inside_slur or is_tied or is_chord or is_beamed
1827
1828         if main_event and hasattr (main_event, 'drum_type') and main_event.drum_type:
1829             modes_found['drummode'] = True
1830
1831         ev_chord = voice_builder.last_event_chord (n._when)
1832         if not ev_chord: 
1833             ev_chord = musicexp.ChordEvent()
1834             voice_builder.add_music (ev_chord, n._duration)
1835
1836         grace = n.get_maybe_exist_typed_child (musicxml.Grace)
1837         if grace:
1838             grace_chord = None
1839             if n.get_maybe_exist_typed_child (musicxml.Chord) and ev_chord.grace_elements:
1840                 grace_chord = ev_chord.grace_elements.get_last_event_chord ()
1841             if not grace_chord:
1842                 grace_chord = musicexp.ChordEvent ()
1843                 ev_chord.append_grace (grace_chord)
1844             if hasattr (grace, 'slash'):
1845                 # TODO: use grace_type = "appoggiatura" for slurred grace notes
1846                 if grace.slash == "yes":
1847                     ev_chord.grace_type = "acciaccatura"
1848             # now that we have inserted the chord into the grace music, insert
1849             # everything into that chord instead of the ev_chord
1850             ev_chord = grace_chord
1851             ev_chord.append (main_event)
1852             ignore_lyrics = True
1853         else:
1854             ev_chord.append (main_event)
1855             # When a note/chord has grace notes (duration==0), the duration of the
1856             # event chord is not yet known, but the event chord was already added
1857             # with duration 0. The following correct this when we hit the real note!
1858             if voice_builder.current_duration () == 0 and n._duration > 0:
1859                 voice_builder.set_duration (n._duration)
1860         
1861         # if we have a figured bass, set its voice builder to the correct position
1862         # and insert the pending figures
1863         if pending_figured_bass:
1864             try:
1865                 figured_bass_builder.jumpto (n._when)
1866             except NegativeSkip, neg:
1867                 pass
1868             for fb in pending_figured_bass:
1869                 # if a duration is given, use that, otherwise the one of the note
1870                 dur = fb.real_duration
1871                 if not dur:
1872                     dur = ev_chord.get_length ()
1873                 if not fb.duration:
1874                     fb.duration = ev_chord.get_duration ()
1875                 figured_bass_builder.add_music (fb, dur)
1876             pending_figured_bass = []
1877         
1878         if pending_chordnames:
1879             try:
1880                 chordnames_builder.jumpto (n._when)
1881             except NegativeSkip, neg:
1882                 pass
1883             for cn in pending_chordnames:
1884                 # Assign the duration of the EventChord
1885                 cn.duration = ev_chord.get_duration ()
1886                 chordnames_builder.add_music (cn, ev_chord.get_length ())
1887             pending_chordnames = []
1888
1889
1890         notations_children = n.get_typed_children (musicxml.Notations)
1891         tuplet_event = None
1892         span_events = []
1893
1894         # The <notation> element can have the following children (+ means implemented, ~ partially, - not):
1895         # +tied | +slur | +tuplet | glissando | slide | 
1896         #    ornaments | technical | articulations | dynamics |
1897         #    +fermata | arpeggiate | non-arpeggiate | 
1898         #    accidental-mark | other-notation
1899         for notations in notations_children:
1900             for tuplet_event in notations.get_tuplets():
1901                 mod = n.get_maybe_exist_typed_child (musicxml.Time_modification)
1902                 frac = (1,1)
1903                 if mod:
1904                     frac = mod.get_fraction ()
1905                 
1906                 tuplet_events.append ((ev_chord, tuplet_event, frac))
1907
1908             slurs = [s for s in notations.get_named_children ('slur')
1909                 if s.get_type () in ('start','stop')]
1910             if slurs:
1911                 if len (slurs) > 1:
1912                     error_message (_ ('cannot have two simultaneous slurs'))
1913                 # record the slur status for the next note in the loop
1914                 if not grace:
1915                     if slurs[0].get_type () == 'start':
1916                         inside_slur = True
1917                     elif slurs[0].get_type () == 'stop':
1918                         inside_slur = False
1919                 lily_ev = musicxml_spanner_to_lily_event (slurs[0])
1920                 ev_chord.append (lily_ev)
1921
1922             if not grace:
1923                 mxl_tie = notations.get_tie ()
1924                 if mxl_tie and mxl_tie.type == 'start':
1925                     ev_chord.append (musicexp.TieEvent ())
1926                     is_tied = True
1927                 else:
1928                     is_tied = False
1929
1930             fermatas = notations.get_named_children ('fermata')
1931             for a in fermatas:
1932                 ev = musicxml_fermata_to_lily_event (a)
1933                 if ev: 
1934                     ev_chord.append (ev)
1935
1936             arpeggiate = notations.get_named_children ('arpeggiate')
1937             for a in arpeggiate:
1938                 ev = musicxml_arpeggiate_to_lily_event (a)
1939                 if ev:
1940                     ev_chord.append (ev)
1941
1942             arpeggiate = notations.get_named_children ('non-arpeggiate')
1943             for a in arpeggiate:
1944                 ev = musicxml_nonarpeggiate_to_lily_event (a)
1945                 if ev:
1946                     ev_chord.append (ev)
1947
1948             glissandos = notations.get_named_children ('glissando')
1949             glissandos += notations.get_named_children ('slide')
1950             for a in glissandos:
1951                 ev = musicxml_spanner_to_lily_event (a)
1952                 if ev:
1953                     ev_chord.append (ev)
1954
1955             # accidental-marks are direct children of <notation>!
1956             for a in notations.get_named_children ('accidental-mark'):
1957                 ev = musicxml_articulation_to_lily_event (a)
1958                 if ev:
1959                     ev_chord.append (ev)
1960
1961             # Articulations can contain the following child elements:
1962             #         accent | strong-accent | staccato | tenuto |
1963             #         detached-legato | staccatissimo | spiccato |
1964             #         scoop | plop | doit | falloff | breath-mark | 
1965             #         caesura | stress | unstress
1966             # Technical can contain the following child elements:
1967             #         up-bow | down-bow | harmonic | open-string |
1968             #         thumb-position | fingering | pluck | double-tongue |
1969             #         triple-tongue | stopped | snap-pizzicato | fret |
1970             #         string | hammer-on | pull-off | bend | tap | heel |
1971             #         toe | fingernails | other-technical
1972             # Ornaments can contain the following child elements:
1973             #         trill-mark | turn | delayed-turn | inverted-turn |
1974             #         shake | wavy-line | mordent | inverted-mordent | 
1975             #         schleifer | tremolo | other-ornament, accidental-mark
1976             ornaments = notations.get_named_children ('ornaments')
1977             ornaments += notations.get_named_children ('articulations')
1978             ornaments += notations.get_named_children ('technical')
1979
1980             for a in ornaments:
1981                 for ch in a.get_all_children ():
1982                     ev = musicxml_articulation_to_lily_event (ch)
1983                     if ev: 
1984                         ev_chord.append (ev)
1985
1986             dynamics = notations.get_named_children ('dynamics')
1987             for a in dynamics:
1988                 for ch in a.get_all_children ():
1989                     ev = musicxml_dynamics_to_lily_event (ch)
1990                     if ev:
1991                         ev_chord.append (ev)
1992
1993
1994         mxl_beams = [b for b in n.get_named_children ('beam')
1995                      if (b.get_type () in ('begin', 'end')
1996                          and b.is_primary ())] 
1997         if mxl_beams and not conversion_settings.ignore_beaming:
1998             beam_ev = musicxml_spanner_to_lily_event (mxl_beams[0])
1999             if beam_ev:
2000                 ev_chord.append (beam_ev)
2001                 if beam_ev.span_direction == -1: # beam and thus melisma starts here
2002                     is_beamed = True
2003                 elif beam_ev.span_direction == 1: # beam and thus melisma ends here
2004                     is_beamed = False
2005             
2006         if tuplet_event:
2007             mod = n.get_maybe_exist_typed_child (musicxml.Time_modification)
2008             frac = (1,1)
2009             if mod:
2010                 frac = mod.get_fraction ()
2011                 
2012             tuplet_events.append ((ev_chord, tuplet_event, frac))
2013
2014         # Extract the lyrics
2015         if not rest and not ignore_lyrics:
2016             note_lyrics_processed = []
2017             note_lyrics_elements = n.get_typed_children (musicxml.Lyric)
2018             for l in note_lyrics_elements:
2019                 if l.get_number () < 0:
2020                     for k in lyrics.keys ():
2021                         lyrics[k].append (l.lyric_to_text ())
2022                         note_lyrics_processed.append (k)
2023                 else:
2024                     lyrics[l.number].append(l.lyric_to_text ())
2025                     note_lyrics_processed.append (l.number)
2026             for lnr in lyrics.keys ():
2027                 if not lnr in note_lyrics_processed:
2028                     lyrics[lnr].append ("\skip4")
2029
2030     ## force trailing mm rests to be written out.   
2031     voice_builder.add_music (musicexp.ChordEvent (), Rational (0))
2032     
2033     ly_voice = group_tuplets (voice_builder.elements, tuplet_events)
2034     ly_voice = group_repeats (ly_voice)
2035
2036     seq_music = musicexp.SequentialMusic ()
2037
2038     if 'drummode' in modes_found.keys ():
2039         ## \key <pitch> barfs in drummode.
2040         ly_voice = [e for e in ly_voice
2041                     if not isinstance(e, musicexp.KeySignatureChange)]
2042     
2043     seq_music.elements = ly_voice
2044     for k in lyrics.keys ():
2045         return_value.lyrics_dict[k] = musicexp.Lyrics ()
2046         return_value.lyrics_dict[k].lyrics_syllables = lyrics[k]
2047     
2048     
2049     if len (modes_found) > 1:
2050        error_message (_ ('cannot simultaneously have more than one mode: %s') % modes_found.keys ())
2051        
2052     if options.relative:
2053         v = musicexp.RelativeMusic ()
2054         v.element = seq_music
2055         v.basepitch = first_pitch
2056         seq_music = v
2057
2058     return_value.ly_voice = seq_music
2059     for mode in modes_found.keys ():
2060         v = musicexp.ModeChangingMusicWrapper()
2061         v.element = seq_music
2062         v.mode = mode
2063         return_value.ly_voice = v
2064     
2065     # create \figuremode { figured bass elements }
2066     if figured_bass_builder.has_relevant_elements:
2067         fbass_music = musicexp.SequentialMusic ()
2068         fbass_music.elements = figured_bass_builder.elements
2069         v = musicexp.ModeChangingMusicWrapper()
2070         v.mode = 'figuremode'
2071         v.element = fbass_music
2072         return_value.figured_bass = v
2073     
2074     # create \chordmode { chords }
2075     if chordnames_builder.has_relevant_elements:
2076         cname_music = musicexp.SequentialMusic ()
2077         cname_music.elements = chordnames_builder.elements
2078         v = musicexp.ModeChangingMusicWrapper()
2079         v.mode = 'chordmode'
2080         v.element = cname_music
2081         return_value.chordnames = v
2082     
2083     return return_value
2084
2085 def musicxml_id_to_lily (id):
2086     digits = ['Zero', 'One', 'Two', 'Three', 'Four', 'Five',
2087               'Six', 'Seven', 'Eight', 'Nine', 'Ten']
2088     
2089     for digit in digits:
2090         d = digits.index (digit)
2091         id = re.sub ('%d' % d, digit, id)
2092
2093     id = re.sub  ('[^a-zA-Z]', 'X', id)
2094     return id
2095
2096 def musicxml_pitch_to_lily (mxl_pitch):
2097     p = musicexp.Pitch ()
2098     p.alteration = mxl_pitch.get_alteration ()
2099     p.step = musicxml_step_to_lily (mxl_pitch.get_step ())
2100     p.octave = mxl_pitch.get_octave () - 4
2101     return p
2102
2103 def musicxml_unpitched_to_lily (mxl_unpitched):
2104     p = None
2105     step = mxl_unpitched.get_step ()
2106     if step:
2107         p = musicexp.Pitch ()
2108         p.step = musicxml_step_to_lily (step)
2109     octave = mxl_unpitched.get_octave ()
2110     if octave and p:
2111         p.octave = octave - 4
2112     return p
2113
2114 def musicxml_restdisplay_to_lily (mxl_rest):
2115     p = None
2116     step = mxl_rest.get_step ()
2117     if step:
2118         p = musicexp.Pitch ()
2119         p.step = musicxml_step_to_lily (step)
2120     octave = mxl_rest.get_octave ()
2121     if octave and p:
2122         p.octave = octave - 4
2123     return p
2124
2125 def voices_in_part (part):
2126     """Return a Name -> Voice dictionary for PART"""
2127     part.interpret ()
2128     part.extract_voices ()
2129     voices = part.get_voices ()
2130     part_info = part.get_staff_attributes ()
2131
2132     return (voices, part_info)
2133
2134 def voices_in_part_in_parts (parts):
2135     """return a Part -> Name -> Voice dictionary"""
2136     return dict([(p.id, voices_in_part (p)) for p in parts])
2137
2138
2139 def get_all_voices (parts):
2140     all_voices = voices_in_part_in_parts (parts)
2141
2142     all_ly_voices = {}
2143     all_ly_staffinfo = {}
2144     for p, (name_voice, staff_info) in all_voices.items ():
2145
2146         part_ly_voices = {}
2147         for n, v in name_voice.items ():
2148             progress (_ ("Converting to LilyPond expressions..."))
2149             # musicxml_voice_to_lily_voice returns (lily_voice, {nr->lyrics, nr->lyrics})
2150             part_ly_voices[n] = musicxml_voice_to_lily_voice (v)
2151
2152         all_ly_voices[p] = part_ly_voices
2153         all_ly_staffinfo[p] = staff_info
2154
2155     return (all_ly_voices, all_ly_staffinfo)
2156
2157
2158 def option_parser ():
2159     p = ly.get_option_parser (usage = _ ("musicxml2ly [OPTION]... FILE.xml"),
2160                              description =
2161 _ ("""Convert MusicXML from FILE.xml to LilyPond input.
2162 If the given filename is -, musicxml2ly reads from the command line.
2163 """), add_help_option=False)
2164
2165     p.add_option("-h", "--help",
2166                  action="help",
2167                  help=_ ("show this help and exit"))
2168
2169     p.version = ('''%prog (LilyPond) @TOPLEVEL_VERSION@\n\n'''
2170 +
2171 _ ("""Copyright (c) 2005--2008 by
2172     Han-Wen Nienhuys <hanwen@xs4all.nl>,
2173     Jan Nieuwenhuizen <janneke@gnu.org> and
2174     Reinhold Kainhofer <reinhold@kainhofer.com>
2175 """
2176 +
2177 """
2178 This program is free software.  It is covered by the GNU General Public
2179 License and you are welcome to change it and/or distribute copies of it
2180 under certain conditions.  Invoke as `%s --warranty' for more
2181 information.""") % 'lilypond')
2182
2183     p.add_option("--version",
2184                  action="version",
2185                  help=_ ("show version number and exit"))
2186
2187     p.add_option ('-v', '--verbose',
2188                   action = "store_true",
2189                   dest = 'verbose',
2190                   help = _ ("be verbose"))
2191
2192     p.add_option ('', '--lxml',
2193                   action = "store_true",
2194                   default = False,
2195                   dest = "use_lxml",
2196                   help = _ ("use lxml.etree; uses less memory and cpu time"))
2197
2198     p.add_option ('-z', '--compressed',
2199                   action = "store_true",
2200                   dest = 'compressed',
2201                   default = False,
2202                   help = _ ("input file is a zip-compressed MusicXML file"))
2203
2204     p.add_option ('-r', '--relative',
2205                   action = "store_true",
2206                   default = True,
2207                   dest = "relative",
2208                   help = _ ("convert pitches in relative mode (default)"))
2209
2210     p.add_option ('-a', '--absolute',
2211                   action = "store_false",
2212                   dest = "relative",
2213                   help = _ ("convert pitches in absolute mode"))
2214
2215     p.add_option ('-l', '--language',
2216                   metavar = _ ("LANG"),
2217                   action = "store",
2218                   help = _ ("use a different language file 'LANG.ly' and corresponding pitch names, e.g. 'deutsch' for deutsch.ly"))
2219
2220     p.add_option ('--nd', '--no-articulation-directions', 
2221                   action = "store_false",
2222                   default = True,
2223                   dest = "convert_directions",
2224                   help = _ ("do not convert directions (^, _ or -) for articulations, dynamics, etc."))
2225
2226     p.add_option ('--no-beaming', 
2227                   action = "store_false",
2228                   default = True,
2229                   dest = "convert_beaming",
2230                   help = _ ("do not convert beaming information, use lilypond's automatic beaming instead"))
2231
2232     p.add_option ('-o', '--output',
2233                   metavar = _ ("FILE"),
2234                   action = "store",
2235                   default = None,
2236                   type = 'string',
2237                   dest = 'output_name',
2238                   help = _ ("set output filename to FILE, stdout if -"))
2239     p.add_option_group ('',
2240                         description = (_ ("Report bugs via")
2241                                      + ''' http://post.gmane.org/post.php'''
2242                                      '''?group=gmane.comp.gnu.lilypond.bugs\n'''))
2243     return p
2244
2245 def music_xml_voice_name_to_lily_name (part_id, name):
2246     str = "Part%sVoice%s" % (part_id, name)
2247     return musicxml_id_to_lily (str) 
2248
2249 def music_xml_lyrics_name_to_lily_name (part_id, name, lyricsnr):
2250     str = "Part%sVoice%sLyrics%s" % (part_id, name, lyricsnr)
2251     return musicxml_id_to_lily (str) 
2252
2253 def music_xml_figuredbass_name_to_lily_name (part_id, voicename):
2254     str = "Part%sVoice%sFiguredBass" % (part_id, voicename)
2255     return musicxml_id_to_lily (str) 
2256
2257 def music_xml_chordnames_name_to_lily_name (part_id, voicename):
2258     str = "Part%sVoice%sChords" % (part_id, voicename)
2259     return musicxml_id_to_lily (str) 
2260
2261 def print_voice_definitions (printer, part_list, voices):
2262     for part in part_list:
2263         part_id = part.id
2264         nv_dict = voices.get (part_id, {})
2265         for (name, voice) in nv_dict.items ():
2266             k = music_xml_voice_name_to_lily_name (part_id, name)
2267             printer.dump ('%s = ' % k)
2268             voice.ly_voice.print_ly (printer)
2269             printer.newline()
2270             if voice.chordnames:
2271                 cnname = music_xml_chordnames_name_to_lily_name (part_id, name)
2272                 printer.dump ('%s = ' % cnname )
2273                 voice.chordnames.print_ly (printer)
2274                 printer.newline()
2275             for l in voice.lyrics_order:
2276                 lname = music_xml_lyrics_name_to_lily_name (part_id, name, l)
2277                 printer.dump ('%s = ' % lname )
2278                 voice.lyrics_dict[l].print_ly (printer)
2279                 printer.newline()
2280             if voice.figured_bass:
2281                 fbname = music_xml_figuredbass_name_to_lily_name (part_id, name)
2282                 printer.dump ('%s = ' % fbname )
2283                 voice.figured_bass.print_ly (printer)
2284                 printer.newline()
2285
2286
2287 def uniq_list (l):
2288     return dict ([(elt,1) for elt in l]).keys ()
2289
2290 # format the information about the staff in the form 
2291 #     [staffid,
2292 #         [
2293 #            [voiceid1, [lyricsid11, lyricsid12,...], figuredbassid1],
2294 #            [voiceid2, [lyricsid21, lyricsid22,...], figuredbassid2],
2295 #            ...
2296 #         ]
2297 #     ]
2298 # raw_voices is of the form [(voicename, lyricsids, havefiguredbass)*]
2299 def format_staff_info (part_id, staff_id, raw_voices):
2300     voices = []
2301     for (v, lyricsids, figured_bass, chordnames) in raw_voices:
2302         voice_name = music_xml_voice_name_to_lily_name (part_id, v)
2303         voice_lyrics = [music_xml_lyrics_name_to_lily_name (part_id, v, l)
2304                    for l in lyricsids]
2305         figured_bass_name = ''
2306         if figured_bass:
2307             figured_bass_name = music_xml_figuredbass_name_to_lily_name (part_id, v)
2308         chordnames_name = ''
2309         if chordnames:
2310             chordnames_name = music_xml_chordnames_name_to_lily_name (part_id, v)
2311         voices.append ([voice_name, voice_lyrics, figured_bass_name, chordnames_name])
2312     return [staff_id, voices]
2313
2314 def update_score_setup (score_structure, part_list, voices):
2315
2316     for part_definition in part_list:
2317         part_id = part_definition.id
2318         nv_dict = voices.get (part_id)
2319         if not nv_dict:
2320             error_message (_ ('unknown part in part-list: %s') % part_id)
2321             continue
2322
2323         staves = reduce (lambda x,y: x+ y,
2324                 [voice.voicedata._staves.keys ()
2325                  for voice in nv_dict.values ()],
2326                 [])
2327         staves_info = []
2328         if len (staves) > 1:
2329             staves_info = []
2330             staves = uniq_list (staves)
2331             staves.sort ()
2332             for s in staves:
2333                 thisstaff_raw_voices = [(voice_name, voice.lyrics_order, voice.figured_bass, voice.chordnames) 
2334                     for (voice_name, voice) in nv_dict.items ()
2335                     if voice.voicedata._start_staff == s]
2336                 staves_info.append (format_staff_info (part_id, s, thisstaff_raw_voices))
2337         else:
2338             thisstaff_raw_voices = [(voice_name, voice.lyrics_order, voice.figured_bass, voice.chordnames) 
2339                 for (voice_name, voice) in nv_dict.items ()]
2340             staves_info.append (format_staff_info (part_id, None, thisstaff_raw_voices))
2341         score_structure.set_part_information (part_id, staves_info)
2342
2343 # Set global values in the \layout block, like auto-beaming etc.
2344 def update_layout_information ():
2345     if not conversion_settings.ignore_beaming and layout_information:
2346         layout_information.set_context_item ('Score', 'autoBeaming = ##f')
2347
2348 def print_ly_preamble (printer, filename):
2349     printer.dump_version ()
2350     printer.print_verbatim ('%% automatically converted from %s\n' % filename)
2351
2352 def print_ly_additional_definitions (printer, filename):
2353     if needed_additional_definitions:
2354         printer.newline ()
2355         printer.print_verbatim ('%% additional definitions required by the score:')
2356         printer.newline ()
2357     for a in set(needed_additional_definitions):
2358         printer.print_verbatim (additional_definitions.get (a, ''))
2359         printer.newline ()
2360     printer.newline ()
2361
2362 # Read in the tree from the given I/O object (either file or string) and 
2363 # demarshall it using the classes from the musicxml.py file
2364 def read_xml (io_object, use_lxml):
2365     if use_lxml:
2366         import lxml.etree
2367         tree = lxml.etree.parse (io_object)
2368         mxl_tree = musicxml.lxml_demarshal_node (tree.getroot ())
2369         return mxl_tree
2370     else:
2371         from xml.dom import minidom, Node
2372         doc = minidom.parse(io_object)
2373         node = doc.documentElement
2374         return musicxml.minidom_demarshal_node (node)
2375     return None
2376
2377
2378 def read_musicxml (filename, compressed, use_lxml):
2379     raw_string = None
2380     if compressed:
2381         if filename == "-":
2382              progress (_ ("Input is compressed, extracting raw MusicXML data from stdin") )
2383              z = zipfile.ZipFile (sys.stdin)
2384         else:
2385             progress (_ ("Input file %s is compressed, extracting raw MusicXML data") % filename)
2386             z = zipfile.ZipFile (filename, "r")
2387         container_xml = z.read ("META-INF/container.xml")
2388         if not container_xml:
2389             return None
2390         container = read_xml (StringIO.StringIO (container_xml), use_lxml)
2391         if not container:
2392             return None
2393         rootfiles = container.get_maybe_exist_named_child ('rootfiles')
2394         if not rootfiles:
2395             return None
2396         rootfile_list = rootfiles.get_named_children ('rootfile')
2397         mxml_file = None
2398         if len (rootfile_list) > 0:
2399             mxml_file = getattr (rootfile_list[0], 'full-path', None)
2400         if mxml_file:
2401             raw_string = z.read (mxml_file)
2402
2403     if raw_string:
2404         io_object = StringIO.StringIO (raw_string)
2405     elif filename == "-":
2406         io_object = sys.stdin
2407     else:
2408         io_object = filename
2409
2410     return read_xml (io_object, use_lxml)
2411
2412
2413 def convert (filename, options):
2414     if filename == "-":
2415         progress (_ ("Reading MusicXML from Standard input ...") )
2416     else:
2417         progress (_ ("Reading MusicXML from %s ...") % filename)
2418
2419     tree = read_musicxml (filename, options.compressed, options.use_lxml)
2420     score_information = extract_score_information (tree)
2421     paper_information = extract_paper_information (tree)
2422
2423     parts = tree.get_typed_children (musicxml.Part)
2424     (voices, staff_info) = get_all_voices (parts)
2425
2426     score_structure = None
2427     mxl_pl = tree.get_maybe_exist_typed_child (musicxml.Part_list)
2428     if mxl_pl:
2429         score_structure = extract_score_structure (mxl_pl, staff_info)
2430         part_list = mxl_pl.get_named_children ("score-part")
2431
2432     # score information is contained in the <work>, <identification> or <movement-title> tags
2433     update_score_setup (score_structure, part_list, voices)
2434     # After the conversion, update the list of settings for the \layout block
2435     update_layout_information ()
2436
2437     if not options.output_name:
2438         options.output_name = os.path.basename (filename) 
2439         options.output_name = os.path.splitext (options.output_name)[0]
2440     elif re.match (".*\.ly", options.output_name):
2441         options.output_name = os.path.splitext (options.output_name)[0]
2442
2443
2444     #defs_ly_name = options.output_name + '-defs.ly'
2445     if (options.output_name == "-"):
2446       output_ly_name = 'Standard output'
2447     else:
2448       output_ly_name = options.output_name + '.ly'
2449
2450     progress (_ ("Output to `%s'") % output_ly_name)
2451     printer = musicexp.Output_printer()
2452     #progress (_ ("Output to `%s'") % defs_ly_name)
2453     if (options.output_name == "-"):
2454       printer.set_file (codecs.getwriter ("utf-8")(sys.stdout))
2455     else:
2456       printer.set_file (codecs.open (output_ly_name, 'wb', encoding='utf-8'))
2457     print_ly_preamble (printer, filename)
2458     print_ly_additional_definitions (printer, filename)
2459     if score_information:
2460         score_information.print_ly (printer)
2461     if paper_information:
2462         paper_information.print_ly (printer)
2463     if layout_information:
2464         layout_information.print_ly (printer)
2465     print_voice_definitions (printer, part_list, voices)
2466     
2467     printer.newline ()
2468     printer.dump ("% The score definition")
2469     printer.newline ()
2470     score_structure.print_ly (printer)
2471     printer.newline ()
2472
2473     return voices
2474
2475 def get_existing_filename_with_extension (filename, ext):
2476     if os.path.exists (filename):
2477         return filename
2478     newfilename = filename + "." + ext
2479     if os.path.exists (newfilename):
2480         return newfilename;
2481     newfilename = filename + ext
2482     if os.path.exists (newfilename):
2483         return newfilename;
2484     return ''
2485
2486 def main ():
2487     opt_parser = option_parser()
2488
2489     global options
2490     (options, args) = opt_parser.parse_args ()
2491     if not args:
2492         opt_parser.print_usage()
2493         sys.exit (2)
2494
2495     if options.language:
2496         musicexp.set_pitch_language (options.language)
2497         needed_additional_definitions.append (options.language)
2498         additional_definitions[options.language] = "\\include \"%s.ly\"\n" % options.language
2499     conversion_settings.ignore_beaming = not options.convert_beaming
2500
2501     # Allow the user to leave out the .xml or xml on the filename
2502     if args[0]=="-": # Read from stdin
2503         filename="-"
2504     else:
2505         filename = get_existing_filename_with_extension (args[0], "xml")
2506         if not filename:
2507             filename = get_existing_filename_with_extension (args[0], "mxl")
2508             options.compressed = True
2509     if filename and (filename == "-" or os.path.exists (filename)):
2510         voices = convert (filename, options)
2511     else:
2512         progress (_ ("Unable to find input file %s") % args[0])
2513
2514 if __name__ == '__main__':
2515     main()