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