]> 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
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
17         def original (self):
18                 return self._original 
19         def get_name (self):
20                 return self._name
21         
22         def get_text (self):
23                 if self._data:
24                         return self._data
25
26                 if not self._children:
27                         return ''
28                 
29                 return ''.join ([c.get_text () for c in self._children])
30
31         def get_typed_children (self, klass):
32                 return [c for c in self._children if c.__class__ == klass]
33
34         def get_children (self, predicate):
35                 return [c for c in self._children if predicate(c)]
36
37         def get_all_children (self):
38                 return self._children
39
40         def get_maybe_exist_typed_child (self, klass):
41                 cn = self.get_typed_children (klass)
42                 if len (cn)==0:
43                         return None
44                 elif len (cn) == 1:
45                         return cn[0]
46                 else:
47                         raise "More than 1 child", klass
48                 
49         def get_unique_typed_child (self, klass):
50                 cn = self.get_typed_children(klass)
51                 if len (cn) <> 1:
52                         print self.__dict__ 
53                         raise 'Child is not unique for', (klass, 'found', cn)
54
55                 return cn[0]
56         
57 class Music_xml_node (Xml_node):
58         def __init__ (self):
59                 Xml_node.__init__ (self)
60                 self.duration = Rational (0)
61                 self.start = Rational (0)
62         
63 class Attributes (Music_xml_node):
64         def __init__ (self):
65                 self._dict = {}
66                 
67         def set_attributes_from_previous (self, dict):
68                 self._dict.update (dict)
69         def read_self (self):
70                 for c in self.get_all_children ():
71                         self._dict[c.get_name()] = c
72
73         def get_named_attribute (self, name):
74                 return self._dict[name]
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         def to_ly (self, output_func):
83                 output_func ('%% %s\n ' % self.get_text())
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         def to_ly (self, output_func):
104                 oct = (self.get_octave () - 4)
105                 oct_str = ''
106                 if oct > 0:
107                         oct_str = "'" * oct
108                 elif oct < 0:
109                         oct_str = "," * -oct
110
111                 alt = self.get_alteration ()
112                 alt_str = ''
113                 if alt > 0:
114                         alt_str = 'is' * alt
115                 elif alt < 0:
116                         alt_str = 'es' * alt 
117                 
118                 output_func ('%s%s%s' % (self.get_step ().lower(), alt_str, oct_str))
119                                        
120 class Note (Music_xml_node):
121         def get_duration_log (self):
122                 ch = self.get_maybe_exist_typed_child (class_dict[u'type'])
123
124                 if ch:
125                         log = ch.get_text ().strip()
126                         return  {'eighth': 3,
127                                  'quarter': 2,
128                                  'half': 1,
129                                  '16th': 4,
130                                  '32nd': 5,
131                                  'breve': -1,
132                                  'long': -2,
133                                  'whole': 0} [log]
134                 else:
135                         return 0
136                 
137         def get_factor (self):
138                 return 1
139         
140         def get_pitches (self):
141                 return self.get_typed_children (class_dict[u'pitch'])
142
143         def to_ly (self, func):
144                 ps = self.get_pitches ()
145
146                 if len (ps) == 0:
147                         func ('r')
148                 else:
149                         func ('<')
150                         for p in ps:
151                                 p.to_ly (func)
152                         func ('>')
153                 
154                 func ('%d ' % (1 << self.get_duration_log ()))
155                 
156
157
158                 
159 class Measure(Music_xml_node):
160         def get_notes (self):
161                 return self.get_typed_children (class_dict[u'note'])
162         def to_ly (self, func):
163                 func (' { % measure \n ')
164                 for c in self._children:
165                         c.to_ly (func)
166                 func (' } \n ')
167
168 class Part (Music_xml_node):
169         def to_ly (self, func):
170                 func (' { %% part %s \n ' % self.name)
171                 for c in self._children:
172                         c.to_ly (func)
173                 func (' } \n ')
174
175         def interpret (self):
176                 """Set durations and starting points."""
177                 
178                 now = Rational (0)
179                 factor = Rational (1)
180                 attr_dict = {}
181                 measures = self.get_typed_children (Measure)
182
183                 for m in measures:
184                         for n in m.get_all_children ():
185                                 dur = Rational (0)
186                                 
187                                 if n.__class__ == Attributes:
188                                         n.set_attributes_from_previous (attr_dict)
189                                         n.read_self ()
190                                         attr_dict = n._dict.copy ()
191                                         
192                                         factor = Rational (1,
193                                                            string.atoi (attr_dict['divisions']
194                                                                         .get_text ()))
195                                 elif (n.get_maybe_exist_typed_child (Duration)
196                                       and not n.get_maybe_exist_typed_child (Chord)):
197                                         mxl_dur = n.get_maybe_exist_typed_child (Duration)
198                                         dur = mxl_dur.get_length () * factor
199                                         if n.get_name() == 'backup':
200                                                 dur = - dur
201                                         if n.get_maybe_exist_typed_child (Grace):
202                                                 dur = Rational (0)
203                                                 
204                                 n._when = now
205                                 n._duration = dur
206                                 now += dur
207
208         def extract_voices (part):
209                 voices = {}
210                 measures = part.get_typed_children (Measure)
211                 elements = []
212                 for m in measures:
213                         elements.extend (m.get_typed_children (Note))
214
215                 for n in elements:
216                         voice_id = n.get_maybe_exist_typed_child (class_dict['voice'])
217
218                         if not voice_id:
219                                 continue
220                         
221                         id = voice_id.get_text ()
222                         if not voices.has_key (id):
223                                 voices[id] = []
224
225                         voices[id].append (n)
226                         
227                 part._voices = voices
228         def get_voices (self):
229                 return self._voices
230         
231 class Chord (Music_xml_node):
232         pass
233
234 class Dot (Music_xml_node):
235         pass
236
237 class Rest (Music_xml_node):
238         pass
239
240 class Type (Music_xml_node):
241         pass
242 class Grace (Music_xml_node):
243         pass
244
245 class_dict = {
246         'grace': Grace,
247         'rest':Rest,
248         'dot': Dot,
249         'chord': Chord,
250         'duration': Duration,
251         'attributes': Attributes,
252         'note': Note,
253         'pitch': Pitch,
254         'part': Part, 
255         'measure': Measure,
256         'type': Type,
257         '#comment': Hash_comment,
258 }
259
260 def name2class_name (name):
261         name = name.replace ('-', '_')
262         name = name.replace ('#', 'hash_')
263         name = name[0].upper() + name[1:].lower()
264         
265         return str (name)
266         
267 def create_classes (names, dict):
268         for n in names:
269                 if dict.has_key (n):
270                         continue
271
272                 class_name = name2class_name (n)
273                 klass = new.classobj (class_name, (Music_xml_node,) , {})
274                 dict[n] = klass
275         
276 def element_names (node, dict):
277         dict[node.nodeName] = 1
278         for cn in node.childNodes:
279                 element_names (cn, dict)
280         return dict
281
282 def demarshal_node (node):
283         name = node.nodeName
284         klass = class_dict[name]
285         py_node = klass()
286         py_node._name = name
287         py_node._children = [demarshal_node (cn) for cn in node.childNodes]
288         if node.attributes:
289                 for (name, value) in node.attributes.items():
290                         py_node.name = value
291
292         py_node._data = None
293         if node.nodeType == node.TEXT_NODE and node.data:
294                 py_node._data = node.data 
295
296         py_node._original = node
297         return py_node
298                 
299 def strip_white_space (node):
300         node._children = \
301         [c for c in node._children
302          if not (c._original.nodeType == Node.TEXT_NODE and
303                  re.match (r'^\s*$', c._data))]
304         
305         for c in node._children:
306                 strip_white_space (c)
307
308 def create_tree (name):
309         doc = minidom.parse(name)
310         node = doc.documentElement
311         names = element_names (node, {}).keys()
312         create_classes (names, class_dict)
313     
314         return demarshal_node (node)
315
316 def oldtest ():
317         n = tree._children[-2]._children[-1]._children[0]
318         print n
319         print n.get_duration_log()
320         print n.get_pitches()
321         print n.get_pitches()[0].get_alteration()
322         
323         
324 def test_musicxml (tree):
325         m = tree._children[-2]
326         print m
327         
328         m.to_ly (lambda str: sys.stdout.write (str))
329         
330 def read_musicxml (name):
331         tree = create_tree (name)
332         strip_white_space (tree)
333         return tree
334
335
336
337
338
339
340 if __name__  == '__main__':
341         tree = read_musicxml ('BeetAnGeSample.xml')
342         test_musicxml (tree)
343