]> git.donarmstrong.com Git - lilypond.git/blob - python/musicxml.py
*** empty log message ***
[lilypond.git] / python / musicxml.py
1 import sys
2 import new
3 import re
4 import string
5 from rational import Rational
6
7 from xml.dom import minidom, Node
8
9 class Xml_node:
10     def __init__ (self):
11         self._children = []
12         self._data = None
13         self._original = None
14         self._name = 'xml_node'
15         self._parent = None
16
17     def is_first (self):
18         return self._parent.get_typed_children (self.__class__)[0] == self
19
20     def original (self):
21         return self._original 
22     def get_name (self):
23         return self._name
24
25     def get_text (self):
26         if self._data:
27             return self._data
28
29         if not self._children:
30             return ''
31
32         return ''.join ([c.get_text () for c in self._children])
33
34     def get_typed_children (self, klass):
35         return [c for c in self._children if isinstance(c, klass)]
36
37     def get_named_children (self, nm):
38         return self.get_typed_children (class_dict[nm])
39
40     def get_named_child (self, nm):
41         return self.get_maybe_exist_named_child (nm)
42
43     def get_children (self, predicate):
44         return [c for c in self._children if predicate(c)]
45
46     def get_all_children (self):
47         return self._children
48
49     def get_maybe_exist_named_child (self, name):
50         return self.get_maybe_exist_typed_child (class_dict[name])
51
52     def get_maybe_exist_typed_child (self, klass):
53         cn = self.get_typed_children (klass)
54         if len (cn)==0:
55             return None
56         elif len (cn) == 1:
57             return cn[0]
58         else:
59             raise "More than 1 child", klass
60
61     def get_unique_typed_child (self, klass):
62         cn = self.get_typed_children(klass)
63         if len (cn) <> 1:
64             print self.__dict__ 
65             raise 'Child is not unique for', (klass, 'found', cn)
66
67         return cn[0]
68
69 class Music_xml_node (Xml_node):
70     def __init__ (self):
71         Xml_node.__init__ (self)
72         self.duration = Rational (0)
73         self.start = Rational (0)
74
75
76 class Duration (Music_xml_node):
77     def get_length (self):
78         dur = string.atoi (self.get_text ()) * Rational (1,4)
79         return dur
80
81 class Hash_comment (Music_xml_node):
82     pass
83
84 class Pitch (Music_xml_node):
85     def get_step (self):
86         ch = self.get_unique_typed_child (class_dict[u'step'])
87         step = ch.get_text ().strip ()
88         return step
89     def get_octave (self):
90         ch = self.get_unique_typed_child (class_dict[u'octave'])
91
92         step = ch.get_text ().strip ()
93         return string.atoi (step)
94
95     def get_alteration (self):
96         ch = self.get_maybe_exist_typed_child (class_dict[u'alter'])
97         alter = 0
98         if ch:
99             alter = string.atoi (ch.get_text ().strip ())
100         return alter
101
102 class Measure_element (Music_xml_node):
103     def get_voice_id (self):
104         voice_id = self.get_maybe_exist_named_child ('voice')
105         if voice_id:
106             return voice_id.get_text ()
107         else:
108             return None
109
110     def is_first (self):
111         cn = self._parent.get_typed_children (self.__class__)
112         cn = [c for c in cn if c.get_voice_id () == self.get_voice_id ()]
113         return cn[0] == self
114
115 class Attributes (Measure_element):
116     def __init__ (self):
117         Measure_element.__init__ (self)
118         self._dict = {}
119
120     def set_attributes_from_previous (self, dict):
121         self._dict.update (dict)
122     def read_self (self):
123         for c in self.get_all_children ():
124             self._dict[c.get_name()] = c
125
126     def get_named_attribute (self, name):
127         return self._dict[name]
128
129 class Note (Measure_element):
130     def get_duration_log (self):
131         ch = self.get_maybe_exist_typed_child (class_dict[u'type'])
132
133         if ch:
134             log = ch.get_text ().strip()
135             return      {'eighth': 3,
136                      'quarter': 2,
137                      'half': 1,
138                      '16th': 4,
139                      '32nd': 5,
140                      'breve': -1,
141                      'long': -2,
142                      'whole': 0} [log]
143         else:
144             return 0
145
146     def get_factor (self):
147         return 1
148
149     def get_pitches (self):
150         return self.get_typed_children (class_dict[u'pitch'])
151
152 class Part_list (Music_xml_node):
153     pass
154
155 class Measure(Music_xml_node):
156     def get_notes (self):
157         return self.get_typed_children (class_dict[u'note'])
158
159
160 class Musicxml_voice:
161     def __init__ (self):
162         self._elements = []
163         self._staves = {}
164         self._start_staff = None
165
166     def add_element (self, e):
167         self._elements.append (e)
168         if (isinstance (e, Note)
169             and e.get_maybe_exist_typed_child (Staff)):
170             name = e.get_maybe_exist_typed_child (Staff).get_text ()
171
172             if not self._start_staff:
173                 self._start_staff = name
174             self._staves[name] = True
175
176     def insert (self, idx, e):
177         self._elements.insert (idx, e)
178
179
180
181 class Part (Music_xml_node):
182     def __init__ (self):
183         self._voices = []
184
185     def interpret (self):
186         """Set durations and starting points."""
187
188         now = Rational (0)
189         factor = Rational (1)
190         attr_dict = {}
191         measures = self.get_typed_children (Measure)
192
193         for m in measures:
194             for n in m.get_all_children ():
195                 dur = Rational (0)
196
197                 if n.__class__ == Attributes:
198                     n.set_attributes_from_previous (attr_dict)
199                     n.read_self ()
200                     attr_dict = n._dict.copy ()
201
202                     factor = Rational (1,
203                                        string.atoi (attr_dict['divisions']
204                                                     .get_text ()))
205                 elif (n.get_maybe_exist_typed_child (Duration)
206                       and not n.get_maybe_exist_typed_child (Chord)):
207                     mxl_dur = n.get_maybe_exist_typed_child (Duration)
208                     dur = mxl_dur.get_length () * factor
209                     if n.get_name() == 'backup':
210                         dur = - dur
211                     if n.get_maybe_exist_typed_child (Grace):
212                         dur = Rational (0)
213
214                 n._when = now
215                 n._duration = dur
216                 now += dur
217
218     def extract_voices (part):
219         voices = {}
220         measures = part.get_typed_children (Measure)
221         elements = []
222         for m in measures:
223             elements.extend (m.get_all_children ())
224
225         start_attr = None
226         for n in elements:
227             voice_id = n.get_maybe_exist_typed_child (class_dict['voice'])
228
229             if not (voice_id or isinstance (n, Attributes)):
230                 continue
231
232             if isinstance (n, Attributes) and not start_attr:
233                 start_attr = n
234                 continue
235
236             if isinstance (n, Attributes):
237                 for v in voices.values ():
238                     v.add_element (n)
239                 continue
240
241             id = voice_id.get_text ()
242             if not voices.has_key (id):
243                 voices[id] = Musicxml_voice()
244
245             voices[id].add_element (n)
246
247         if start_attr:
248             for (k,v) in voices.items ():
249                 v.insert (0, start_attr)
250
251         part._voices = voices
252
253     def get_voices (self):
254         return self._voices
255
256 class Notations (Music_xml_node):
257     def get_tie (self):
258         ts = self.get_named_children ('tied')
259         starts = [t for t in ts if t.type == 'start']
260         if starts:
261             return starts[0]
262         else:
263             return None
264
265     def get_tuplet (self):
266         return self.get_maybe_exist_typed_child (Tuplet)
267
268 class Time_modification(Music_xml_node):
269     def get_fraction (self):
270         b = self.get_maybe_exist_typed_child (class_dict['actual-notes'])
271         a = self.get_maybe_exist_typed_child (class_dict['normal-notes'])
272         return (string.atoi(a.get_text ()), string.atoi (b.get_text ()))
273
274 class Accidental (Music_xml_node):
275     def __init__ (self):
276         Music_xml_node.__init__ (self)
277         self.editorial = False
278         self.cautionary = False
279
280
281 class Tuplet(Music_xml_node):
282     pass
283
284 class Slur (Music_xml_node):
285     def get_type (self):
286         return self.type
287
288 class Beam (Music_xml_node):
289     def get_type (self):
290         return self.get_text ()
291
292 class Chord (Music_xml_node):
293     pass
294
295 class Dot (Music_xml_node):
296     pass
297
298 class Alter (Music_xml_node):
299     pass
300
301 class Rest (Music_xml_node):
302     pass
303 class Mode (Music_xml_node):
304     pass
305 class Tied (Music_xml_node):
306     pass
307
308 class Type (Music_xml_node):
309     pass
310 class Grace (Music_xml_node):
311     pass
312 class Staff (Music_xml_node):
313     pass
314
315 class_dict = {
316         '#comment': Hash_comment,
317         'accidental': Accidental,
318         'alter': Alter,
319         'attributes': Attributes,
320         'beam' : Beam,
321         'chord': Chord,
322         'dot': Dot,
323         'duration': Duration,
324         'grace': Grace,
325         'mode' : Mode,
326         'measure': Measure,
327         'notations': Notations,
328         'note': Note,
329         'part': Part,
330         'pitch': Pitch,
331         'rest':Rest,
332         'slur': Slur,
333         'tied': Tied,
334         'time-modification': Time_modification,
335         'tuplet': Tuplet,
336         'type': Type,
337         'part-list': Part_list,
338         'staff': Staff,
339 }
340
341 def name2class_name (name):
342     name = name.replace ('-', '_')
343     name = name.replace ('#', 'hash_')
344     name = name[0].upper() + name[1:].lower()
345
346     return str (name)
347
348 def create_classes (names, dict):
349     for n in names:
350         if dict.has_key (n):
351             continue
352
353         class_name = name2class_name (n)
354         klass = new.classobj (class_name, (Music_xml_node,) , {})
355         dict[n] = klass
356
357 def element_names (node, dict):
358     dict[node.nodeName] = 1
359     for cn in node.childNodes:
360         element_names (cn, dict)
361     return dict
362
363 def demarshal_node (node):
364     name = node.nodeName
365     klass = class_dict[name]
366     py_node = klass()
367     py_node._name = name
368     py_node._children = [demarshal_node (cn) for cn in node.childNodes]
369     for c in py_node._children:
370         c._parent = py_node
371
372     if node.attributes:
373
374         for (nm, value) in node.attributes.items():
375             py_node.__dict__[nm] = value
376
377     py_node._data = None
378     if node.nodeType == node.TEXT_NODE and node.data:
379         py_node._data = node.data 
380
381     py_node._original = node
382     return py_node
383
384 def strip_white_space (node):
385     node._children = \
386     [c for c in node._children
387      if not (c._original.nodeType == Node.TEXT_NODE and
388              re.match (r'^\s*$', c._data))]
389
390     for c in node._children:
391         strip_white_space (c)
392
393 def create_tree (name):
394     doc = minidom.parse(name)
395     node = doc.documentElement
396     names = element_names (node, {}).keys()
397     create_classes (names, class_dict)
398
399     return demarshal_node (node)
400
401 def test_musicxml (tree):
402     m = tree._children[-2]
403     print m
404
405 def read_musicxml (name):
406     tree = create_tree (name)
407     strip_white_space (tree)
408     return tree
409
410 if __name__  == '__main__':
411     tree = read_musicxml ('BeetAnGeSample.xml')
412     test_musicxml (tree)
413
414