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