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