]> git.donarmstrong.com Git - lilypond.git/blob - python/musicxml.py
MusicXML: Don't crash when no time signature is given
[lilypond.git] / python / musicxml.py
1 import new
2 import string
3 from rational import *
4 import re
5 import sys
6
7 def escape_ly_output_string (input_string):
8     return_string = input_string
9     needs_quotes = not re.match ("^[a-zA-ZäöüÜÄÖßñ]*$", return_string);
10     if needs_quotes:
11         return_string = "\"" + string.replace (return_string, "\"", "\\\"") + "\""
12     return return_string
13
14
15 class Xml_node:
16     def __init__ (self):
17         self._children = []
18         self._data = None
19         self._original = None
20         self._name = 'xml_node'
21         self._parent = None
22         self._attribute_dict = {}
23         
24     def get_parent (self):
25         return self._parent
26     
27     def is_first (self):
28         return self._parent.get_typed_children (self.__class__)[0] == self
29
30     def original (self):
31         return self._original 
32     def get_name (self):
33         return self._name
34
35     def get_text (self):
36         if self._data:
37             return self._data
38
39         if not self._children:
40             return ''
41
42         return ''.join ([c.get_text () for c in self._children])
43
44     def message (self, msg):
45         sys.stderr.write (msg)
46
47         p = self
48         while p:
49             sys.stderr.write ('  In: <%s %s>' % (p._name, ' '.join (['%s=%s' % item for item in p._attribute_dict.items()])))
50             p = p.get_parent ()
51         
52     def get_typed_children (self, klass):
53         if not klass:
54             return []
55         else:
56             return [c for c in self._children if isinstance(c, klass)]
57
58     def get_named_children (self, nm):
59         return self.get_typed_children (get_class (nm))
60
61     def get_named_child (self, nm):
62         return self.get_maybe_exist_named_child (nm)
63
64     def get_children (self, predicate):
65         return [c for c in self._children if predicate(c)]
66
67     def get_all_children (self):
68         return self._children
69
70     def get_maybe_exist_named_child (self, name):
71         return self.get_maybe_exist_typed_child (get_class (name))
72
73     def get_maybe_exist_typed_child (self, klass):
74         cn = self.get_typed_children (klass)
75         if len (cn)==0:
76             return None
77         elif len (cn) == 1:
78             return cn[0]
79         else:
80             raise "More than 1 child", klass
81
82     def get_unique_typed_child (self, klass):
83         cn = self.get_typed_children(klass)
84         if len (cn) <> 1:
85             sys.stderr.write (self.__dict__)
86             raise 'Child is not unique for', (klass, 'found', cn)
87
88         return cn[0]
89
90 class Music_xml_node (Xml_node):
91     def __init__ (self):
92         Xml_node.__init__ (self)
93         self.duration = Rational (0)
94         self.start = Rational (0)
95
96 class Work (Xml_node):
97     def get_work_information (self, tag):
98         wt = self.get_maybe_exist_named_child (tag)
99         if wt:
100             return wt.get_text ()
101         else:
102             return ''
103       
104     def get_work_title (self):
105         return self.get_work_information ('work-title')
106     def get_work_number (self):
107         return self.get_work_information ('work-number')
108     def get_opus (self):
109         return self.get_work_information ('opus')
110
111 class Identification (Xml_node):
112     def get_rights (self):
113         rights = self.get_maybe_exist_named_child ('rights')
114         if rights:
115             return rights.get_text ()
116         else:
117             return ''
118
119     def get_creator (self, type):
120         creators = self.get_named_children ('creator')
121         # return the first creator tag that has the particular type
122         for i in creators:
123             if hasattr (i, 'type') and i.type == type:
124                 return i.get_text ()
125             else:
126                 return ''
127
128     def get_composer (self):
129         c = self.get_creator ('composer')
130         if c:
131             return c
132         creators = self.get_named_children ('creator')
133         # return the first creator tag that has no type at all
134         for i in creators:
135             if not hasattr (i, 'type'):
136                 return i.get_text ()
137         return c
138     def get_arranger (self):
139         return self.get_creator ('arranger')
140     def get_editor (self):
141         return self.get_creator ('editor')
142     def get_poet (self):
143         return self.get_creator ('poet')
144     
145     def get_encoding_information (self, type):
146         enc = self.get_named_children ('encoding')
147         if enc:
148             children = enc[0].get_named_children (type)
149             if children:
150                 return children[0].get_text ()
151         else:
152             return ''
153       
154     def get_encoding_software (self):
155         return self.get_encoding_information ('software')
156     def get_encoding_date (self):
157         return self.get_encoding_information ('encoding-date')
158     def get_encoding_person (self):
159         return self.get_encoding_information ('encoder')
160     def get_encoding_description (self):
161         return self.get_encoding_information ('encoding-description')
162
163
164 class Duration (Music_xml_node):
165     def get_length (self):
166         dur = int (self.get_text ()) * Rational (1,4)
167         return dur
168
169 class Hash_comment (Music_xml_node):
170     pass
171 class Hash_text (Music_xml_node):
172     pass
173
174 class Pitch (Music_xml_node):
175     def get_step (self):
176         ch = self.get_unique_typed_child (get_class (u'step'))
177         step = ch.get_text ().strip ()
178         return step
179     def get_octave (self):
180         ch = self.get_unique_typed_child (get_class (u'octave'))
181
182         step = ch.get_text ().strip ()
183         return int (step)
184
185     def get_alteration (self):
186         ch = self.get_maybe_exist_typed_child (get_class (u'alter'))
187         alter = 0
188         if ch:
189             alter = int (ch.get_text ().strip ())
190         return alter
191
192 class Measure_element (Music_xml_node):
193     def get_voice_id (self):
194         voice_id = self.get_maybe_exist_named_child ('voice')
195         if voice_id:
196             return voice_id.get_text ()
197         else:
198             return None
199
200     def is_first (self):
201         cn = self._parent.get_typed_children (self.__class__)
202         cn = [c for c in cn if c.get_voice_id () == self.get_voice_id ()]
203         return cn[0] == self
204
205 class Attributes (Measure_element):
206     def __init__ (self):
207         Measure_element.__init__ (self)
208         self._dict = {}
209
210     def set_attributes_from_previous (self, dict):
211         self._dict.update (dict)
212         
213     def read_self (self):
214         for c in self.get_all_children ():
215             self._dict[c.get_name()] = c
216
217     def get_named_attribute (self, name):
218         return self._dict.get (name)
219
220     def get_measure_length (self):
221         (n,d) = self.get_time_signature ()
222         return Rational (n,d)
223         
224     def get_time_signature (self):
225         "return time sig as a (beat, beat-type) tuple"
226
227         try:
228             mxl = self.get_named_attribute ('time')
229             if mxl:
230                 beats = mxl.get_maybe_exist_named_child ('beats')
231                 type = mxl.get_maybe_exist_named_child ('beat-type')
232                 return (int (beats.get_text ()),
233                         int (type.get_text ()))
234             else:
235                 return (4, 4)
236         except KeyError:
237             sys.stderr.write ('error: requested time signature, but time sig unknown')
238             return (4, 4)
239
240     # returns clef information in the form ("cleftype", position, octave-shift)
241     def get_clef_information (self):
242         clefinfo = ['G', 2, 0]
243         mxl = self.get_named_attribute ('clef')
244         if not mxl:
245             return clefinfo
246         sign = mxl.get_maybe_exist_named_child ('sign')
247         if sign:
248             clefinfo[0] = sign.get_text()
249         line = mxl.get_maybe_exist_named_child ('line')
250         if line:
251             clefinfo[1] = string.atoi (line.get_text ())
252         octave = mxl.get_maybe_exist_named_child ('clef-octave-change')
253         if octave:
254             clefinfo[2] = string.atoi (octave.get_text ())
255         return clefinfo
256
257     def get_key_signature (self):
258         "return (fifths, mode) tuple"
259
260         key = self.get_named_attribute ('key')
261         mode_node = key.get_maybe_exist_named_child ('mode')
262         mode = 'major'
263         if mode_node:
264             mode = mode_node.get_text ()
265
266         fifths = int (key.get_maybe_exist_named_child ('fifths').get_text ())
267         return (fifths, mode)
268                 
269
270 class Note (Measure_element):
271     def __init__ (self):
272         Measure_element.__init__ (self)
273         self.instrument_name = ''
274         
275     def get_duration_log (self):
276         ch = self.get_maybe_exist_typed_child (get_class (u'type'))
277
278         if ch:
279             log = ch.get_text ().strip()
280             return {'256th': 8,
281                     '128th': 7,
282                     '64th': 6,
283                     '32nd': 5,
284                     '16th': 4,
285                     'eighth': 3,
286                     'quarter': 2,
287                     'half': 1,
288                     'whole': 0,
289                     'breve': -1,
290                     'long': -2}.get (log, 0)
291         else:
292             sys.stderr.write ("Encountered note without duration (no <type> element): %s\n" % self)
293             return 0
294
295     def get_factor (self):
296         return 1
297
298     def get_pitches (self):
299         return self.get_typed_children (get_class (u'pitch'))
300
301 class Part_list (Music_xml_node):
302     def __init__ (self):
303         Music_xml_node.__init__ (self)
304         self._id_instrument_name_dict = {}
305         
306     def generate_id_instrument_dict (self):
307
308         ## not empty to make sure this happens only once.
309         mapping = {1: 1}
310         for score_part in self.get_named_children ('score-part'):
311             for instr in score_part.get_named_children ('score-instrument'):
312                 id = instr.id
313                 name = instr.get_named_child ("instrument-name")
314                 mapping[id] = name.get_text ()
315
316         self._id_instrument_name_dict = mapping
317
318     def get_instrument (self, id):
319         if not self._id_instrument_name_dict:
320             self.generate_id_instrument_dict()
321
322         instrument_name = self._id_instrument_name_dict.get (id)
323         if instrument_name:
324             return instrument_name
325         else:
326             sys.stderr.write ("Opps, couldn't find instrument for ID=%s" % id)
327             return "Grand Piano"
328         
329 class Measure (Music_xml_node):
330     def get_notes (self):
331         return self.get_typed_children (get_class (u'note'))
332
333 class Syllabic (Music_xml_node):
334     def continued (self):
335         text = self.get_text()
336         return (text == "begin") or (text == "middle")
337 class Text (Music_xml_node):
338     pass
339
340 class Lyric (Music_xml_node):
341     def get_number (self):
342         if hasattr (self, 'number'):
343             return self.number
344         else:
345             return -1
346
347     def lyric_to_text (self):
348         continued = False
349         syllabic = self.get_maybe_exist_typed_child (Syllabic)
350         if syllabic:
351             continued = syllabic.continued ()
352         text = self.get_maybe_exist_typed_child (Text)
353         
354         if text:
355             text = text.get_text()
356             # We need to convert soft hyphens to -, otherwise the ascii codec as well
357             # as lilypond will barf on that character
358             text = string.replace( text, u'\xad', '-' )
359         
360         if text == "-" and continued:
361             return "--"
362         elif text == "_" and continued:
363             return "__"
364         elif continued and text:
365             return escape_ly_output_string (text) + " --"
366         elif continued:
367             return "--"
368         elif text:
369             return escape_ly_output_string (text)
370         else:
371             return ""
372
373 class Musicxml_voice:
374     def __init__ (self):
375         self._elements = []
376         self._staves = {}
377         self._start_staff = None
378         self._lyrics = []
379         self._has_lyrics = False
380
381     def add_element (self, e):
382         self._elements.append (e)
383         if (isinstance (e, Note)
384             and e.get_maybe_exist_typed_child (Staff)):
385             name = e.get_maybe_exist_typed_child (Staff).get_text ()
386
387             if not self._start_staff:
388                 self._start_staff = name
389             self._staves[name] = True
390
391         lyrics = e.get_typed_children (Lyric)
392         if not self._has_lyrics:
393           self.has_lyrics = len (lyrics) > 0
394
395         for l in lyrics:
396             nr = l.get_number()
397             if (nr > 0) and not (nr in self._lyrics):
398                 self._lyrics.append (nr)
399
400     def insert (self, idx, e):
401         self._elements.insert (idx, e)
402
403     def get_lyrics_numbers (self):
404         if (len (self._lyrics) == 0) and self._has_lyrics:
405             #only happens if none of the <lyric> tags has a number attribute
406             return [1]
407         else:
408             return self._lyrics
409
410
411
412 class Part (Music_xml_node):
413     def __init__ (self):
414         Music_xml_node.__init__ (self)
415         self._voices = []
416
417     def get_part_list (self):
418         n = self
419         while n and n.get_name() != 'score-partwise':
420             n = n._parent
421
422         return n.get_named_child ('part-list')
423         
424     def interpret (self):
425         """Set durations and starting points."""
426         
427         part_list = self.get_part_list ()
428         
429         now = Rational (0)
430         factor = Rational (1)
431         attributes_dict = {}
432         attributes_object = None
433         measures = self.get_typed_children (Measure)
434         last_moment = Rational (-1)
435         last_measure_position = Rational (-1)
436         for m in measures:
437             measure_start_moment = now
438             measure_position = Rational (0)
439             for n in m.get_all_children ():
440                 if isinstance (n, Hash_text):
441                     continue
442                 dur = Rational (0)
443
444                 if n.__class__ == Attributes:
445                     n.set_attributes_from_previous (attributes_dict)
446                     n.read_self ()
447                     attributes_dict = n._dict.copy ()
448                     attributes_object = n
449                     
450                     factor = Rational (1,
451                                        int (attributes_dict.get ('divisions').get_text ()))
452
453                 
454                 if (n.get_maybe_exist_typed_child (Duration)):
455                     mxl_dur = n.get_maybe_exist_typed_child (Duration)
456                     dur = mxl_dur.get_length () * factor
457                     
458                     if n.get_name() == 'backup':
459                         dur = - dur
460                     if n.get_maybe_exist_typed_child (Grace):
461                         dur = Rational (0)
462
463                     rest = n.get_maybe_exist_typed_child (Rest)
464                     if (rest
465                         and attributes_object
466                         and attributes_object.get_measure_length () == dur):
467
468                         rest._is_whole_measure = True
469
470                 if (dur > Rational (0) 
471                     and n.get_maybe_exist_typed_child (Chord)):
472                     now = last_moment
473                     measure_position = last_measure_position
474
475                 n._when = now
476                 n._measure_position = measure_position
477                 n._duration = dur
478                 if dur > Rational (0):
479                     last_moment = now
480                     last_measure_position = measure_position
481                     now += dur
482                     measure_position += dur
483                 elif dur < Rational (0):
484                     # backup element, reset measure position
485                     now += dur
486                     measure_position += dur
487                     if measure_position < 0:
488                         # backup went beyond the measure start => reset to 0
489                         now -= measure_position
490                         measure_position = 0
491                     last_moment = now
492                     last_measure_position = measure_position
493                 if n._name == 'note':
494                     instrument = n.get_maybe_exist_named_child ('instrument')
495                     if instrument:
496                         n.instrument_name = part_list.get_instrument (instrument.id)
497
498             if attributes_object:
499                 length = attributes_object.get_measure_length ()
500                 new_now = measure_start_moment + length
501                 
502                 if now <> new_now:
503                     problem = 'incomplete'
504                     if now > new_now:
505                         problem = 'overfull'
506
507                     ## only for verbose operation.
508                     if problem <> 'incomplete':
509                         m.message ('%s measure? Expected: %s, Difference: %s' % (problem, now, new_now - now))
510
511                 now = new_now
512
513     def extract_voices (part):
514         voices = {}
515         measures = part.get_typed_children (Measure)
516         elements = []
517         for m in measures:
518             elements.extend (m.get_all_children ())
519
520         start_attr = None
521         for n in elements:
522             voice_id = n.get_maybe_exist_typed_child (get_class ('voice'))
523
524             # TODO: If the first element of a voice is a dynamics entry,
525             #       then voice_id is not yet set! Thus it will currently be ignored
526             if not (voice_id or isinstance (n, Attributes) or isinstance (n, Direction) ):
527                 continue
528
529             if isinstance (n, Attributes) and not start_attr:
530                 start_attr = n
531                 continue
532
533             if isinstance (n, Attributes) or isinstance (n, Direction):
534                 for v in voices.values ():
535                     v.add_element (n)
536                 continue
537
538             id = voice_id.get_text ()
539             if not voices.has_key (id):
540                 voices[id] = Musicxml_voice()
541
542             voices[id].add_element (n)
543
544         if start_attr:
545             for (k,v) in voices.items ():
546                 v.insert (0, start_attr)
547
548         part._voices = voices
549
550     def get_voices (self):
551         return self._voices
552
553 class Notations (Music_xml_node):
554     def get_tie (self):
555         ts = self.get_named_children ('tied')
556         starts = [t for t in ts if t.type == 'start']
557         if starts:
558             return starts[0]
559         else:
560             return None
561
562     def get_tuplet (self):
563         return self.get_maybe_exist_typed_child (Tuplet)
564
565 class Time_modification(Music_xml_node):
566     def get_fraction (self):
567         b = self.get_maybe_exist_named_child ('actual-notes')
568         a = self.get_maybe_exist_named_child ('normal-notes')
569         return (int(a.get_text ()), int (b.get_text ()))
570
571 class Accidental (Music_xml_node):
572     def __init__ (self):
573         Music_xml_node.__init__ (self)
574         self.editorial = False
575         self.cautionary = False
576
577 class Music_xml_spanner (Music_xml_node):
578     def get_type (self):
579         if hasattr (self, 'type'):
580             return self.type
581         else:
582             return 0
583     def get_size (self):
584         if hasattr (self, 'size'):
585             return string.atoi (self.size)
586         else:
587             return 0
588
589 class Wedge (Music_xml_spanner):
590     pass
591
592 class Tuplet (Music_xml_spanner):
593     pass
594
595 class Slur (Music_xml_spanner):
596     def get_type (self):
597         return self.type
598
599 class Beam (Music_xml_spanner):
600     def get_type (self):
601         return self.get_text ()
602     def is_primary (self):
603         return self.number == "1"
604
605 class Wavy_line (Music_xml_spanner):
606     pass
607     
608 class Pedal (Music_xml_spanner):
609     pass
610
611 class Glissando (Music_xml_spanner):
612     pass
613
614 class Octave_shift (Music_xml_spanner):
615     # default is 8 for the octave-shift!
616     def get_size (self):
617         if hasattr (self, 'size'):
618             return string.atoi (self.size)
619         else:
620             return 8
621
622 class Chord (Music_xml_node):
623     pass
624
625 class Dot (Music_xml_node):
626     pass
627
628 # Rests in MusicXML are <note> blocks with a <rest> inside. This class is only
629 # for the inner <rest> element, not the whole rest block.
630 class Rest (Music_xml_node):
631     def __init__ (self):
632         Music_xml_node.__init__ (self)
633         self._is_whole_measure = False
634     def is_whole_measure (self):
635         return self._is_whole_measure
636     def get_step (self):
637         ch = self.get_maybe_exist_typed_child (get_class (u'display-step'))
638         if ch:
639             step = ch.get_text ().strip ()
640             return step
641         else:
642             return None
643     def get_octave (self):
644         ch = self.get_maybe_exist_typed_child (get_class (u'display-octave'))
645         if ch:
646             step = ch.get_text ().strip ()
647             return int (step)
648         else:
649             return None
650
651 class Type (Music_xml_node):
652     pass
653 class Grace (Music_xml_node):
654     pass
655 class Staff (Music_xml_node):
656     pass
657
658 class Direction (Music_xml_node):
659     pass
660 class DirType (Music_xml_node):
661     pass
662
663 class Bend (Music_xml_node):
664     def bend_alter (self):
665         alter = self.get_maybe_exist_named_child ('bend-alter')
666         if alter:
667             return alter.get_text()
668         else:
669             return 0
670
671
672
673 ## need this, not all classes are instantiated
674 ## for every input file. Only add those classes, that are either directly
675 ## used by class name or extend Music_xml_node in some way!
676 class_dict = {
677         '#comment': Hash_comment,
678         '#text': Hash_text,
679         'accidental': Accidental,
680         'attributes': Attributes,
681         'beam' : Beam,
682         'bend' : Bend,
683         'chord': Chord,
684         'dot': Dot,
685         'direction': Direction,
686         'direction-type': DirType,
687         'duration': Duration,
688         'glissando': Glissando,
689         'grace': Grace,
690         'identification': Identification,
691         'lyric': Lyric,
692         'measure': Measure,
693         'notations': Notations,
694         'note': Note,
695         'octave-shift': Octave_shift,
696         'part': Part,
697         'part-list': Part_list,
698         'pedal': Pedal,
699         'pitch': Pitch,
700         'rest': Rest,
701         'slur': Slur,
702         'staff': Staff,
703         'syllabic': Syllabic,
704         'text': Text,
705         'time-modification': Time_modification,
706         'tuplet': Tuplet,
707         'type': Type,
708         'wavy-line': Wavy_line,
709         'wedge': Wedge,
710         'work': Work,
711 }
712
713 def name2class_name (name):
714     name = name.replace ('-', '_')
715     name = name.replace ('#', 'hash_')
716     name = name[0].upper() + name[1:].lower()
717
718     return str (name)
719
720 def get_class (name):
721     classname = class_dict.get (name)
722     if classname:
723         return classname
724     else:
725         class_name = name2class_name (name)
726         klass = new.classobj (class_name, (Music_xml_node,) , {})
727         class_dict[name] = klass
728         return klass
729         
730 def lxml_demarshal_node (node):
731     name = node.tag
732
733     if name is None:
734         return None
735     klass = get_class (name)
736     py_node = klass()
737     
738     py_node._original = node
739     py_node._name = name
740     py_node._data = node.text
741     py_node._children = [lxml_demarshal_node (cn) for cn in node.getchildren()]
742     py_node._children = filter (lambda x: x, py_node._children)
743     
744     for c in py_node._children:
745         c._parent = py_node
746
747     for (k,v) in node.items ():
748         py_node.__dict__[k] = v
749         py_node._attribute_dict[k] = v
750
751     return py_node
752
753 def minidom_demarshal_node (node):
754     name = node.nodeName
755
756     klass = get_class (name)
757     py_node = klass()
758     py_node._name = name
759     py_node._children = [minidom_demarshal_node (cn) for cn in node.childNodes]
760     for c in py_node._children:
761         c._parent = py_node
762
763     if node.attributes:
764         for (nm, value) in node.attributes.items():
765             py_node.__dict__[nm] = value
766             py_node._attribute_dict[nm] = value
767             
768     py_node._data = None
769     if node.nodeType == node.TEXT_NODE and node.data:
770         py_node._data = node.data 
771
772     py_node._original = node
773     return py_node
774
775
776 if __name__  == '__main__':
777         import lxml.etree
778         
779         tree = lxml.etree.parse ('beethoven.xml')
780         mxl_tree = lxml_demarshal_node (tree.getroot ())
781         ks = class_dict.keys()
782         ks.sort()
783         print '\n'.join (ks)