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