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