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