]> git.donarmstrong.com Git - lilypond.git/blob - python/musicxml.py
87240cbb160dc5a725a0e76fb32b47af87c521a4
[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 class Dot (Music_xml_node):
234         pass
235 class Alter (Music_xml_node):
236         pass
237
238 class Rest (Music_xml_node):
239         pass
240
241 class Type (Music_xml_node):
242         pass
243 class Grace (Music_xml_node):
244         pass
245
246 class_dict = {
247         'alter': Alter,
248         'grace': Grace,
249         'rest':Rest,
250         'dot': Dot,
251         'chord': Chord,
252         'duration': Duration,
253         'attributes': Attributes,
254         'note': Note,
255         'pitch': Pitch,
256         'part': Part, 
257         'measure': Measure,
258         'type': Type,
259         '#comment': Hash_comment,
260 }
261
262 def name2class_name (name):
263         name = name.replace ('-', '_')
264         name = name.replace ('#', 'hash_')
265         name = name[0].upper() + name[1:].lower()
266         
267         return str (name)
268         
269 def create_classes (names, dict):
270         for n in names:
271                 if dict.has_key (n):
272                         continue
273
274                 class_name = name2class_name (n)
275                 klass = new.classobj (class_name, (Music_xml_node,) , {})
276                 dict[n] = klass
277         
278 def element_names (node, dict):
279         dict[node.nodeName] = 1
280         for cn in node.childNodes:
281                 element_names (cn, dict)
282         return dict
283
284 def demarshal_node (node):
285         name = node.nodeName
286         klass = class_dict[name]
287         py_node = klass()
288         py_node._name = name
289         py_node._children = [demarshal_node (cn) for cn in node.childNodes]
290         if node.attributes:
291                 for (name, value) in node.attributes.items():
292                         py_node.name = value
293
294         py_node._data = None
295         if node.nodeType == node.TEXT_NODE and node.data:
296                 py_node._data = node.data 
297
298         py_node._original = node
299         return py_node
300                 
301 def strip_white_space (node):
302         node._children = \
303         [c for c in node._children
304          if not (c._original.nodeType == Node.TEXT_NODE and
305                  re.match (r'^\s*$', c._data))]
306         
307         for c in node._children:
308                 strip_white_space (c)
309
310 def create_tree (name):
311         doc = minidom.parse(name)
312         node = doc.documentElement
313         names = element_names (node, {}).keys()
314         create_classes (names, class_dict)
315     
316         return demarshal_node (node)
317
318 def oldtest ():
319         n = tree._children[-2]._children[-1]._children[0]
320         print n
321         print n.get_duration_log()
322         print n.get_pitches()
323         print n.get_pitches()[0].get_alteration()
324         
325         
326 def test_musicxml (tree):
327         m = tree._children[-2]
328         print m
329         
330         m.to_ly (lambda str: sys.stdout.write (str))
331         
332 def read_musicxml (name):
333         tree = create_tree (name)
334         strip_white_space (tree)
335         return tree
336
337 if __name__  == '__main__':
338         tree = read_musicxml ('BeetAnGeSample.xml')
339         test_musicxml (tree)
340