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