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