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