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