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