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