]> git.donarmstrong.com Git - lilypond.git/blob - scripts/musicxml2ly.py
4256ea0504e52a4539db7cd4814ce52d94e7b1eb
[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         if options.convert_rest_positions:
1841             pitch = musicxml_restdisplay_to_lily (rest)
1842             event.pitch = pitch
1843
1844     elif n.instrument_name:
1845         event = musicexp.NoteEvent ()
1846         drum_type = instrument_drumtype_dict.get (n.instrument_name)
1847         if drum_type:
1848             event.drum_type = drum_type
1849         else:
1850             n.message (_ ("drum %s type unknown, please add to instrument_drumtype_dict") % n.instrument_name)
1851             event.drum_type = 'acousticsnare'
1852
1853     else:
1854         n.message (_ ("cannot find suitable event"))
1855
1856     if event:
1857         event.duration = musicxml_duration_to_lily (n)
1858
1859     noteheads = n.get_named_children ('notehead')
1860     for nh in noteheads:
1861         styles = musicxml_notehead_to_lily (nh)
1862         for s in styles:
1863             event.add_associated_event (s)
1864
1865     return event
1866
1867 def musicxml_lyrics_to_text (lyrics):
1868     # TODO: Implement text styles for lyrics syllables
1869     continued = False
1870     extended = False
1871     text = ''
1872     for e in lyrics.get_all_children ():
1873         if isinstance (e, musicxml.Syllabic):
1874             continued = e.continued ()
1875         elif isinstance (e, musicxml.Text):
1876             # We need to convert soft hyphens to -, otherwise the ascii codec as well
1877             # as lilypond will barf on that character
1878             text += string.replace( e.get_text(), u'\xad', '-' )
1879         elif isinstance (e, musicxml.Elision):
1880             if text:
1881                 text += " "
1882             continued = False
1883             extended = False
1884         elif isinstance (e, musicxml.Extend):
1885             if text:
1886                 text += " "
1887             extended = True
1888
1889     if text == "-" and continued:
1890         return "--"
1891     elif text == "_" and extended:
1892         return "__"
1893     elif continued and text:
1894         return musicxml.escape_ly_output_string (text) + " --"
1895     elif continued:
1896         return "--"
1897     elif extended and text:
1898         return musicxml.escape_ly_output_string (text) + " __"
1899     elif extended:
1900         return "__"
1901     elif text:
1902         return musicxml.escape_ly_output_string (text)
1903     else:
1904         return ""
1905
1906 ## TODO
1907 class NegativeSkip:
1908     def __init__ (self, here, dest):
1909         self.here = here
1910         self.dest = dest
1911
1912 class LilyPondVoiceBuilder:
1913     def __init__ (self):
1914         self.elements = []
1915         self.pending_dynamics = []
1916         self.end_moment = Rational (0)
1917         self.begin_moment = Rational (0)
1918         self.pending_multibar = Rational (0)
1919         self.ignore_skips = False
1920         self.has_relevant_elements = False
1921         self.measure_length = Rational (4, 4)
1922
1923     def _insert_multibar (self):
1924         layout_information.set_context_item ('Score', 'skipBars = ##t')
1925         r = musicexp.MultiMeasureRest ()
1926         lenfrac = self.measure_length
1927         r.duration = rational_to_lily_duration (lenfrac)
1928         r.duration.factor *= self.pending_multibar / lenfrac
1929         self.elements.append (r)
1930         self.begin_moment = self.end_moment
1931         self.end_moment = self.begin_moment + self.pending_multibar
1932         self.pending_multibar = Rational (0)
1933
1934     def set_measure_length (self, mlen):
1935         if (mlen != self.measure_length) and self.pending_multibar:
1936             self._insert_multibar ()
1937         self.measure_length = mlen
1938
1939     def add_multibar_rest (self, duration):
1940         self.pending_multibar += duration
1941
1942     def set_duration (self, duration):
1943         self.end_moment = self.begin_moment + duration
1944     def current_duration (self):
1945         return self.end_moment - self.begin_moment
1946         
1947     def add_music (self, music, duration):
1948         assert isinstance (music, musicexp.Music)
1949         if self.pending_multibar > Rational (0):
1950             self._insert_multibar ()
1951
1952         self.has_relevant_elements = True
1953         self.elements.append (music)
1954         self.begin_moment = self.end_moment
1955         self.set_duration (duration)
1956         
1957         # Insert all pending dynamics right after the note/rest:
1958         if isinstance (music, musicexp.ChordEvent) and self.pending_dynamics:
1959             for d in self.pending_dynamics:
1960                 music.append (d)
1961             self.pending_dynamics = []
1962
1963     # Insert some music command that does not affect the position in the measure
1964     def add_command (self, command):
1965         assert isinstance (command, musicexp.Music)
1966         if self.pending_multibar > Rational (0):
1967             self._insert_multibar ()
1968         self.has_relevant_elements = True
1969         self.elements.append (command)
1970     def add_barline (self, barline):
1971         # TODO: Implement merging of default barline and custom bar line
1972         self.add_music (barline, Rational (0))
1973     def add_partial (self, command):
1974         self.ignore_skips = True
1975         self.add_command (command)
1976
1977     def add_dynamics (self, dynamic):
1978         # store the dynamic item(s) until we encounter the next note/rest:
1979         self.pending_dynamics.append (dynamic)
1980
1981     def add_bar_check (self, number):
1982         # re/store has_relevant_elements, so that a barline alone does not
1983         # trigger output for figured bass, chord names
1984         has_relevant = self.has_relevant_elements
1985         b = musicexp.BarLine ()
1986         b.bar_number = number
1987         self.add_barline (b)
1988         self.has_relevant_elements = has_relevant
1989
1990     def jumpto (self, moment):
1991         current_end = self.end_moment + self.pending_multibar
1992         diff = moment - current_end
1993         
1994         if diff < Rational (0):
1995             error_message (_ ('Negative skip %s (from position %s to %s)') % 
1996                              (diff, current_end, moment))
1997             diff = Rational (0)
1998
1999         if diff > Rational (0) and not (self.ignore_skips and moment == 0):
2000             skip = musicexp.SkipEvent()
2001             duration_factor = 1
2002             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)
2003             duration_dots = 0
2004             # TODO: Use the time signature for skips, too. Problem: The skip 
2005             #       might not start at a measure boundary!
2006             if duration_log > 0: # denominator is a power of 2...
2007                 if diff.numerator () == 3:
2008                     duration_log -= 1
2009                     duration_dots = 1
2010                 else:
2011                     duration_factor = Rational (diff.numerator ())
2012             else:
2013                 # for skips of a whole or more, simply use s1*factor
2014                 duration_log = 0
2015                 duration_factor = diff
2016             skip.duration.duration_log = duration_log
2017             skip.duration.factor = duration_factor
2018             skip.duration.dots = duration_dots
2019
2020             evc = musicexp.ChordEvent ()
2021             evc.elements.append (skip)
2022             self.add_music (evc, diff)
2023
2024         if diff > Rational (0) and moment == 0:
2025             self.ignore_skips = False
2026
2027     def last_event_chord (self, starting_at):
2028
2029         value = None
2030
2031         # if the position matches, find the last ChordEvent, do not cross a bar line!
2032         at = len( self.elements ) - 1
2033         while (at >= 0 and
2034                not isinstance (self.elements[at], musicexp.ChordEvent) and
2035                not isinstance (self.elements[at], musicexp.BarLine)):
2036             at -= 1
2037
2038         if (self.elements
2039             and at >= 0
2040             and isinstance (self.elements[at], musicexp.ChordEvent)
2041             and self.begin_moment == starting_at):
2042             value = self.elements[at]
2043         else:
2044             self.jumpto (starting_at)
2045             value = None
2046         return value
2047         
2048     def correct_negative_skip (self, goto):
2049         self.end_moment = goto
2050         self.begin_moment = goto
2051         evc = musicexp.ChordEvent ()
2052         self.elements.append (evc)
2053
2054
2055 class VoiceData:
2056     def __init__ (self):
2057         self.voicename = None
2058         self.voicedata = None
2059         self.ly_voice = None
2060         self.figured_bass = None
2061         self.chordnames = None
2062         self.lyrics_dict = {}
2063         self.lyrics_order = []
2064
2065 def musicxml_step_to_lily (step):
2066     if step:
2067         return (ord (step) - ord ('A') + 7 - 2) % 7
2068     else:
2069         return None
2070
2071 def measure_length_from_attributes (attr, current_measure_length):
2072     len = attr.get_measure_length ()
2073     if not len:
2074         len = current_measure_length
2075     return len
2076
2077 def musicxml_voice_to_lily_voice (voice):
2078     tuplet_events = []
2079     modes_found = {}
2080     lyrics = {}
2081     return_value = VoiceData ()
2082     return_value.voicedata = voice
2083     
2084     # First pitch needed for relative mode (if selected in command-line options)
2085     first_pitch = None
2086
2087     # Needed for melismata detection (ignore lyrics on those notes!):
2088     inside_slur = False
2089     is_tied = False
2090     is_chord = False
2091     is_beamed = False
2092     ignore_lyrics = False
2093
2094     current_staff = None
2095     
2096     pending_figured_bass = []
2097     pending_chordnames = []
2098
2099     # Make sure that the keys in the dict don't get reordered, since
2100     # we need the correct ordering of the lyrics stanzas! By default,
2101     # a dict will reorder its keys
2102     return_value.lyrics_order = voice.get_lyrics_numbers ()
2103     for k in return_value.lyrics_order:
2104         lyrics[k] = []
2105
2106     voice_builder = LilyPondVoiceBuilder ()
2107     figured_bass_builder = LilyPondVoiceBuilder ()
2108     chordnames_builder = LilyPondVoiceBuilder ()
2109     current_measure_length = Rational (4, 4)
2110     voice_builder.set_measure_length (current_measure_length)
2111
2112     for n in voice._elements:
2113         if n.get_name () == 'forward':
2114             continue
2115         staff = n.get_maybe_exist_named_child ('staff')
2116         if staff:
2117             staff = staff.get_text ()
2118             if current_staff and staff <> current_staff and not n.get_maybe_exist_named_child ('chord'):
2119                 voice_builder.add_command (musicexp.StaffChange (staff))
2120             current_staff = staff
2121
2122         if isinstance (n, musicxml.Partial) and n.partial > 0:
2123             a = musicxml_partial_to_lily (n.partial)
2124             if a:
2125                 voice_builder.add_partial (a)
2126             continue
2127
2128         is_chord = n.get_maybe_exist_named_child ('chord')
2129         is_after_grace = (isinstance (n, musicxml.Note) and n.is_after_grace ());
2130         if not is_chord and not is_after_grace:
2131             try:
2132                 voice_builder.jumpto (n._when)
2133             except NegativeSkip, neg:
2134                 voice_builder.correct_negative_skip (n._when)
2135                 n.message (_ ("Negative skip found: from %s to %s, difference is %s") % (neg.here, neg.dest, neg.dest - neg.here))
2136
2137         if isinstance (n, musicxml.Barline):
2138             barlines = musicxml_barline_to_lily (n)
2139             for a in barlines:
2140                 if isinstance (a, musicexp.BarLine):
2141                     voice_builder.add_barline (a)
2142                 elif isinstance (a, RepeatMarker) or isinstance (a, EndingMarker):
2143                     voice_builder.add_command (a)
2144             continue
2145
2146         # Continue any multimeasure-rests before trying to add bar checks!
2147         # Don't handle new MM rests yet, because for them we want bar checks!
2148         rest = n.get_maybe_exist_typed_child (musicxml.Rest)
2149         if (rest and rest.is_whole_measure ()
2150                  and voice_builder.pending_multibar > Rational (0)):
2151             voice_builder.add_multibar_rest (n._duration)
2152             continue
2153
2154
2155         # print a bar check at the beginning of each measure!
2156         if n.is_first () and n._measure_position == Rational (0) and n != voice._elements[0]:
2157             try:
2158                 num = int (n.get_parent ().number)
2159             except ValueError:
2160                 num = 0
2161             if num > 0:
2162                 voice_builder.add_bar_check (num)
2163                 figured_bass_builder.add_bar_check (num)
2164                 chordnames_builder.add_bar_check (num)
2165
2166         # Start any new multimeasure rests
2167         if (rest and rest.is_whole_measure ()):
2168             voice_builder.add_multibar_rest (n._duration)
2169             continue
2170
2171
2172         if isinstance (n, musicxml.Direction):
2173             for a in musicxml_direction_to_lily (n):
2174                 if a.wait_for_note ():
2175                     voice_builder.add_dynamics (a)
2176                 else:
2177                     voice_builder.add_command (a)
2178             continue
2179
2180         if isinstance (n, musicxml.Harmony):
2181             for a in musicxml_harmony_to_lily (n):
2182                 if a.wait_for_note ():
2183                     voice_builder.add_dynamics (a)
2184                 else:
2185                     voice_builder.add_command (a)
2186             for a in musicxml_harmony_to_lily_chordname (n):
2187                 pending_chordnames.append (a)
2188             continue
2189
2190         if isinstance (n, musicxml.FiguredBass):
2191             a = musicxml_figured_bass_to_lily (n)
2192             if a:
2193                 pending_figured_bass.append (a)
2194             continue
2195
2196         if isinstance (n, musicxml.Attributes):
2197             for a in musicxml_attributes_to_lily (n):
2198                 voice_builder.add_command (a)
2199             measure_length = measure_length_from_attributes (n, current_measure_length)
2200             if current_measure_length != measure_length:
2201                 current_measure_length = measure_length
2202                 voice_builder.set_measure_length (current_measure_length)
2203             continue
2204
2205         if not n.__class__.__name__ == 'Note':
2206             n.message (_ ('unexpected %s; expected %s or %s or %s') % (n, 'Note', 'Attributes', 'Barline'))
2207             continue
2208         
2209         main_event = musicxml_note_to_lily_main_event (n)
2210         if main_event and not first_pitch:
2211             first_pitch = main_event.pitch
2212         # ignore lyrics for notes inside a slur, tie, chord or beam
2213         ignore_lyrics = inside_slur or is_tied or is_chord or is_beamed
2214
2215         if main_event and hasattr (main_event, 'drum_type') and main_event.drum_type:
2216             modes_found['drummode'] = True
2217
2218         ev_chord = voice_builder.last_event_chord (n._when)
2219         if not ev_chord: 
2220             ev_chord = musicexp.ChordEvent()
2221             voice_builder.add_music (ev_chord, n._duration)
2222
2223         # For grace notes:
2224         grace = n.get_maybe_exist_typed_child (musicxml.Grace)
2225         if n.is_grace ():
2226             is_after_grace = ev_chord.has_elements () or n.is_after_grace ();
2227             is_chord = n.get_maybe_exist_typed_child (musicxml.Chord)
2228
2229             grace_chord = None
2230
2231             # after-graces and other graces use different lists; Depending on
2232             # whether we have a chord or not, obtain either a new ChordEvent or 
2233             # the previous one to create a chord
2234             if is_after_grace:
2235                 if ev_chord.after_grace_elements and n.get_maybe_exist_typed_child (musicxml.Chord):
2236                     grace_chord = ev_chord.after_grace_elements.get_last_event_chord ()
2237                 if not grace_chord:
2238                     grace_chord = musicexp.ChordEvent ()
2239                     ev_chord.append_after_grace (grace_chord)
2240             elif n.is_grace ():
2241                 if ev_chord.grace_elements and n.get_maybe_exist_typed_child (musicxml.Chord):
2242                     grace_chord = ev_chord.grace_elements.get_last_event_chord ()
2243                 if not grace_chord:
2244                     grace_chord = musicexp.ChordEvent ()
2245                     ev_chord.append_grace (grace_chord)
2246
2247             if hasattr (grace, 'slash') and not is_after_grace:
2248                 # TODO: use grace_type = "appoggiatura" for slurred grace notes
2249                 if grace.slash == "yes":
2250                     ev_chord.grace_type = "acciaccatura"
2251             # now that we have inserted the chord into the grace music, insert
2252             # everything into that chord instead of the ev_chord
2253             ev_chord = grace_chord
2254             ev_chord.append (main_event)
2255             ignore_lyrics = True
2256         else:
2257             ev_chord.append (main_event)
2258             # When a note/chord has grace notes (duration==0), the duration of the
2259             # event chord is not yet known, but the event chord was already added
2260             # with duration 0. The following correct this when we hit the real note!
2261             if voice_builder.current_duration () == 0 and n._duration > 0:
2262                 voice_builder.set_duration (n._duration)
2263         
2264         # if we have a figured bass, set its voice builder to the correct position
2265         # and insert the pending figures
2266         if pending_figured_bass:
2267             try:
2268                 figured_bass_builder.jumpto (n._when)
2269             except NegativeSkip, neg:
2270                 pass
2271             for fb in pending_figured_bass:
2272                 # if a duration is given, use that, otherwise the one of the note
2273                 dur = fb.real_duration
2274                 if not dur:
2275                     dur = ev_chord.get_length ()
2276                 if not fb.duration:
2277                     fb.duration = ev_chord.get_duration ()
2278                 figured_bass_builder.add_music (fb, dur)
2279             pending_figured_bass = []
2280         
2281         if pending_chordnames:
2282             try:
2283                 chordnames_builder.jumpto (n._when)
2284             except NegativeSkip, neg:
2285                 pass
2286             for cn in pending_chordnames:
2287                 # Assign the duration of the EventChord
2288                 cn.duration = ev_chord.get_duration ()
2289                 chordnames_builder.add_music (cn, ev_chord.get_length ())
2290             pending_chordnames = []
2291
2292         notations_children = n.get_typed_children (musicxml.Notations)
2293         tuplet_event = None
2294         span_events = []
2295
2296         # The <notation> element can have the following children (+ means implemented, ~ partially, - not):
2297         # +tied | +slur | +tuplet | glissando | slide | 
2298         #    ornaments | technical | articulations | dynamics |
2299         #    +fermata | arpeggiate | non-arpeggiate | 
2300         #    accidental-mark | other-notation
2301         for notations in notations_children:
2302             for tuplet_event in notations.get_tuplets():
2303                 time_mod = n.get_maybe_exist_typed_child (musicxml.Time_modification)
2304                 tuplet_events.append ((ev_chord, tuplet_event, time_mod))
2305
2306             # First, close all open slurs, only then start any new slur
2307             # TODO: Record the number of the open slur to dtermine the correct
2308             #       closing slur!
2309             endslurs = [s for s in notations.get_named_children ('slur')
2310                 if s.get_type () in ('stop')]
2311             if endslurs and not inside_slur:
2312                 endslurs[0].message (_ ('Encountered closing slur, but no slur is open'))
2313             elif endslurs:
2314                 if len (endslurs) > 1:
2315                     endslurs[0].message (_ ('Cannot have two simultaneous (closing) slurs'))
2316                 # record the slur status for the next note in the loop
2317                 if not grace:
2318                     inside_slur = False
2319                 lily_ev = musicxml_spanner_to_lily_event (endslurs[0])
2320                 ev_chord.append (lily_ev)
2321
2322             startslurs = [s for s in notations.get_named_children ('slur')
2323                 if s.get_type () in ('start')]
2324             if startslurs and inside_slur:
2325                 startslurs[0].message (_ ('Cannot have a slur inside another slur'))
2326             elif startslurs:
2327                 if len (startslurs) > 1:
2328                     startslurs[0].message (_ ('Cannot have two simultaneous slurs'))
2329                 # record the slur status for the next note in the loop
2330                 if not grace:
2331                     inside_slur = True
2332                 lily_ev = musicxml_spanner_to_lily_event (startslurs[0])
2333                 ev_chord.append (lily_ev)
2334
2335
2336             if not grace:
2337                 mxl_tie = notations.get_tie ()
2338                 if mxl_tie and mxl_tie.type == 'start':
2339                     ev_chord.append (musicexp.TieEvent ())
2340                     is_tied = True
2341                 else:
2342                     is_tied = False
2343
2344             fermatas = notations.get_named_children ('fermata')
2345             for a in fermatas:
2346                 ev = musicxml_fermata_to_lily_event (a)
2347                 if ev: 
2348                     ev_chord.append (ev)
2349
2350             arpeggiate = notations.get_named_children ('arpeggiate')
2351             for a in arpeggiate:
2352                 ev = musicxml_arpeggiate_to_lily_event (a)
2353                 if ev:
2354                     ev_chord.append (ev)
2355
2356             arpeggiate = notations.get_named_children ('non-arpeggiate')
2357             for a in arpeggiate:
2358                 ev = musicxml_nonarpeggiate_to_lily_event (a)
2359                 if ev:
2360                     ev_chord.append (ev)
2361
2362             glissandos = notations.get_named_children ('glissando')
2363             glissandos += notations.get_named_children ('slide')
2364             for a in glissandos:
2365                 ev = musicxml_spanner_to_lily_event (a)
2366                 if ev:
2367                     ev_chord.append (ev)
2368
2369             # accidental-marks are direct children of <notation>!
2370             for a in notations.get_named_children ('accidental-mark'):
2371                 ev = musicxml_articulation_to_lily_event (a)
2372                 if ev:
2373                     ev_chord.append (ev)
2374
2375             # Articulations can contain the following child elements:
2376             #         accent | strong-accent | staccato | tenuto |
2377             #         detached-legato | staccatissimo | spiccato |
2378             #         scoop | plop | doit | falloff | breath-mark | 
2379             #         caesura | stress | unstress
2380             # Technical can contain the following child elements:
2381             #         up-bow | down-bow | harmonic | open-string |
2382             #         thumb-position | fingering | pluck | double-tongue |
2383             #         triple-tongue | stopped | snap-pizzicato | fret |
2384             #         string | hammer-on | pull-off | bend | tap | heel |
2385             #         toe | fingernails | other-technical
2386             # Ornaments can contain the following child elements:
2387             #         trill-mark | turn | delayed-turn | inverted-turn |
2388             #         shake | wavy-line | mordent | inverted-mordent | 
2389             #         schleifer | tremolo | other-ornament, accidental-mark
2390             ornaments = notations.get_named_children ('ornaments')
2391             ornaments += notations.get_named_children ('articulations')
2392             ornaments += notations.get_named_children ('technical')
2393
2394             for a in ornaments:
2395                 for ch in a.get_all_children ():
2396                     ev = musicxml_articulation_to_lily_event (ch)
2397                     if ev:
2398                         ev_chord.append (ev)
2399
2400             dynamics = notations.get_named_children ('dynamics')
2401             for a in dynamics:
2402                 for ch in a.get_all_children ():
2403                     ev = musicxml_dynamics_to_lily_event (ch)
2404                     if ev:
2405                         ev_chord.append (ev)
2406
2407
2408         mxl_beams = [b for b in n.get_named_children ('beam')
2409                      if (b.get_type () in ('begin', 'end')
2410                          and b.is_primary ())] 
2411         if mxl_beams and not conversion_settings.ignore_beaming:
2412             beam_ev = musicxml_spanner_to_lily_event (mxl_beams[0])
2413             if beam_ev:
2414                 ev_chord.append (beam_ev)
2415                 if beam_ev.span_direction == -1: # beam and thus melisma starts here
2416                     is_beamed = True
2417                 elif beam_ev.span_direction == 1: # beam and thus melisma ends here
2418                     is_beamed = False
2419
2420         # Extract the lyrics
2421         if not rest and not ignore_lyrics:
2422             note_lyrics_processed = []
2423             note_lyrics_elements = n.get_typed_children (musicxml.Lyric)
2424             for l in note_lyrics_elements:
2425                 if l.get_number () < 0:
2426                     for k in lyrics.keys ():
2427                         lyrics[k].append (musicxml_lyrics_to_text (l))
2428                         note_lyrics_processed.append (k)
2429                 else:
2430                     lyrics[l.number].append(musicxml_lyrics_to_text (l))
2431                     note_lyrics_processed.append (l.number)
2432             for lnr in lyrics.keys ():
2433                 if not lnr in note_lyrics_processed:
2434                     lyrics[lnr].append ("\skip4")
2435
2436     ## force trailing mm rests to be written out.   
2437     voice_builder.add_music (musicexp.ChordEvent (), Rational (0))
2438     
2439     ly_voice = group_tuplets (voice_builder.elements, tuplet_events)
2440     ly_voice = group_repeats (ly_voice)
2441
2442     seq_music = musicexp.SequentialMusic ()
2443
2444     if 'drummode' in modes_found.keys ():
2445         ## \key <pitch> barfs in drummode.
2446         ly_voice = [e for e in ly_voice
2447                     if not isinstance(e, musicexp.KeySignatureChange)]
2448     
2449     seq_music.elements = ly_voice
2450     for k in lyrics.keys ():
2451         return_value.lyrics_dict[k] = musicexp.Lyrics ()
2452         return_value.lyrics_dict[k].lyrics_syllables = lyrics[k]
2453     
2454     
2455     if len (modes_found) > 1:
2456        error_message (_ ('cannot simultaneously have more than one mode: %s') % modes_found.keys ())
2457        
2458     if options.relative:
2459         v = musicexp.RelativeMusic ()
2460         v.element = seq_music
2461         v.basepitch = first_pitch
2462         seq_music = v
2463
2464     return_value.ly_voice = seq_music
2465     for mode in modes_found.keys ():
2466         v = musicexp.ModeChangingMusicWrapper()
2467         v.element = seq_music
2468         v.mode = mode
2469         return_value.ly_voice = v
2470     
2471     # create \figuremode { figured bass elements }
2472     if figured_bass_builder.has_relevant_elements:
2473         fbass_music = musicexp.SequentialMusic ()
2474         fbass_music.elements = figured_bass_builder.elements
2475         v = musicexp.ModeChangingMusicWrapper()
2476         v.mode = 'figuremode'
2477         v.element = fbass_music
2478         return_value.figured_bass = v
2479     
2480     # create \chordmode { chords }
2481     if chordnames_builder.has_relevant_elements:
2482         cname_music = musicexp.SequentialMusic ()
2483         cname_music.elements = chordnames_builder.elements
2484         v = musicexp.ModeChangingMusicWrapper()
2485         v.mode = 'chordmode'
2486         v.element = cname_music
2487         return_value.chordnames = v
2488     
2489     return return_value
2490
2491 def musicxml_id_to_lily (id):
2492     digits = ['Zero', 'One', 'Two', 'Three', 'Four', 'Five',
2493               'Six', 'Seven', 'Eight', 'Nine', 'Ten']
2494     
2495     for digit in digits:
2496         d = digits.index (digit)
2497         id = re.sub ('%d' % d, digit, id)
2498
2499     id = re.sub  ('[^a-zA-Z]', 'X', id)
2500     return id
2501
2502 def musicxml_pitch_to_lily (mxl_pitch):
2503     p = musicexp.Pitch ()
2504     p.alteration = mxl_pitch.get_alteration ()
2505     p.step = musicxml_step_to_lily (mxl_pitch.get_step ())
2506     p.octave = mxl_pitch.get_octave () - 4
2507     return p
2508
2509 def musicxml_unpitched_to_lily (mxl_unpitched):
2510     p = None
2511     step = mxl_unpitched.get_step ()
2512     if step:
2513         p = musicexp.Pitch ()
2514         p.step = musicxml_step_to_lily (step)
2515     octave = mxl_unpitched.get_octave ()
2516     if octave and p:
2517         p.octave = octave - 4
2518     return p
2519
2520 def musicxml_restdisplay_to_lily (mxl_rest):
2521     p = None
2522     step = mxl_rest.get_step ()
2523     if step:
2524         p = musicexp.Pitch ()
2525         p.step = musicxml_step_to_lily (step)
2526     octave = mxl_rest.get_octave ()
2527     if octave and p:
2528         p.octave = octave - 4
2529     return p
2530
2531 def voices_in_part (part):
2532     """Return a Name -> Voice dictionary for PART"""
2533     part.interpret ()
2534     part.extract_voices ()
2535     voices = part.get_voices ()
2536     part_info = part.get_staff_attributes ()
2537
2538     return (voices, part_info)
2539
2540 def voices_in_part_in_parts (parts):
2541     """return a Part -> Name -> Voice dictionary"""
2542     return dict([(p.id, voices_in_part (p)) for p in parts])
2543
2544
2545 def get_all_voices (parts):
2546     all_voices = voices_in_part_in_parts (parts)
2547
2548     all_ly_voices = {}
2549     all_ly_staffinfo = {}
2550     for p, (name_voice, staff_info) in all_voices.items ():
2551
2552         part_ly_voices = {}
2553         for n, v in name_voice.items ():
2554             progress (_ ("Converting to LilyPond expressions..."))
2555             # musicxml_voice_to_lily_voice returns (lily_voice, {nr->lyrics, nr->lyrics})
2556             part_ly_voices[n] = musicxml_voice_to_lily_voice (v)
2557
2558         all_ly_voices[p] = part_ly_voices
2559         all_ly_staffinfo[p] = staff_info
2560
2561     return (all_ly_voices, all_ly_staffinfo)
2562
2563
2564 def option_parser ():
2565     p = ly.get_option_parser (usage = _ ("musicxml2ly [OPTION]... FILE.xml"),
2566                              description =
2567 _ ("""Convert MusicXML from FILE.xml to LilyPond input.
2568 If the given filename is -, musicxml2ly reads from the command line.
2569 """), add_help_option=False)
2570
2571     p.add_option("-h", "--help",
2572                  action="help",
2573                  help=_ ("show this help and exit"))
2574
2575     p.version = ('''%prog (LilyPond) @TOPLEVEL_VERSION@\n\n'''
2576 +
2577 _ ("""Copyright (c) 2005--2009 by
2578     Han-Wen Nienhuys <hanwen@xs4all.nl>,
2579     Jan Nieuwenhuizen <janneke@gnu.org> and
2580     Reinhold Kainhofer <reinhold@kainhofer.com>
2581 """
2582 +
2583 """
2584 This program is free software.  It is covered by the GNU General Public
2585 License and you are welcome to change it and/or distribute copies of it
2586 under certain conditions.  Invoke as `%s --warranty' for more
2587 information.""") % 'lilypond')
2588
2589     p.add_option("--version",
2590                  action="version",
2591                  help=_ ("show version number and exit"))
2592
2593     p.add_option ('-v', '--verbose',
2594                   action = "store_true",
2595                   dest = 'verbose',
2596                   help = _ ("be verbose"))
2597
2598     p.add_option ('', '--lxml',
2599                   action = "store_true",
2600                   default = False,
2601                   dest = "use_lxml",
2602                   help = _ ("use lxml.etree; uses less memory and cpu time"))
2603
2604     p.add_option ('-z', '--compressed',
2605                   action = "store_true",
2606                   dest = 'compressed',
2607                   default = False,
2608                   help = _ ("input file is a zip-compressed MusicXML file"))
2609
2610     p.add_option ('-r', '--relative',
2611                   action = "store_true",
2612                   default = True,
2613                   dest = "relative",
2614                   help = _ ("convert pitches in relative mode (default)"))
2615
2616     p.add_option ('-a', '--absolute',
2617                   action = "store_false",
2618                   dest = "relative",
2619                   help = _ ("convert pitches in absolute mode"))
2620
2621     p.add_option ('-l', '--language',
2622                   metavar = _ ("LANG"),
2623                   action = "store",
2624                   help = _ ("use a different language file 'LANG.ly' and corresponding pitch names, e.g. 'deutsch' for deutsch.ly"))
2625
2626     p.add_option ('--nd', '--no-articulation-directions', 
2627                   action = "store_false",
2628                   default = True,
2629                   dest = "convert_directions",
2630                   help = _ ("do not convert directions (^, _ or -) for articulations, dynamics, etc."))
2631
2632     p.add_option ('--nrp', '--no-rest-positions', 
2633                   action = "store_false",
2634                   default = True,
2635                   dest = "convert_rest_positions",
2636                   help = _ ("do not convert exact vertical positions of rests"))
2637
2638     p.add_option ('--no-beaming', 
2639                   action = "store_false",
2640                   default = True,
2641                   dest = "convert_beaming",
2642                   help = _ ("do not convert beaming information, use lilypond's automatic beaming instead"))
2643
2644     p.add_option ('-o', '--output',
2645                   metavar = _ ("FILE"),
2646                   action = "store",
2647                   default = None,
2648                   type = 'string',
2649                   dest = 'output_name',
2650                   help = _ ("set output filename to FILE, stdout if -"))
2651     p.add_option_group ('',
2652                         description = (
2653             _ ("Report bugs via %s")
2654             % 'http://post.gmane.org/post.php'
2655             '?group=gmane.comp.gnu.lilypond.bugs') + '\n')
2656     return p
2657
2658 def music_xml_voice_name_to_lily_name (part_id, name):
2659     str = "Part%sVoice%s" % (part_id, name)
2660     return musicxml_id_to_lily (str) 
2661
2662 def music_xml_lyrics_name_to_lily_name (part_id, name, lyricsnr):
2663     str = "Part%sVoice%sLyrics%s" % (part_id, name, lyricsnr)
2664     return musicxml_id_to_lily (str) 
2665
2666 def music_xml_figuredbass_name_to_lily_name (part_id, voicename):
2667     str = "Part%sVoice%sFiguredBass" % (part_id, voicename)
2668     return musicxml_id_to_lily (str) 
2669
2670 def music_xml_chordnames_name_to_lily_name (part_id, voicename):
2671     str = "Part%sVoice%sChords" % (part_id, voicename)
2672     return musicxml_id_to_lily (str) 
2673
2674 def print_voice_definitions (printer, part_list, voices):
2675     for part in part_list:
2676         part_id = part.id
2677         nv_dict = voices.get (part_id, {})
2678         for (name, voice) in nv_dict.items ():
2679             k = music_xml_voice_name_to_lily_name (part_id, name)
2680             printer.dump ('%s = ' % k)
2681             voice.ly_voice.print_ly (printer)
2682             printer.newline()
2683             if voice.chordnames:
2684                 cnname = music_xml_chordnames_name_to_lily_name (part_id, name)
2685                 printer.dump ('%s = ' % cnname )
2686                 voice.chordnames.print_ly (printer)
2687                 printer.newline()
2688             for l in voice.lyrics_order:
2689                 lname = music_xml_lyrics_name_to_lily_name (part_id, name, l)
2690                 printer.dump ('%s = ' % lname )
2691                 voice.lyrics_dict[l].print_ly (printer)
2692                 printer.newline()
2693             if voice.figured_bass:
2694                 fbname = music_xml_figuredbass_name_to_lily_name (part_id, name)
2695                 printer.dump ('%s = ' % fbname )
2696                 voice.figured_bass.print_ly (printer)
2697                 printer.newline()
2698
2699
2700 def uniq_list (l):
2701     return dict ([(elt,1) for elt in l]).keys ()
2702
2703 # format the information about the staff in the form 
2704 #     [staffid,
2705 #         [
2706 #            [voiceid1, [lyricsid11, lyricsid12,...], figuredbassid1],
2707 #            [voiceid2, [lyricsid21, lyricsid22,...], figuredbassid2],
2708 #            ...
2709 #         ]
2710 #     ]
2711 # raw_voices is of the form [(voicename, lyricsids, havefiguredbass)*]
2712 def format_staff_info (part_id, staff_id, raw_voices):
2713     voices = []
2714     for (v, lyricsids, figured_bass, chordnames) in raw_voices:
2715         voice_name = music_xml_voice_name_to_lily_name (part_id, v)
2716         voice_lyrics = [music_xml_lyrics_name_to_lily_name (part_id, v, l)
2717                    for l in lyricsids]
2718         figured_bass_name = ''
2719         if figured_bass:
2720             figured_bass_name = music_xml_figuredbass_name_to_lily_name (part_id, v)
2721         chordnames_name = ''
2722         if chordnames:
2723             chordnames_name = music_xml_chordnames_name_to_lily_name (part_id, v)
2724         voices.append ([voice_name, voice_lyrics, figured_bass_name, chordnames_name])
2725     return [staff_id, voices]
2726
2727 def update_score_setup (score_structure, part_list, voices):
2728
2729     for part_definition in part_list:
2730         part_id = part_definition.id
2731         nv_dict = voices.get (part_id)
2732         if not nv_dict:
2733             error_message (_ ('unknown part in part-list: %s') % part_id)
2734             continue
2735
2736         staves = reduce (lambda x,y: x+ y,
2737                 [voice.voicedata._staves.keys ()
2738                  for voice in nv_dict.values ()],
2739                 [])
2740         staves_info = []
2741         if len (staves) > 1:
2742             staves_info = []
2743             staves = uniq_list (staves)
2744             staves.sort ()
2745             for s in staves:
2746                 thisstaff_raw_voices = [(voice_name, voice.lyrics_order, voice.figured_bass, voice.chordnames) 
2747                     for (voice_name, voice) in nv_dict.items ()
2748                     if voice.voicedata._start_staff == s]
2749                 staves_info.append (format_staff_info (part_id, s, thisstaff_raw_voices))
2750         else:
2751             thisstaff_raw_voices = [(voice_name, voice.lyrics_order, voice.figured_bass, voice.chordnames) 
2752                 for (voice_name, voice) in nv_dict.items ()]
2753             staves_info.append (format_staff_info (part_id, None, thisstaff_raw_voices))
2754         score_structure.set_part_information (part_id, staves_info)
2755
2756 # Set global values in the \layout block, like auto-beaming etc.
2757 def update_layout_information ():
2758     if not conversion_settings.ignore_beaming and layout_information:
2759         layout_information.set_context_item ('Score', 'autoBeaming = ##f')
2760
2761 def print_ly_preamble (printer, filename):
2762     printer.dump_version ()
2763     printer.print_verbatim ('%% automatically converted from %s\n' % filename)
2764
2765 def print_ly_additional_definitions (printer, filename):
2766     if needed_additional_definitions:
2767         printer.newline ()
2768         printer.print_verbatim ('%% additional definitions required by the score:')
2769         printer.newline ()
2770     for a in set(needed_additional_definitions):
2771         printer.print_verbatim (additional_definitions.get (a, ''))
2772         printer.newline ()
2773     printer.newline ()
2774
2775 # Read in the tree from the given I/O object (either file or string) and 
2776 # demarshall it using the classes from the musicxml.py file
2777 def read_xml (io_object, use_lxml):
2778     if use_lxml:
2779         import lxml.etree
2780         tree = lxml.etree.parse (io_object)
2781         mxl_tree = musicxml.lxml_demarshal_node (tree.getroot ())
2782         return mxl_tree
2783     else:
2784         from xml.dom import minidom, Node
2785         doc = minidom.parse(io_object)
2786         node = doc.documentElement
2787         return musicxml.minidom_demarshal_node (node)
2788     return None
2789
2790
2791 def read_musicxml (filename, compressed, use_lxml):
2792     raw_string = None
2793     if compressed:
2794         if filename == "-":
2795              progress (_ ("Input is compressed, extracting raw MusicXML data from stdin") )
2796              z = zipfile.ZipFile (sys.stdin)
2797         else:
2798             progress (_ ("Input file %s is compressed, extracting raw MusicXML data") % filename)
2799             z = zipfile.ZipFile (filename, "r")
2800         container_xml = z.read ("META-INF/container.xml")
2801         if not container_xml:
2802             return None
2803         container = read_xml (StringIO.StringIO (container_xml), use_lxml)
2804         if not container:
2805             return None
2806         rootfiles = container.get_maybe_exist_named_child ('rootfiles')
2807         if not rootfiles:
2808             return None
2809         rootfile_list = rootfiles.get_named_children ('rootfile')
2810         mxml_file = None
2811         if len (rootfile_list) > 0:
2812             mxml_file = getattr (rootfile_list[0], 'full-path', None)
2813         if mxml_file:
2814             raw_string = z.read (mxml_file)
2815
2816     if raw_string:
2817         io_object = StringIO.StringIO (raw_string)
2818     elif filename == "-":
2819         io_object = sys.stdin
2820     else:
2821         io_object = filename
2822
2823     return read_xml (io_object, use_lxml)
2824
2825
2826 def convert (filename, options):
2827     if filename == "-":
2828         progress (_ ("Reading MusicXML from Standard input ...") )
2829     else:
2830         progress (_ ("Reading MusicXML from %s ...") % filename)
2831
2832     tree = read_musicxml (filename, options.compressed, options.use_lxml)
2833     score_information = extract_score_information (tree)
2834     paper_information = extract_paper_information (tree)
2835
2836     parts = tree.get_typed_children (musicxml.Part)
2837     (voices, staff_info) = get_all_voices (parts)
2838
2839     score = None
2840     mxl_pl = tree.get_maybe_exist_typed_child (musicxml.Part_list)
2841     if mxl_pl:
2842         score = extract_score_structure (mxl_pl, staff_info)
2843         part_list = mxl_pl.get_named_children ("score-part")
2844
2845     # score information is contained in the <work>, <identification> or <movement-title> tags
2846     update_score_setup (score, part_list, voices)
2847     # After the conversion, update the list of settings for the \layout block
2848     update_layout_information ()
2849
2850     if not options.output_name:
2851         options.output_name = os.path.basename (filename) 
2852         options.output_name = os.path.splitext (options.output_name)[0]
2853     elif re.match (".*\.ly", options.output_name):
2854         options.output_name = os.path.splitext (options.output_name)[0]
2855
2856
2857     #defs_ly_name = options.output_name + '-defs.ly'
2858     if (options.output_name == "-"):
2859       output_ly_name = 'Standard output'
2860     else:
2861       output_ly_name = options.output_name + '.ly'
2862
2863     progress (_ ("Output to `%s'") % output_ly_name)
2864     printer = musicexp.Output_printer()
2865     #progress (_ ("Output to `%s'") % defs_ly_name)
2866     if (options.output_name == "-"):
2867       printer.set_file (codecs.getwriter ("utf-8")(sys.stdout))
2868     else:
2869       printer.set_file (codecs.open (output_ly_name, 'wb', encoding='utf-8'))
2870     print_ly_preamble (printer, filename)
2871     print_ly_additional_definitions (printer, filename)
2872     if score_information:
2873         score_information.print_ly (printer)
2874     if paper_information:
2875         paper_information.print_ly (printer)
2876     if layout_information:
2877         layout_information.print_ly (printer)
2878     print_voice_definitions (printer, part_list, voices)
2879     
2880     printer.newline ()
2881     printer.dump ("% The score definition")
2882     printer.newline ()
2883     score.print_ly (printer)
2884     printer.newline ()
2885
2886     return voices
2887
2888 def get_existing_filename_with_extension (filename, ext):
2889     if os.path.exists (filename):
2890         return filename
2891     newfilename = filename + "." + ext
2892     if os.path.exists (newfilename):
2893         return newfilename;
2894     newfilename = filename + ext
2895     if os.path.exists (newfilename):
2896         return newfilename;
2897     return ''
2898
2899 def main ():
2900     opt_parser = option_parser()
2901
2902     global options
2903     (options, args) = opt_parser.parse_args ()
2904     if not args:
2905         opt_parser.print_usage()
2906         sys.exit (2)
2907
2908     if options.language:
2909         musicexp.set_pitch_language (options.language)
2910         needed_additional_definitions.append (options.language)
2911         additional_definitions[options.language] = "\\include \"%s.ly\"\n" % options.language
2912     conversion_settings.ignore_beaming = not options.convert_beaming
2913
2914     # Allow the user to leave out the .xml or xml on the filename
2915     basefilename = args[0].decode('utf-8')
2916     if basefilename == "-": # Read from stdin
2917         basefilename = "-"
2918     else:
2919         filename = get_existing_filename_with_extension (basefilename, "xml")
2920         if not filename:
2921             filename = get_existing_filename_with_extension (basefilename, "mxl")
2922             options.compressed = True
2923     if filename and filename.endswith ("mxl"):
2924         options.compressed = True
2925
2926     if filename and (filename == "-" or os.path.exists (filename)):
2927         voices = convert (filename, options)
2928     else:
2929         progress (_ ("Unable to find input file %s") % basefilename)
2930
2931 if __name__ == '__main__':
2932     main()