]> git.donarmstrong.com Git - lilypond.git/blob - python/musicxml.py
MusicXML: don't use the class_dict directly
[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 (get_class (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 (get_class (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 ''
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 class Duration (Music_xml_node):
154     def get_length (self):
155         dur = int (self.get_text ()) * Rational (1,4)
156         return dur
157
158 class Hash_comment (Music_xml_node):
159     pass
160
161 class Pitch (Music_xml_node):
162     def get_step (self):
163         ch = self.get_unique_typed_child (get_class (u'step'))
164         step = ch.get_text ().strip ()
165         return step
166     def get_octave (self):
167         ch = self.get_unique_typed_child (get_class (u'octave'))
168
169         step = ch.get_text ().strip ()
170         return int (step)
171
172     def get_alteration (self):
173         ch = self.get_maybe_exist_typed_child (get_class (u'alter'))
174         alter = 0
175         if ch:
176             alter = int (ch.get_text ().strip ())
177         return alter
178
179 class Measure_element (Music_xml_node):
180     def get_voice_id (self):
181         voice_id = self.get_maybe_exist_named_child ('voice')
182         if voice_id:
183             return voice_id.get_text ()
184         else:
185             return None
186
187     def is_first (self):
188         cn = self._parent.get_typed_children (self.__class__)
189         cn = [c for c in cn if c.get_voice_id () == self.get_voice_id ()]
190         return cn[0] == self
191
192 class Attributes (Measure_element):
193     def __init__ (self):
194         Measure_element.__init__ (self)
195         self._dict = {}
196
197     def set_attributes_from_previous (self, dict):
198         self._dict.update (dict)
199         
200     def read_self (self):
201         for c in self.get_all_children ():
202             self._dict[c.get_name()] = c
203
204     def get_named_attribute (self, name):
205         return self._dict.get (name)
206
207     def get_measure_length (self):
208         (n,d) = self.get_time_signature ()
209         return Rational (n,d)
210         
211     def get_time_signature (self):
212         "return time sig as a (beat, beat-type) tuple"
213
214         try:
215             mxl = self.get_named_attribute ('time')
216             
217             beats = mxl.get_maybe_exist_named_child ('beats')
218             type = mxl.get_maybe_exist_named_child ('beat-type')
219             return (int (beats.get_text ()),
220                     int (type.get_text ()))
221         except KeyError:
222             print 'error: requested time signature, but time sig unknown'
223             return (4, 4)
224
225     def get_clef_sign (self):
226         mxl = self.get_named_attribute ('clef')
227         sign = mxl.get_maybe_exist_named_child ('sign')
228         if sign:
229             return sign.get_text ()
230         else:
231             print 'clef requested, but unknow'
232             return 'G'
233
234     def get_key_signature (self):
235         "return (fifths, mode) tuple"
236
237         key = self.get_named_attribute ('key')
238         mode_node = key.get_maybe_exist_named_child ('mode')
239         mode = 'major'
240         if mode_node:
241             mode = mode_node.get_text ()
242
243         fifths = int (key.get_maybe_exist_named_child ('fifths').get_text ())
244         return (fifths, mode)
245                 
246
247 class Note (Measure_element):
248     def __init__ (self):
249         Measure_element.__init__ (self)
250         self.instrument_name = ''
251         
252     def get_duration_log (self):
253         ch = self.get_maybe_exist_typed_child (get_class (u'type'))
254
255         if ch:
256             log = ch.get_text ().strip()
257             return {'eighth': 3,
258                     'quarter': 2,
259                     'half': 1,
260                     '16th': 4,
261                     '32nd': 5,
262                      'breve': -1,
263                     'long': -2,
264                     'whole': 0}.get (log)
265         else:
266             return 0
267
268     def get_factor (self):
269         return 1
270
271     def get_pitches (self):
272         return self.get_typed_children (get_class (u'pitch'))
273
274 class Part_list (Music_xml_node):
275     def __init__ (self):
276         Music_xml_node.__init__ (self)
277         self._id_instrument_name_dict = {}
278         
279     def generate_id_instrument_dict (self):
280
281         ## not empty to make sure this happens only once.
282         mapping = {1: 1}
283         for score_part in self.get_named_children ('score-part'):
284             for instr in score_part.get_named_children ('score-instrument'):
285                 id = instr.id
286                 name = instr.get_named_child ("instrument-name")
287                 mapping[id] = name.get_text ()
288
289         self._id_instrument_name_dict = mapping
290
291     def get_instrument (self, id):
292         if not self._id_instrument_name_dict:
293             self.generate_id_instrument_dict()
294
295         instrument_name = self._id_instrument_name_dict.get (id)
296         if instrument_name:
297             return instrument_name
298         else:
299             print "Opps, couldn't find instrument for ID=", id
300             return "Grand Piano"
301         
302 class Measure(Music_xml_node):
303     def get_notes (self):
304         return self.get_typed_children (get_class (u'note'))
305
306     
307 class Musicxml_voice:
308     def __init__ (self):
309         self._elements = []
310         self._staves = {}
311         self._start_staff = None
312
313     def add_element (self, e):
314         self._elements.append (e)
315         if (isinstance (e, Note)
316             and e.get_maybe_exist_typed_child (Staff)):
317             name = e.get_maybe_exist_typed_child (Staff).get_text ()
318
319             if not self._start_staff:
320                 self._start_staff = name
321             self._staves[name] = True
322
323     def insert (self, idx, e):
324         self._elements.insert (idx, e)
325
326
327
328 class Part (Music_xml_node):
329     def __init__ (self):
330         Music_xml_node.__init__ (self)
331         self._voices = []
332
333     def get_part_list (self):
334         n = self
335         while n and n.get_name() != 'score-partwise':
336             n = n._parent
337
338         return n.get_named_child ('part-list')
339         
340     def interpret (self):
341         """Set durations and starting points."""
342         
343         part_list = self.get_part_list ()
344         
345         now = Rational (0)
346         factor = Rational (1)
347         attributes_dict = {}
348         attributes_object = None
349         measures = self.get_typed_children (Measure)
350         last_moment = Rational (-1)
351         last_measure_position = Rational (-1)
352         for m in measures:
353             measure_start_moment = now
354             measure_position = Rational (0)
355             for n in m.get_all_children ():
356                 dur = Rational (0)
357
358                 if n.__class__ == Attributes:
359                     n.set_attributes_from_previous (attributes_dict)
360                     n.read_self ()
361                     attributes_dict = n._dict.copy ()
362                     attributes_object = n
363                     
364                     factor = Rational (1,
365                                        int (attributes_dict.get ('divisions').get_text ()))
366
367                 
368                 if (n.get_maybe_exist_typed_child (Duration)):
369                     mxl_dur = n.get_maybe_exist_typed_child (Duration)
370                     dur = mxl_dur.get_length () * factor
371                     
372                     if n.get_name() == 'backup':
373                         dur = - dur
374                     if n.get_maybe_exist_typed_child (Grace):
375                         dur = Rational (0)
376
377                     rest = n.get_maybe_exist_typed_child (Rest)
378                     if (rest
379                         and attributes_object
380                         and attributes_object.get_measure_length () == dur):
381
382                         rest._is_whole_measure = True
383
384                 if (dur > Rational (0) 
385                     and n.get_maybe_exist_typed_child (Chord)):
386                     now = last_moment
387                     measure_position = last_measure_position
388                     
389                 last_moment = now
390                 last_measure_position = measure_position
391
392                 n._when = now
393                 n._measure_position = measure_position
394                 n._duration = dur
395                 now += dur
396                 measure_position += dur
397                 if n._name == 'note':
398                     instrument = n.get_maybe_exist_named_child ('instrument')
399                     if instrument:
400                         n.instrument_name = part_list.get_instrument (instrument.id)
401
402             if attributes_object:
403                 length = attributes_object.get_measure_length ()
404                 new_now = measure_start_moment + length
405                 
406                 if now <> new_now:
407                     problem = 'incomplete'
408                     if now > new_now:
409                         problem = 'overfull'
410
411                     ## only for verbose operation.
412                     if problem <> 'incomplete':
413                         m.message ('%s measure? Expected: %s, Difference: %s' % (problem, now, new_now - now))
414
415                 now = new_now
416
417     def extract_voices (part):
418         voices = {}
419         measures = part.get_typed_children (Measure)
420         elements = []
421         for m in measures:
422             elements.extend (m.get_all_children ())
423
424         start_attr = None
425         for n in elements:
426             voice_id = n.get_maybe_exist_typed_child (get_class ('voice'))
427
428             # TODO: If the first element of a voice is a dynamics entry,
429             #       then voice_id is not yet set! Thus it will currently be ignored
430             if not (voice_id or isinstance (n, Attributes) or isinstance (n, Direction) ):
431                 continue
432
433             if isinstance (n, Attributes) and not start_attr:
434                 start_attr = n
435                 continue
436
437             if isinstance (n, Attributes) or isinstance (n, Direction):
438                 for v in voices.values ():
439                     v.add_element (n)
440                 continue
441
442             id = voice_id.get_text ()
443             if not voices.has_key (id):
444                 voices[id] = Musicxml_voice()
445
446             voices[id].add_element (n)
447
448         if start_attr:
449             for (k,v) in voices.items ():
450                 v.insert (0, start_attr)
451
452         part._voices = voices
453
454     def get_voices (self):
455         return self._voices
456
457 class Notations (Music_xml_node):
458     def get_tie (self):
459         ts = self.get_named_children ('tied')
460         starts = [t for t in ts if t.type == 'start']
461         if starts:
462             return starts[0]
463         else:
464             return None
465
466     def get_tuplet (self):
467         return self.get_maybe_exist_typed_child (Tuplet)
468
469 class Time_modification(Music_xml_node):
470     def get_fraction (self):
471         b = self.get_maybe_exist_named_child ('actual-notes')
472         a = self.get_maybe_exist_named_child ('normal-notes')
473         return (int(a.get_text ()), int (b.get_text ()))
474
475 class Accidental (Music_xml_node):
476     def __init__ (self):
477         Music_xml_node.__init__ (self)
478         self.editorial = False
479         self.cautionary = False
480
481
482 class Tuplet(Music_xml_node):
483     pass
484
485 class Slur (Music_xml_node):
486     def get_type (self):
487         return self.type
488
489 class Beam (Music_xml_node):
490     def get_type (self):
491         return self.get_text ()
492     def is_primary (self):
493         return self.number == "1"
494     
495 class Chord (Music_xml_node):
496     pass
497
498 class Dot (Music_xml_node):
499     pass
500
501 class Rest (Music_xml_node):
502     def __init__ (self):
503         Music_xml_node.__init__ (self)
504         self._is_whole_measure = False
505     def is_whole_measure (self):
506         return self._is_whole_measure
507
508 class Type (Music_xml_node):
509     pass
510 class Grace (Music_xml_node):
511     pass
512 class Staff (Music_xml_node):
513     pass
514
515 class Direction (Music_xml_node):
516     pass
517 class DirType (Music_xml_node):
518     pass
519
520
521 ## need this, not all classes are instantiated
522 ## for every input file. Only add those classes, that are either directly
523 ## used by class name or extend Music_xml_node in some way!
524 class_dict = {
525         '#comment': Hash_comment,
526         'accidental': Accidental,
527         'attributes': Attributes,
528         'beam' : Beam,
529         'chord': Chord,
530         'dot': Dot,
531         'direction': Direction,
532         'direction-type': DirType,
533         'duration': Duration,
534         'grace': Grace,
535         'identification': Identification,
536         'measure': Measure,
537         'notations': Notations,
538         'note': Note,
539         'part': Part,
540         'part-list': Part_list,
541         'pitch': Pitch,
542         'rest': Rest,
543         'slur': Slur,
544         'time-modification': Time_modification,
545         'type': Type,
546         'work': Work,
547 }
548
549 def name2class_name (name):
550     name = name.replace ('-', '_')
551     name = name.replace ('#', 'hash_')
552     name = name[0].upper() + name[1:].lower()
553
554     return str (name)
555
556 def get_class (name):
557     classname = class_dict.get (name)
558     if classname:
559         return classname
560     else:
561         class_name = name2class_name (name)
562         klass = new.classobj (class_name, (Music_xml_node,) , {})
563         class_dict[name] = klass
564         return klass
565         
566 def lxml_demarshal_node (node):
567     name = node.tag
568
569     if name is None:
570         return None
571     klass = get_class (name)
572     py_node = klass()
573     
574     py_node._original = node
575     py_node._name = name
576     py_node._data = node.text
577     py_node._children = [lxml_demarshal_node (cn) for cn in node.getchildren()]
578     py_node._children = filter (lambda x: x, py_node._children)
579     
580     for c in py_node._children:
581         c._parent = py_node
582
583     for (k,v) in node.items ():
584         py_node.__dict__[k] = v
585         py_node._attribute_dict[k] = v
586
587     return py_node
588
589 def minidom_demarshal_node (node):
590     name = node.nodeName
591
592     klass = get_class (name)
593     py_node = klass()
594     py_node._name = name
595     py_node._children = [minidom_demarshal_node (cn) for cn in node.childNodes]
596     for c in py_node._children:
597         c._parent = py_node
598
599     if node.attributes:
600         for (nm, value) in node.attributes.items():
601             py_node.__dict__[nm] = value
602             py_node._attribute_dict[nm] = value
603             
604     py_node._data = None
605     if node.nodeType == node.TEXT_NODE and node.data:
606         py_node._data = node.data 
607
608     py_node._original = node
609     return py_node
610
611
612 if __name__  == '__main__':
613         import lxml.etree
614         
615         tree = lxml.etree.parse ('beethoven.xml')
616         mxl_tree = lxml_demarshal_node (tree.getroot ())
617         ks = class_dict.keys()
618         ks.sort()
619         print '\n'.join (ks)