* flower/test-std.cc: Add simple unit test for vector migration.
[lilypond/patrick.git] / python / musicxml.py
blob78d68dcd0bf2e878abe05d283fa9ee4d19dfd37e
1 import sys
2 import new
3 import re
4 import string
5 from rational import Rational
7 from xml.dom import minidom, Node
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
18 def is_first (self):
19 return self._parent.get_typed_children (self.__class__)[0] == self
21 def original (self):
22 return self._original
23 def get_name (self):
24 return self._name
26 def get_text (self):
27 if self._data:
28 return self._data
30 if not self._children:
31 return ''
33 return ''.join ([c.get_text () for c in self._children])
35 def get_typed_children (self, klass):
36 return [c for c in self._children if isinstance(c, klass)]
38 def get_named_children (self, nm):
39 return self.get_typed_children (class_dict[nm])
41 def get_named_child (self, nm):
42 return self.get_maybe_exist_named_child (nm)
44 def get_children (self, predicate):
45 return [c for c in self._children if predicate(c)]
47 def get_all_children (self):
48 return self._children
50 def get_maybe_exist_named_child (self, name):
51 return self.get_maybe_exist_typed_child (class_dict[name])
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
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)
68 return cn[0]
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)
77 class Duration (Music_xml_node):
78 def get_length (self):
79 dur = string.atoi (self.get_text ()) * Rational (1,4)
80 return dur
82 class Hash_comment (Music_xml_node):
83 pass
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'])
93 step = ch.get_text ().strip ()
94 return string.atoi (step)
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
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
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
116 class Attributes (Measure_element):
117 def __init__ (self):
118 Measure_element.__init__ (self)
119 self._dict = {}
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
127 def get_named_attribute (self, name):
128 return self._dict[name]
130 class Note (Measure_element):
131 def get_duration_log (self):
132 ch = self.get_maybe_exist_typed_child (class_dict[u'type'])
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
147 def get_factor (self):
148 return 1
150 def get_pitches (self):
151 return self.get_typed_children (class_dict[u'pitch'])
153 class Part_list (Music_xml_node):
154 pass
156 class Measure(Music_xml_node):
157 def get_notes (self):
158 return self.get_typed_children (class_dict[u'note'])
161 class Musicxml_voice:
162 def __init__ (self):
163 self._elements = []
164 self._staves = {}
165 self._start_staff = None
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 ()
173 if not self._start_staff:
174 self._start_staff = name
175 self._staves[name] = True
177 def insert (self, idx, e):
178 self._elements.insert (idx, e)
182 class Part (Music_xml_node):
183 def __init__ (self):
184 self._voices = []
186 def interpret (self):
187 """Set durations and starting points."""
189 now = Rational (0)
190 factor = Rational (1)
191 attr_dict = {}
192 measures = self.get_typed_children (Measure)
194 for m in measures:
195 for n in m.get_all_children ():
196 dur = Rational (0)
198 if n.__class__ == Attributes:
199 n.set_attributes_from_previous (attr_dict)
200 n.read_self ()
201 attr_dict = n._dict.copy ()
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)
215 n._when = now
216 n._duration = dur
217 now += dur
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 ())
226 start_attr = None
227 for n in elements:
228 voice_id = n.get_maybe_exist_typed_child (class_dict['voice'])
230 if not (voice_id or isinstance (n, Attributes)):
231 continue
233 if isinstance (n, Attributes) and not start_attr:
234 start_attr = n
235 continue
237 if isinstance (n, Attributes):
238 for v in voices.values ():
239 v.add_element (n)
240 continue
242 id = voice_id.get_text ()
243 if not voices.has_key (id):
244 voices[id] = Musicxml_voice()
246 voices[id].add_element (n)
248 if start_attr:
249 for (k,v) in voices.items ():
250 v.insert (0, start_attr)
252 part._voices = voices
254 def get_voices (self):
255 return self._voices
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
266 def get_tuplet (self):
267 return self.get_maybe_exist_typed_child (Tuplet)
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 ()))
275 class Accidental (Music_xml_node):
276 def __init__ (self):
277 Music_xml_node.__init__ (self)
278 self.editorial = False
279 self.cautionary = False
282 class Tuplet(Music_xml_node):
283 pass
285 class Slur (Music_xml_node):
286 def get_type (self):
287 return self.type
289 class Beam (Music_xml_node):
290 def get_type (self):
291 return self.get_text ()
293 class Chord (Music_xml_node):
294 pass
296 class Dot (Music_xml_node):
297 pass
299 class Alter (Music_xml_node):
300 pass
302 class Rest (Music_xml_node):
303 pass
304 class Mode (Music_xml_node):
305 pass
306 class Tied (Music_xml_node):
307 pass
309 class Type (Music_xml_node):
310 pass
311 class Grace (Music_xml_node):
312 pass
313 class Staff (Music_xml_node):
314 pass
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,
342 def name2class_name (name):
343 name = name.replace ('-', '_')
344 name = name.replace ('#', 'hash_')
345 name = name[0].upper() + name[1:].lower()
347 return str (name)
349 def create_classes (names, dict):
350 for n in names:
351 if dict.has_key (n):
352 continue
354 class_name = name2class_name (n)
355 klass = new.classobj (class_name, (Music_xml_node,) , {})
356 dict[n] = klass
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
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
373 if node.attributes:
375 for (nm, value) in node.attributes.items():
376 py_node.__dict__[nm] = value
378 py_node._data = None
379 if node.nodeType == node.TEXT_NODE and node.data:
380 py_node._data = node.data
382 py_node._original = node
383 return py_node
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))]
391 for c in node._children:
392 strip_white_space (c)
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)
400 return demarshal_node (node)
402 def test_musicxml (tree):
403 m = tree._children[-2]
404 print m
406 def read_musicxml (name):
407 tree = create_tree (name)
408 strip_white_space (tree)
409 return tree
411 if __name__ == '__main__':
412 tree = read_musicxml ('BeetAnGeSample.xml')
413 test_musicxml (tree)