]> git.donarmstrong.com Git - lilypond.git/blob - python/musicxml.py
3466473c4352a879c2d1259e46f4f3b6d90f6f4a
[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         return [c for c in self._children if isinstance(c, klass)]
43
44     def get_named_children (self, nm):
45         return self.get_typed_children (class_dict[nm])
46
47     def get_named_child (self, nm):
48         return self.get_maybe_exist_named_child (nm)
49
50     def get_children (self, predicate):
51         return [c for c in self._children if predicate(c)]
52
53     def get_all_children (self):
54         return self._children
55
56     def get_maybe_exist_named_child (self, name):
57         return self.get_maybe_exist_typed_child (class_dict[name])
58
59     def get_maybe_exist_typed_child (self, klass):
60         cn = self.get_typed_children (klass)
61         if len (cn)==0:
62             return None
63         elif len (cn) == 1:
64             return cn[0]
65         else:
66             raise "More than 1 child", klass
67
68     def get_unique_typed_child (self, klass):
69         cn = self.get_typed_children(klass)
70         if len (cn) <> 1:
71             print self.__dict__ 
72             raise 'Child is not unique for', (klass, 'found', cn)
73
74         return cn[0]
75
76 class Music_xml_node (Xml_node):
77     def __init__ (self):
78         Xml_node.__init__ (self)
79         self.duration = Rational (0)
80         self.start = Rational (0)
81
82
83 class Duration (Music_xml_node):
84     def get_length (self):
85         dur = int (self.get_text ()) * Rational (1,4)
86         return dur
87
88 class Hash_comment (Music_xml_node):
89     pass
90
91 class Pitch (Music_xml_node):
92     def get_step (self):
93         ch = self.get_unique_typed_child (class_dict[u'step'])
94         step = ch.get_text ().strip ()
95         return step
96     def get_octave (self):
97         ch = self.get_unique_typed_child (class_dict[u'octave'])
98
99         step = ch.get_text ().strip ()
100         return int (step)
101
102     def get_alteration (self):
103         ch = self.get_maybe_exist_typed_child (class_dict[u'alter'])
104         alter = 0
105         if ch:
106             alter = int (ch.get_text ().strip ())
107         return alter
108
109 class Measure_element (Music_xml_node):
110     def get_voice_id (self):
111         voice_id = self.get_maybe_exist_named_child ('voice')
112         if voice_id:
113             return voice_id.get_text ()
114         else:
115             return None
116
117     def is_first (self):
118         cn = self._parent.get_typed_children (self.__class__)
119         cn = [c for c in cn if c.get_voice_id () == self.get_voice_id ()]
120         return cn[0] == self
121
122 class Attributes (Measure_element):
123     def __init__ (self):
124         Measure_element.__init__ (self)
125         self._dict = {}
126
127     def set_attributes_from_previous (self, dict):
128         self._dict.update (dict)
129         
130     def read_self (self):
131         for c in self.get_all_children ():
132             self._dict[c.get_name()] = c
133
134     def get_named_attribute (self, name):
135         return self._dict[name]
136
137     def get_measure_length (self):
138         (n,d) = self.get_time_signature ()
139         return Rational (n,d)
140         
141     def get_time_signature (self):
142         "return time sig as a (beat, beat-type) tuple"
143
144         try:
145             mxl = self.get_named_attribute ('time')
146             
147             beats = mxl.get_maybe_exist_named_child ('beats')
148             type = mxl.get_maybe_exist_named_child ('beat-type')
149             return (int (beats.get_text ()),
150                     int (type.get_text ()))
151         except KeyError:
152             print 'error: requested time signature, but time sig unknown'
153             return (4, 4)
154
155     def get_clef_sign (self):
156         mxl = self.get_named_attribute ('clef')
157         sign = mxl.get_maybe_exist_named_child ('sign')
158         if sign:
159             return sign.get_text ()
160         else:
161             print 'clef requested, but unknow'
162             return 'G'
163
164     def get_key_signature (self):
165         "return (fifths, mode) tuple"
166
167         key = self.get_named_attribute ('key')
168         mode_node = key.get_maybe_exist_named_child ('mode')
169         mode = 'major'
170         if mode_node:
171             mode = mode_node.get_text ()
172
173         fifths = int (key.get_maybe_exist_named_child ('fifths').get_text ())
174         return (fifths, mode)
175                 
176
177 class Note (Measure_element):
178     def __init__ (self):
179         Measure_element.__init__ (self)
180         self.instrument_name = ''
181         
182     def get_duration_log (self):
183         ch = self.get_maybe_exist_typed_child (class_dict[u'type'])
184
185         if ch:
186             log = ch.get_text ().strip()
187             return {'eighth': 3,
188                     'quarter': 2,
189                     'half': 1,
190                     '16th': 4,
191                     '32nd': 5,
192                      'breve': -1,
193                     'long': -2,
194                     'whole': 0} [log]
195         else:
196             return 0
197
198     def get_factor (self):
199         return 1
200
201     def get_pitches (self):
202         return self.get_typed_children (class_dict[u'pitch'])
203
204 class Part_list (Music_xml_node):
205     def __init__ (self):
206         Music_xml_node.__init__ (self)
207         self._id_instrument_name_dict = {}
208         
209     def generate_id_instrument_dict (self):
210
211         ## not empty to make sure this happens only once.
212         mapping = {1: 1}
213         for score_part in self.get_named_children ('score-part'):
214             for instr in score_part.get_named_children ('score-instrument'):
215                 id = instr.id
216                 name = instr.get_named_child ("instrument-name")
217                 mapping[id] = name.get_text ()
218
219         self._id_instrument_name_dict = mapping
220
221     def get_instrument (self, id):
222         if not self._id_instrument_name_dict:
223             self.generate_id_instrument_dict()
224
225         try:
226             return self._id_instrument_name_dict[id]
227         except KeyError:
228             print "Opps, couldn't find instrument for ID=", id
229             return "Grand Piano"
230         
231 class Measure(Music_xml_node):
232     def get_notes (self):
233         return self.get_typed_children (class_dict[u'note'])
234
235     
236 class Musicxml_voice:
237     def __init__ (self):
238         self._elements = []
239         self._staves = {}
240         self._start_staff = None
241
242     def add_element (self, e):
243         self._elements.append (e)
244         if (isinstance (e, Note)
245             and e.get_maybe_exist_typed_child (Staff)):
246             name = e.get_maybe_exist_typed_child (Staff).get_text ()
247
248             if not self._start_staff:
249                 self._start_staff = name
250             self._staves[name] = True
251
252     def insert (self, idx, e):
253         self._elements.insert (idx, e)
254
255
256
257 class Part (Music_xml_node):
258     def __init__ (self):
259         Music_xml_node.__init__ (self)
260         self._voices = []
261
262     def get_part_list (self):
263         n = self
264         while n and n.get_name() != 'score-partwise':
265             n = n._parent
266
267         return n.get_named_child ('part-list')
268         
269     def interpret (self):
270         """Set durations and starting points."""
271         
272         part_list = self.get_part_list ()
273         
274         now = Rational (0)
275         factor = Rational (1)
276         attributes_dict = {}
277         attributes_object = None
278         measures = self.get_typed_children (Measure)
279         last_moment = Rational (-1)
280         last_measure_position = Rational (-1)
281         for m in measures:
282             measure_start_moment = now
283             measure_position = Rational (0)
284             for n in m.get_all_children ():
285                 dur = Rational (0)
286
287                 if n.__class__ == Attributes:
288                     n.set_attributes_from_previous (attributes_dict)
289                     n.read_self ()
290                     attributes_dict = n._dict.copy ()
291                     attributes_object = n
292                     
293                     factor = Rational (1,
294                                        int (attributes_dict['divisions'].get_text ()))
295
296                 
297                 if (n.get_maybe_exist_typed_child (Duration)):
298                     mxl_dur = n.get_maybe_exist_typed_child (Duration)
299                     dur = mxl_dur.get_length () * factor
300                     
301                     if n.get_name() == 'backup':
302                         dur = - dur
303                     if n.get_maybe_exist_typed_child (Grace):
304                         dur = Rational (0)
305
306                     rest = n.get_maybe_exist_typed_child (Rest)
307                     if (rest
308                         and attributes_object
309                         and attributes_object.get_measure_length () == dur):
310
311                         rest._is_whole_measure = True
312
313                 if (dur > Rational (0) 
314                     and n.get_maybe_exist_typed_child (Chord)):
315                     now = last_moment
316                     measure_position = last_measure_position
317                     
318                 last_moment = now
319                 last_measure_position = measure_position
320
321                 n._when = now
322                 n._measure_position = measure_position
323                 n._duration = dur
324                 now += dur
325                 measure_position += dur
326                 if n._name == 'note':
327                     instrument = n.get_maybe_exist_named_child ('instrument')
328                     if instrument:
329                         n.instrument_name = part_list.get_instrument (instrument.id)
330
331             if attributes_object:
332                 length = attributes_object.get_measure_length ()
333                 new_now = measure_start_moment + length
334                 
335                 if now <> new_now:
336                     problem = 'incomplete'
337                     if now > new_now:
338                         problem = 'overfull'
339
340                     ## only for verbose operation.
341                     if problem <> 'incomplete':
342                         m.message ('%s measure? Expected: %s, Difference: %s' % (problem, now, new_now - now))
343
344                 now = new_now
345
346     def extract_voices (part):
347         voices = {}
348         measures = part.get_typed_children (Measure)
349         elements = []
350         for m in measures:
351             elements.extend (m.get_all_children ())
352
353         start_attr = None
354         for n in elements:
355             voice_id = n.get_maybe_exist_typed_child (class_dict['voice'])
356
357             if not (voice_id or isinstance (n, Attributes)):
358                 continue
359
360             if isinstance (n, Attributes) and not start_attr:
361                 start_attr = n
362                 continue
363
364             if isinstance (n, Attributes):
365                 for v in voices.values ():
366                     v.add_element (n)
367                 continue
368
369             id = voice_id.get_text ()
370             if not voices.has_key (id):
371                 voices[id] = Musicxml_voice()
372
373             voices[id].add_element (n)
374
375         if start_attr:
376             for (k,v) in voices.items ():
377                 v.insert (0, start_attr)
378
379         part._voices = voices
380
381     def get_voices (self):
382         return self._voices
383
384 class Notations (Music_xml_node):
385     def get_tie (self):
386         ts = self.get_named_children ('tied')
387         starts = [t for t in ts if t.type == 'start']
388         if starts:
389             return starts[0]
390         else:
391             return None
392
393     def get_tuplet (self):
394         return self.get_maybe_exist_typed_child (Tuplet)
395
396 class Time_modification(Music_xml_node):
397     def get_fraction (self):
398         b = self.get_maybe_exist_typed_child (class_dict['actual-notes'])
399         a = self.get_maybe_exist_typed_child (class_dict['normal-notes'])
400         return (int(a.get_text ()), int (b.get_text ()))
401
402 class Accidental (Music_xml_node):
403     def __init__ (self):
404         Music_xml_node.__init__ (self)
405         self.editorial = False
406         self.cautionary = False
407
408
409 class Tuplet(Music_xml_node):
410     pass
411
412 class Slur (Music_xml_node):
413     def get_type (self):
414         return self.type
415
416 class Beam (Music_xml_node):
417     def get_type (self):
418         return self.get_text ()
419     def is_primary (self):
420         return self.number == "1"
421     
422 class Chord (Music_xml_node):
423     pass
424
425 class Dot (Music_xml_node):
426     pass
427
428 class Alter (Music_xml_node):
429     pass
430
431 class Rest (Music_xml_node):
432     def __init__ (self):
433         Music_xml_node.__init__ (self)
434         self._is_whole_measure = False
435     def is_whole_measure (self):
436         return self._is_whole_measure
437
438 class Mode (Music_xml_node):
439     pass
440 class Tied (Music_xml_node):
441     pass
442
443 class Type (Music_xml_node):
444     pass
445 class Grace (Music_xml_node):
446     pass
447 class Staff (Music_xml_node):
448     pass
449
450 class Instrument (Music_xml_node):
451     pass
452
453 ## need this, not all classes are instantiated
454 ## for every input file.
455 class_dict = {
456         '#comment': Hash_comment,
457         'accidental': Accidental,
458         'alter': Alter,
459         'attributes': Attributes,
460         'beam' : Beam,
461         'chord': Chord,
462         'dot': Dot,
463         'duration': Duration,
464         'grace': Grace,
465         'instrument': Instrument, 
466         'mode' : Mode,
467         'measure': Measure,
468         'notations': Notations,
469         'note': Note,
470         'part': Part,
471         'pitch': Pitch,
472         'rest':Rest,
473         'slur': Slur,
474         'tied': Tied,
475         'time-modification': Time_modification,
476         'tuplet': Tuplet,
477         'type': Type,
478         'part-list': Part_list,
479         'staff': Staff,
480 }
481
482 def name2class_name (name):
483     name = name.replace ('-', '_')
484     name = name.replace ('#', 'hash_')
485     name = name[0].upper() + name[1:].lower()
486
487     return str (name)
488
489 def get_class (name):
490     try:
491         return class_dict[name]
492     except KeyError:
493         class_name = name2class_name (name)
494         klass = new.classobj (class_name, (Music_xml_node,) , {})
495         class_dict[name] = klass
496         return klass
497         
498 def lxml_demarshal_node (node):
499     name = node.tag
500
501     if name is None:
502         return None
503     klass = get_class (name)
504     py_node = klass()
505     
506     py_node._original = node
507     py_node._name = name
508     py_node._data = node.text
509     py_node._children = [lxml_demarshal_node (cn) for cn in node.getchildren()]
510     py_node._children = filter (lambda x: x, py_node._children)
511     
512     for c in py_node._children:
513         c._parent = py_node
514
515     for (k,v) in node.items ():
516         py_node.__dict__[k] = v
517         py_node._attribute_dict[k] = v
518
519     return py_node
520
521 def minidom_demarshal_node (node):
522     name = node.nodeName
523
524     klass = get_class (name)
525     py_node = klass()
526     py_node._name = name
527     py_node._children = [minidom_demarshal_node (cn) for cn in node.childNodes]
528     for c in py_node._children:
529         c._parent = py_node
530
531     if node.attributes:
532         for (nm, value) in node.attributes.items():
533             py_node.__dict__[nm] = value
534             py_node._attribute_dict[nm] = value
535             
536     py_node._data = None
537     if node.nodeType == node.TEXT_NODE and node.data:
538         py_node._data = node.data 
539
540     py_node._original = node
541     return py_node
542
543
544 if __name__  == '__main__':
545         import lxml.etree
546         
547         tree = lxml.etree.parse ('beethoven.xml')
548         mxl_tree = lxml_demarshal_node (tree.getroot ())
549         ks = class_dict.keys()
550         ks.sort()
551         print '\n'.join (ks)