]> git.donarmstrong.com Git - lilypond.git/blob - scripts/musicxml2ly.py
(datadir): remove LILYPONDPREFIX support.
[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 from gettext import gettext as _
9
10
11
12 for d in ['@lilypond_datadir@',
13           '@lilypond_libdir@']:
14     sys.path.insert (0, os.path.join (d, 'python'))
15
16 # dynamic relocation, for GUB binaries.
17 bindir = os.path.abspath (os.path.split (sys.argv[0])[0])
18 for p in ['share', 'lib']:
19     datadir = os.path.abspath (bindir + '/../%s/lilypond/current/python/' % p)
20     sys.path.insert (0, datadir)
21
22
23
24
25 import lilylib as ly
26
27 import musicxml
28 import musicexp
29
30 from rational import Rational
31
32
33 def progress (str):
34     sys.stderr.write (str + '\n')
35     sys.stderr.flush ()
36     
37
38 def musicxml_duration_to_lily (mxl_note):
39     d = musicexp.Duration ()
40     if mxl_note.get_maybe_exist_typed_child (musicxml.Type):
41         d.duration_log = mxl_note.get_duration_log ()
42     else:
43         d.duration_log = 0
44
45     d.dots = len (mxl_note.get_typed_children (musicxml.Dot))
46     d.factor = mxl_note._duration / d.get_length ()
47
48     return d         
49
50 def group_tuplets (music_list, events):
51
52
53     """Collect Musics from
54     MUSIC_LIST demarcated by EVENTS_LIST in TimeScaledMusic objects.
55     """
56
57     
58     indices = []
59
60     j = 0
61     for (ev_chord, tuplet_elt, fraction) in events:
62         while (j < len (music_list)):
63             if music_list[j]== ev_chord:
64                 break
65             j += 1
66         if tuplet_elt.type == 'start':
67             indices.append ((j, None, fraction))
68         elif tuplet_elt.type == 'stop':
69             indices[-1] = (indices[-1][0], j, indices[-1][2])
70
71     new_list = []
72     last = 0
73     for (i1, i2, frac) in indices:
74         if i1 >= i2:
75             continue
76
77         new_list.extend (music_list[last:i1])
78         seq = musicexp.SequentialMusic ()
79         last = i2 + 1
80         seq.elements = music_list[i1:last]
81
82         tsm = musicexp.TimeScaledMusic ()
83         tsm.element = seq
84
85         tsm.numerator = frac[0]
86         tsm.denominator  = frac[1]
87
88         new_list.append (tsm)
89
90     new_list.extend (music_list[last:])
91     return new_list
92
93
94 def musicxml_clef_to_lily (attributes):
95     change = musicexp.ClefChange ()
96     change.type = attributes.get_clef_sign ()
97     return change
98     
99 def musicxml_time_to_lily (attributes):
100     (beats, type) = attributes.get_time_signature ()
101
102     change = musicexp.TimeSignatureChange()
103     change.fraction = (beats, type)
104     
105     return change
106
107 def musicxml_key_to_lily (attributes):
108     start_pitch  = musicexp.Pitch ()
109     (fifths, mode) = attributes.get_key_signature () 
110     try:
111         (n,a) = {
112             'major' : (0,0),
113             'minor' : (6,0),
114             }[mode]
115         start_pitch.step = n
116         start_pitch.alteration = a
117     except  KeyError:
118         print 'unknown mode', mode
119
120     fifth = musicexp.Pitch()
121     fifth.step = 4
122     if fifths < 0:
123         fifths *= -1
124         fifth.step *= -1
125         fifth.normalize ()
126     
127     start_pitch = musicexp.Pitch()
128     for x in range (fifths):
129         start_pitch = start_pitch.transposed (fifth)
130
131     start_pitch.octave = 0
132
133     change = musicexp.KeySignatureChange()
134     change.mode = mode
135     change.tonic = start_pitch
136     return change
137     
138 def musicxml_attributes_to_lily (attrs):
139     elts = []
140     attr_dispatch =  {
141         'clef': musicxml_clef_to_lily,
142         'time': musicxml_time_to_lily,
143         'key': musicxml_key_to_lily
144     }
145     for (k, func) in attr_dispatch.items ():
146         childs = attrs.get_named_children (k)
147
148         ## ugh: you get clefs spread over staves for piano
149         if childs:
150             elts.append (func (attrs))
151     
152     return elts
153
154 spanner_event_dict = {
155     'slur' : musicexp.SlurEvent,
156     'beam' : musicexp.BeamEvent,
157 }        
158 spanner_type_dict = {
159     'start': -1,
160     'begin': -1,
161     'stop': 1,
162     'end' : 1
163 }
164
165 def musicxml_spanner_to_lily_event (mxl_event):
166     ev = None
167     
168     name = mxl_event.get_name()
169     try:
170         func = spanner_event_dict[name]
171         ev = func()
172     except KeyError:
173         print 'unknown span event ', mxl_event
174
175     try:
176         key = mxl_event.get_type ()
177         ev.span_direction = spanner_type_dict[key]
178     except KeyError:
179         print 'unknown span type', key, 'for', name
180
181     return ev
182
183 instrument_drumtype_dict = {
184     'Acoustic Snare Drum': 'acousticsnare',
185     'Side Stick': 'sidestick',
186     'Open Triangle': 'opentriangle',
187     'Mute Triangle': 'mutetriangle',
188     'Tambourine': 'tambourine',
189     
190 }
191
192 def musicxml_note_to_lily_main_event (n):
193     pitch  = None
194     duration = None
195         
196     mxl_pitch = n.get_maybe_exist_typed_child (musicxml.Pitch)
197     event = None
198     if mxl_pitch:
199         pitch = musicxml_pitch_to_lily (mxl_pitch)
200         event = musicexp.NoteEvent()
201         event.pitch = pitch
202
203         acc = n.get_maybe_exist_named_child ('accidental')
204         if acc:
205             # let's not force accs everywhere. 
206             event.cautionary = acc.editorial
207         
208     elif n.get_maybe_exist_typed_child (musicxml.Rest):
209         event = musicexp.RestEvent()
210     elif n.instrument_name:
211         event = musicexp.NoteEvent ()
212         event.drum_type = instrument_drumtype_dict[n.instrument_name]
213         
214     
215     if not event:
216         n.message ("could not find suitable event")
217
218     event.duration = musicxml_duration_to_lily (n)
219     return event
220
221
222 ## todo
223 class NegativeSkip:
224     def __init__ (self, here, dest):
225         self.here = here
226         self.dest = dest
227
228 class LilyPondVoiceBuilder:
229     def __init__ (self):
230         self.elements = []
231         self.end_moment = Rational (0)
232         self.begin_moment = Rational (0)
233         self.pending_multibar = Rational (0)
234
235     def _insert_multibar (self):
236         r = musicexp.MultiMeasureRest ()
237         r.duration = musicexp.Duration()
238         r.duration.duration_log = 0
239         r.duration.factor = self.pending_multibar
240         self.elements.append (r)
241         self.begin_moment = self.end_moment
242         self.end_moment = self.begin_moment + self.pending_multibar
243         self.pending_multibar = Rational (0)
244         
245     def add_multibar_rest (self, duration):
246         self.pending_multibar += duration
247         
248         
249     def add_music (self, music, duration):
250         assert isinstance (music, musicexp.Music)
251         if self.pending_multibar > Rational (0):
252             self._insert_multibar ()
253
254         self.elements.append (music)
255         self.begin_moment = self.end_moment
256         self.end_moment = self.begin_moment + duration 
257
258     def add_bar_check (self, number):
259         b = musicexp.BarCheck ()
260         b.bar_number = number
261         self.add_music (b, Rational (0))
262
263     def jumpto (self, moment):
264         current_end = self.end_moment + self.pending_multibar
265         diff = moment - current_end
266         
267         if diff < Rational (0):
268             raise NegativeSkip(current_end, moment)
269
270         if diff > Rational (0):
271             skip = musicexp.SkipEvent()
272             skip.duration.duration_log = 0
273             skip.duration.factor = diff
274
275             evc = musicexp.EventChord ()
276             evc.elements.append (skip)
277             self.add_music (evc, diff)
278                 
279     def last_event_chord (self, starting_at):
280
281         value = None
282         if (self.elements
283             and isinstance (self.elements[-1], musicexp.EventChord)
284             and self.begin_moment == starting_at):
285             value = self.elements[-1]
286         else:
287             self.jumpto (starting_at)
288             value = None
289
290         return value
291         
292     def correct_negative_skip (self, goto):
293         self.end_moment = goto
294         self.begin_moment = goto
295         evc = musicexp.EventChord ()
296         self.elements.append (evc)
297         
298 def musicxml_voice_to_lily_voice (voice):
299     tuplet_events = []
300     modes_found = {}
301
302     voice_builder = LilyPondVoiceBuilder()
303
304     for n in voice._elements:
305         if n.get_name () == 'forward':
306             continue
307
308         if not n.get_maybe_exist_named_child ('chord'):
309             try:
310                 voice_builder.jumpto (n._when)
311             except NegativeSkip, neg:
312                 voice_builder.correct_negative_skip (n._when)
313                 n.message ("Negative skip? from %s to %s, diff %s" % (neg.here, neg.dest, neg.dest - neg.here))
314             
315         if isinstance (n, musicxml.Attributes):
316             if n.is_first () and n._measure_position == Rational (0):
317                 voice_builder.add_bar_check (int (n.get_parent ().number))
318             for a in musicxml_attributes_to_lily (n):
319                 voice_builder.add_music (a, Rational (0))
320             continue
321
322         if not n.__class__.__name__ == 'Note':
323             print 'not a Note or Attributes?', n
324             continue
325
326         rest = n.get_maybe_exist_typed_child (musicxml.Rest)
327         if (rest
328             and rest.is_whole_measure ()):
329
330             voice_builder.add_multibar_rest (n._duration)
331             continue
332
333         if n.is_first () and n._measure_position == Rational (0):
334             num = int (n.get_parent ().number)
335             voice_builder.add_bar_check (num)
336         
337         main_event = musicxml_note_to_lily_main_event (n)
338
339         try:
340             if main_event.drum_type:
341                 modes_found['drummode'] = True
342         except AttributeError:
343             pass
344
345
346         ev_chord = voice_builder.last_event_chord (n._when)
347         if not ev_chord: 
348             ev_chord = musicexp.EventChord()
349             voice_builder.add_music (ev_chord, n._duration)
350
351         ev_chord.append (main_event)
352         
353         notations = n.get_maybe_exist_typed_child (musicxml.Notations)
354         tuplet_event = None
355         span_events = []
356         if notations:
357             if notations.get_tuplet():
358                 tuplet_event = notations.get_tuplet()
359                 mod = n.get_maybe_exist_typed_child (musicxml.Time_modification)
360                 frac = (1,1)
361                 if mod:
362                     frac = mod.get_fraction ()
363                 
364                 tuplet_events.append ((ev_chord, tuplet_event, frac))
365
366             slurs = [s for s in notations.get_named_children ('slur')
367                 if s.get_type () in ('start','stop')]
368             if slurs:
369                 if len (slurs) > 1:
370                     print 'more than 1 slur?'
371
372                 lily_ev = musicxml_spanner_to_lily_event (slurs[0])
373                 ev_chord.append (lily_ev)
374
375             mxl_tie = notations.get_tie ()
376             if mxl_tie and mxl_tie.type == 'start':
377                 ev_chord.append (musicexp.TieEvent ())
378
379         mxl_beams = [b for b in n.get_named_children ('beam')
380                      if (b.get_type () in ('begin', 'end')
381                          and b.is_primary ())] 
382         if mxl_beams:
383             beam_ev = musicxml_spanner_to_lily_event (mxl_beams[0])
384             if beam_ev:
385                 ev_chord.append (beam_ev)
386             
387         if tuplet_event:
388             mod = n.get_maybe_exist_typed_child (musicxml.Time_modification)
389             frac = (1,1)
390             if mod:
391                 frac = mod.get_fraction ()
392                 
393             tuplet_events.append ((ev_chord, tuplet_event, frac))
394
395     ## force trailing mm rests to be written out.   
396     voice_builder.add_music (musicexp.EventChord (), Rational (0))
397     
398     ly_voice = group_tuplets (voice_builder.elements, tuplet_events)
399
400     seq_music = musicexp.SequentialMusic()
401
402     if 'drummode' in modes_found.keys ():
403         ## \key <pitch> barfs in drummode.
404         ly_voice = [e for e in ly_voice
405                     if not isinstance(e, musicexp.KeySignatureChange)]
406     
407     seq_music.elements = ly_voice
408
409     
410     
411     if len (modes_found) > 1:
412        print 'Too many modes found', modes_found.keys ()
413
414     return_value = seq_music
415     for mode in modes_found.keys ():
416         v = musicexp.ModeChangingMusicWrapper()
417         v.element = return_value
418         v.mode = mode
419         return_value = v
420     
421     return return_value
422
423
424 def musicxml_id_to_lily (id):
425     digits = ['one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight',
426               'nine', 'ten']
427     
428     for dig in digits:
429         d = digits.index (dig) + 1
430         dig = dig[0].upper() + dig[1:]
431         id = re.sub ('%d' % d, dig, id)
432
433     id = re.sub  ('[^a-zA-Z]', 'X', id)
434     return id
435
436
437 def musicxml_pitch_to_lily (mxl_pitch):
438     p = musicexp.Pitch()
439     p.alteration = mxl_pitch.get_alteration ()
440     p.step = (ord (mxl_pitch.get_step ()) - ord ('A') + 7 - 2) % 7
441     p.octave = mxl_pitch.get_octave () - 4
442     return p
443
444 def voices_in_part (part):
445     """Return a Name -> Voice dictionary for PART"""
446     part.interpret ()
447     part.extract_voices ()
448     voice_dict = part.get_voices ()
449
450     return voice_dict
451
452 def voices_in_part_in_parts (parts):
453     """return a Part -> Name -> Voice dictionary"""
454     return dict([(p, voices_in_part (p)) for p in parts])
455
456
457 def get_all_voices (parts):
458     all_voices = voices_in_part_in_parts (parts)
459
460     all_ly_voices = {}
461     for p, name_voice in all_voices.items ():
462
463         part_ly_voices = {}
464         for n, v in name_voice.items ():
465             progress ("Converting to LilyPond expressions...")
466             part_ly_voices[n] = (musicxml_voice_to_lily_voice (v), v)
467
468         all_ly_voices[p] = part_ly_voices
469         
470     return all_ly_voices
471
472
473 def option_parser ():
474     p = ly.get_option_parser(usage='musicxml2ly FILE.xml',
475                  version = """%prog (LilyPond) @TOPLEVEL_VERSION@
476
477 This program is free software.  It is covered by the GNU General Public
478 License and you are welcome to change it and/or distribute copies of it
479 under certain conditions.  Invoke as `lilypond --warranty' for more
480 information.
481
482 Copyright (c) 2005--2006 by
483     Han-Wen Nienhuys <hanwen@xs4all.nl> and
484     Jan Nieuwenhuizen <janneke@gnu.org>
485 """,
486
487                  description  =
488                  """Convert MusicXML file to LilyPond input.
489 """
490                  )
491     p.add_option ('-v', '--verbose',
492                   action = "store_true",
493                   dest = 'verbose',
494                   help = 'be verbose')
495
496     p.add_option ('', '--lxml',
497                   action="store_true",
498                   default=False,
499                   dest="use_lxml",
500                   help="Use lxml.etree; uses less memory and cpu time.")
501     
502     p.add_option ('-o', '--output',
503                   metavar = 'FILE',
504                   action="store",
505                   default=None,
506                   type='string',
507                   dest='output_name',
508                   help='set output file')
509
510     p.add_option_group  ('', description = '''Report bugs via http://post.gmane.org/post.php?group=gmane.comp.gnu.lilypond.bugs
511 ''')
512     return p
513
514 def music_xml_voice_name_to_lily_name (part, name):
515     str = "Part%sVoice%s" % (part.id, name)
516     return musicxml_id_to_lily (str) 
517
518 def print_voice_definitions (printer, voices):
519     for (part, nv_dict) in voices.items():
520         
521         for (name, (voice, mxlvoice)) in nv_dict.items ():
522             k = music_xml_voice_name_to_lily_name (part, name)
523             printer.dump ('%s = ' % k)
524             voice.print_ly (printer)
525             printer.newline()
526
527             
528 def uniq_list (l):
529     return dict ([(elt,1) for elt in l]).keys ()
530     
531 def print_score_setup (printer, part_list, voices):
532     part_dict = dict ([(p.id, p) for p in voices.keys ()]) 
533
534     printer ('<<')
535     printer.newline ()
536     for part_definition in part_list:
537         part_name = part_definition.id
538         try:
539             part = part_dict[part_name]
540         except KeyError:
541             print 'unknown part in part-list:', part_name
542             continue
543
544         nv_dict = voices[part]
545         staves = reduce (lambda x,y: x+ y,
546                 [mxlvoice._staves.keys ()
547                  for (v, mxlvoice) in nv_dict.values ()],
548                 [])
549
550         if len (staves) > 1:
551             staves = uniq_list (staves)
552             staves.sort ()
553             printer ('\\context PianoStaff << ')
554             printer.newline ()
555             
556             for s in staves:
557                 staff_voices = [music_xml_voice_name_to_lily_name (part, voice_name)
558                         for (voice_name, (v, mxlvoice)) in nv_dict.items ()
559                         if mxlvoice._start_staff == s]
560                 
561                 printer ('\\context Staff = "%s" << ' % s)
562                 printer.newline ()
563                 for v in staff_voices:
564                     printer ('\\context Voice = "%s"  \\%s' % (v,v))
565                     printer.newline ()
566                 printer ('>>')
567                 printer.newline ()
568                 
569             printer ('>>')
570             printer.newline ()
571             
572         else:
573             printer ('\\new Staff <<')
574             printer.newline ()
575             for (n,v) in nv_dict.items ():
576
577                 n = music_xml_voice_name_to_lily_name (part, n) 
578                 printer ('\\context Voice = "%s"  \\%s' % (n,n))
579             printer ('>>')
580             printer.newline ()
581             
582     printer ('>>')
583     printer.newline ()
584
585 def print_ly_preamble (printer, filename):
586     printer.dump_version ()
587     printer.print_verbatim ('%% converted from %s\n' % filename)
588
589 def read_musicxml (filename, use_lxml):
590     if use_lxml:
591         import lxml.etree
592         
593         tree = lxml.etree.parse (filename)
594         mxl_tree = musicxml.lxml_demarshal_node (tree.getroot ())
595         return mxl_tree
596     else:
597         from xml.dom import minidom, Node
598         
599         doc = minidom.parse(filename)
600         node = doc.documentElement
601         return musicxml.minidom_demarshal_node (node)
602
603     return None
604
605
606 def convert (filename, options):
607     progress ("Reading MusicXML from %s ..." % filename)
608     
609     tree = read_musicxml (filename, options.use_lxml)
610
611     part_list = []
612     id_instrument_map = {}
613     if tree.get_maybe_exist_typed_child (musicxml.Part_list):
614         mxl_pl = tree.get_maybe_exist_typed_child (musicxml.Part_list)
615         part_list = mxl_pl.get_named_children ("score-part")
616         
617     parts = tree.get_typed_children (musicxml.Part)
618     voices = get_all_voices (parts)
619
620     if not options.output_name:
621         options.output_name = os.path.basename (filename) 
622         options.output_name = os.path.splitext (options.output_name)[0]
623
624
625     defs_ly_name = options.output_name + '-defs.ly'
626     driver_ly_name = options.output_name + '.ly'
627
628     printer = musicexp.Output_printer()
629     progress ("Output to `%s'" % defs_ly_name)
630     printer.set_file (open (defs_ly_name, 'w'))
631
632     print_ly_preamble (printer, filename)
633     print_voice_definitions (printer, voices)
634     
635     printer.close ()
636     
637     
638     progress ("Output to `%s'" % driver_ly_name)
639     printer = musicexp.Output_printer()
640     printer.set_file (open (driver_ly_name, 'w'))
641     print_ly_preamble (printer, filename)
642     printer.dump (r'\include "%s"' % defs_ly_name)
643     print_score_setup (printer, part_list, voices)
644     printer.newline ()
645
646     return voices
647
648
649 def main ():
650     opt_parser = option_parser()
651
652     (options, args) = opt_parser.parse_args ()
653     if not args:
654         opt_parser.print_usage()
655         sys.exit (2)
656
657     voices = convert (args[0], options)
658
659 if __name__ == '__main__':
660     main()