]> git.donarmstrong.com Git - lilypond.git/blob - python/musicxml.py
Convert dynamic marks (given in a <direction> tag, assigned to the staff at a given...
[lilypond.git] / python / musicxml.py
1 import new
2 from rational import *
3
4 class Xml_node:
5     def __init__ (self):
6         self._children = []
7         self._data = None
8         self._original = None
9         self._name = 'xml_node'
10         self._parent = None
11         self._attribute_dict = {}
12         
13     def get_parent (self):
14         return self._parent
15     
16     def is_first (self):
17         return self._parent.get_typed_children (self.__class__)[0] == self
18
19     def original (self):
20         return self._original 
21     def get_name (self):
22         return self._name
23
24     def get_text (self):
25         if self._data:
26             return self._data
27
28         if not self._children:
29             return ''
30
31         return ''.join ([c.get_text () for c in self._children])
32
33     def message (self, msg):
34         print msg
35
36         p = self
37         while p:
38             print '  In: <%s %s>' % (p._name, ' '.join (['%s=%s' % item for item in p._attribute_dict.items()]))
39             p = p.get_parent ()
40         
41     def get_typed_children (self, klass):
42         if not klass:
43             return []
44         else:
45             return [c for c in self._children if isinstance(c, klass)]
46
47     def get_named_children (self, nm):
48         return self.get_typed_children (class_dict.get (nm))
49
50     def get_named_child (self, nm):
51         return self.get_maybe_exist_named_child (nm)
52
53     def get_children (self, predicate):
54         return [c for c in self._children if predicate(c)]
55
56     def get_all_children (self):
57         return self._children
58
59     def get_maybe_exist_named_child (self, name):
60         return self.get_maybe_exist_typed_child (class_dict[name])
61
62     def get_maybe_exist_typed_child (self, klass):
63         cn = self.get_typed_children (klass)
64         if len (cn)==0:
65             return None
66         elif len (cn) == 1:
67             return cn[0]
68         else:
69             raise "More than 1 child", klass
70
71     def get_unique_typed_child (self, klass):
72         cn = self.get_typed_children(klass)
73         if len (cn) <> 1:
74             print self.__dict__ 
75             raise 'Child is not unique for', (klass, 'found', cn)
76
77         return cn[0]
78
79 class Music_xml_node (Xml_node):
80     def __init__ (self):
81         Xml_node.__init__ (self)
82         self.duration = Rational (0)
83         self.start = Rational (0)
84
85
86 class Duration (Music_xml_node):
87     def get_length (self):
88         dur = int (self.get_text ()) * Rational (1,4)
89         return dur
90
91 class Hash_comment (Music_xml_node):
92     pass
93
94 class Pitch (Music_xml_node):
95     def get_step (self):
96         ch = self.get_unique_typed_child (class_dict[u'step'])
97         step = ch.get_text ().strip ()
98         return step
99     def get_octave (self):
100         ch = self.get_unique_typed_child (class_dict[u'octave'])
101
102         step = ch.get_text ().strip ()
103         return int (step)
104
105     def get_alteration (self):
106         ch = self.get_maybe_exist_typed_child (class_dict[u'alter'])
107         alter = 0
108         if ch:
109             alter = int (ch.get_text ().strip ())
110         return alter
111
112 class Measure_element (Music_xml_node):
113     def get_voice_id (self):
114         voice_id = self.get_maybe_exist_named_child ('voice')
115         if voice_id:
116             return voice_id.get_text ()
117         else:
118             return None
119
120     def is_first (self):
121         cn = self._parent.get_typed_children (self.__class__)
122         cn = [c for c in cn if c.get_voice_id () == self.get_voice_id ()]
123         return cn[0] == self
124
125 class Attributes (Measure_element):
126     def __init__ (self):
127         Measure_element.__init__ (self)
128         self._dict = {}
129
130     def set_attributes_from_previous (self, dict):
131         self._dict.update (dict)
132         
133     def read_self (self):
134         for c in self.get_all_children ():
135             self._dict[c.get_name()] = c
136
137     def get_named_attribute (self, name):
138         return self._dict[name]
139
140     def get_measure_length (self):
141         (n,d) = self.get_time_signature ()
142         return Rational (n,d)
143         
144     def get_time_signature (self):
145         "return time sig as a (beat, beat-type) tuple"
146
147         try:
148             mxl = self.get_named_attribute ('time')
149             
150             beats = mxl.get_maybe_exist_named_child ('beats')
151             type = mxl.get_maybe_exist_named_child ('beat-type')
152             return (int (beats.get_text ()),
153                     int (type.get_text ()))
154         except KeyError:
155             print 'error: requested time signature, but time sig unknown'
156             return (4, 4)
157
158     def get_clef_sign (self):
159         mxl = self.get_named_attribute ('clef')
160         sign = mxl.get_maybe_exist_named_child ('sign')
161         if sign:
162             return sign.get_text ()
163         else:
164             print 'clef requested, but unknow'
165             return 'G'
166
167     def get_key_signature (self):
168         "return (fifths, mode) tuple"
169
170         key = self.get_named_attribute ('key')
171         mode_node = key.get_maybe_exist_named_child ('mode')
172         mode = 'major'
173         if mode_node:
174             mode = mode_node.get_text ()
175
176         fifths = int (key.get_maybe_exist_named_child ('fifths').get_text ())
177         return (fifths, mode)
178                 
179
180 class Note (Measure_element):
181     def __init__ (self):
182         Measure_element.__init__ (self)
183         self.instrument_name = ''
184         
185     def get_duration_log (self):
186         ch = self.get_maybe_exist_typed_child (class_dict[u'type'])
187
188         if ch:
189             log = ch.get_text ().strip()
190             return {'eighth': 3,
191                     'quarter': 2,
192                     'half': 1,
193                     '16th': 4,
194                     '32nd': 5,
195                      'breve': -1,
196                     'long': -2,
197                     'whole': 0} [log]
198         else:
199             return 0
200
201     def get_factor (self):
202         return 1
203
204     def get_pitches (self):
205         return self.get_typed_children (class_dict[u'pitch'])
206
207 class Part_list (Music_xml_node):
208     def __init__ (self):
209         Music_xml_node.__init__ (self)
210         self._id_instrument_name_dict = {}
211         
212     def generate_id_instrument_dict (self):
213
214         ## not empty to make sure this happens only once.
215         mapping = {1: 1}
216         for score_part in self.get_named_children ('score-part'):
217             for instr in score_part.get_named_children ('score-instrument'):
218                 id = instr.id
219                 name = instr.get_named_child ("instrument-name")
220                 mapping[id] = name.get_text ()
221
222         self._id_instrument_name_dict = mapping
223
224     def get_instrument (self, id):
225         if not self._id_instrument_name_dict:
226             self.generate_id_instrument_dict()
227
228         try:
229             return self._id_instrument_name_dict[id]
230         except KeyError:
231             print "Opps, couldn't find instrument for ID=", id
232             return "Grand Piano"
233         
234 class Measure(Music_xml_node):
235     def get_notes (self):
236         return self.get_typed_children (class_dict[u'note'])
237
238     
239 class Musicxml_voice:
240     def __init__ (self):
241         self._elements = []
242         self._staves = {}
243         self._start_staff = None
244
245     def add_element (self, e):
246         self._elements.append (e)
247         if (isinstance (e, Note)
248             and e.get_maybe_exist_typed_child (Staff)):
249             name = e.get_maybe_exist_typed_child (Staff).get_text ()
250
251             if not self._start_staff:
252                 self._start_staff = name
253             self._staves[name] = True
254
255     def insert (self, idx, e):
256         self._elements.insert (idx, e)
257
258
259
260 class Part (Music_xml_node):
261     def __init__ (self):
262         Music_xml_node.__init__ (self)
263         self._voices = []
264
265     def get_part_list (self):
266         n = self
267         while n and n.get_name() != 'score-partwise':
268             n = n._parent
269
270         return n.get_named_child ('part-list')
271         
272     def interpret (self):
273         """Set durations and starting points."""
274         
275         part_list = self.get_part_list ()
276         
277         now = Rational (0)
278         factor = Rational (1)
279         attributes_dict = {}
280         attributes_object = None
281         measures = self.get_typed_children (Measure)
282         last_moment = Rational (-1)
283         last_measure_position = Rational (-1)
284         for m in measures:
285             measure_start_moment = now
286             measure_position = Rational (0)
287             for n in m.get_all_children ():
288                 dur = Rational (0)
289
290                 if n.__class__ == Attributes:
291                     n.set_attributes_from_previous (attributes_dict)
292                     n.read_self ()
293                     attributes_dict = n._dict.copy ()
294                     attributes_object = n
295                     
296                     factor = Rational (1,
297                                        int (attributes_dict['divisions'].get_text ()))
298
299                 
300                 if (n.get_maybe_exist_typed_child (Duration)):
301                     mxl_dur = n.get_maybe_exist_typed_child (Duration)
302                     dur = mxl_dur.get_length () * factor
303                     
304                     if n.get_name() == 'backup':
305                         dur = - dur
306                     if n.get_maybe_exist_typed_child (Grace):
307                         dur = Rational (0)
308
309                     rest = n.get_maybe_exist_typed_child (Rest)
310                     if (rest
311                         and attributes_object
312                         and attributes_object.get_measure_length () == dur):
313
314                         rest._is_whole_measure = True
315
316                 if (dur > Rational (0) 
317                     and n.get_maybe_exist_typed_child (Chord)):
318                     now = last_moment
319                     measure_position = last_measure_position
320                     
321                 last_moment = now
322                 last_measure_position = measure_position
323
324                 n._when = now
325                 n._measure_position = measure_position
326                 n._duration = dur
327                 now += dur
328                 measure_position += dur
329                 if n._name == 'note':
330                     instrument = n.get_maybe_exist_named_child ('instrument')
331                     if instrument:
332                         n.instrument_name = part_list.get_instrument (instrument.id)
333
334             if attributes_object:
335                 length = attributes_object.get_measure_length ()
336                 new_now = measure_start_moment + length
337                 
338                 if now <> new_now:
339                     problem = 'incomplete'
340                     if now > new_now:
341                         problem = 'overfull'
342
343                     ## only for verbose operation.
344                     if problem <> 'incomplete':
345                         m.message ('%s measure? Expected: %s, Difference: %s' % (problem, now, new_now - now))
346
347                 now = new_now
348
349     def extract_voices (part):
350         voices = {}
351         measures = part.get_typed_children (Measure)
352         elements = []
353         for m in measures:
354             elements.extend (m.get_all_children ())
355
356         start_attr = None
357         for n in elements:
358             voice_id = n.get_maybe_exist_typed_child (class_dict['voice'])
359
360             # TODO: If the first element of a voice is a dynamics entry,
361             #       then voice_id is not yet set! Thus it will currently be ignored
362             if not (voice_id or isinstance (n, Attributes) or isinstance (n, Direction) ):
363                 continue
364
365             if isinstance (n, Attributes) and not start_attr:
366                 start_attr = n
367                 continue
368
369             if isinstance (n, Attributes) or isinstance (n, Direction):
370                 for v in voices.values ():
371                     v.add_element (n)
372                 continue
373
374             id = voice_id.get_text ()
375             if not voices.has_key (id):
376                 voices[id] = Musicxml_voice()
377
378             voices[id].add_element (n)
379
380         if start_attr:
381             for (k,v) in voices.items ():
382                 v.insert (0, start_attr)
383
384         part._voices = voices
385
386     def get_voices (self):
387         return self._voices
388
389 class Notations (Music_xml_node):
390     def get_tie (self):
391         ts = self.get_named_children ('tied')
392         starts = [t for t in ts if t.type == 'start']
393         if starts:
394             return starts[0]
395         else:
396             return None
397
398     def get_tuplet (self):
399         return self.get_maybe_exist_typed_child (Tuplet)
400
401 class Time_modification(Music_xml_node):
402     def get_fraction (self):
403         b = self.get_maybe_exist_typed_child (class_dict['actual-notes'])
404         a = self.get_maybe_exist_typed_child (class_dict['normal-notes'])
405         return (int(a.get_text ()), int (b.get_text ()))
406
407 class Accidental (Music_xml_node):
408     def __init__ (self):
409         Music_xml_node.__init__ (self)
410         self.editorial = False
411         self.cautionary = False
412
413
414 class Tuplet(Music_xml_node):
415     pass
416
417 class Slur (Music_xml_node):
418     def get_type (self):
419         return self.type
420
421 class Beam (Music_xml_node):
422     def get_type (self):
423         return self.get_text ()
424     def is_primary (self):
425         return self.number == "1"
426     
427 class Chord (Music_xml_node):
428     pass
429
430 class Dot (Music_xml_node):
431     pass
432
433 class Alter (Music_xml_node):
434     pass
435
436 class Rest (Music_xml_node):
437     def __init__ (self):
438         Music_xml_node.__init__ (self)
439         self._is_whole_measure = False
440     def is_whole_measure (self):
441         return self._is_whole_measure
442
443 class Mode (Music_xml_node):
444     pass
445 class Tied (Music_xml_node):
446     pass
447
448 class Type (Music_xml_node):
449     pass
450 class Grace (Music_xml_node):
451     pass
452 class Staff (Music_xml_node):
453     pass
454
455 class Instrument (Music_xml_node):
456     pass
457
458 class Fermata (Music_xml_node):
459     pass
460 class Dynamics (Music_xml_node):
461     pass
462 class Articulations (Music_xml_node):
463     pass
464 class Accent (Music_xml_node):
465     pass
466 class Staccato (Music_xml_node):
467     pass
468 class Tenuto (Music_xml_node):
469     pass
470 class Tremolo (Music_xml_node):
471     pass
472 class Technical (Music_xml_node):
473     pass
474 class Ornaments (Music_xml_node):
475     pass
476
477
478 class Direction (Music_xml_node):
479     pass
480 class DirType (Music_xml_node):
481     pass
482 class Wedge (Music_xml_node):
483     pass
484
485
486 ## need this, not all classes are instantiated
487 ## for every input file.
488 class_dict = {
489         '#comment': Hash_comment,
490         'accidental': Accidental,
491         'alter': Alter,
492         'attributes': Attributes,
493         'beam' : Beam,
494         'chord': Chord,
495         'dot': Dot,
496         'duration': Duration,
497         'grace': Grace,
498         'instrument': Instrument, 
499         'mode' : Mode,
500         'measure': Measure,
501         'notations': Notations,
502         'note': Note,
503         'part': Part,
504         'pitch': Pitch,
505         'rest':Rest,
506         'slur': Slur,
507         'tied': Tied,
508         'time-modification': Time_modification,
509         'tuplet': Tuplet,
510         'type': Type,
511         'part-list': Part_list,
512         'staff': Staff,
513         'fermata': Fermata,
514         'articulations': Articulations,
515         'accent': Accent,
516         'staccato': Staccato,
517         'tenuto': Tenuto,
518         'tremolo': Tremolo,
519         'technical': Technical,
520         'ornaments': Ornaments,
521         'direction': Direction,
522         'direction-type': DirType,
523         'dynamics': Dynamics,
524         'wedge': Wedge
525 }
526
527 def name2class_name (name):
528     name = name.replace ('-', '_')
529     name = name.replace ('#', 'hash_')
530     name = name[0].upper() + name[1:].lower()
531
532     return str (name)
533
534 def get_class (name):
535     try:
536         return class_dict[name]
537     except KeyError:
538         class_name = name2class_name (name)
539         klass = new.classobj (class_name, (Music_xml_node,) , {})
540         class_dict[name] = klass
541         return klass
542         
543 def lxml_demarshal_node (node):
544     name = node.tag
545
546     if name is None:
547         return None
548     klass = get_class (name)
549     py_node = klass()
550     
551     py_node._original = node
552     py_node._name = name
553     py_node._data = node.text
554     py_node._children = [lxml_demarshal_node (cn) for cn in node.getchildren()]
555     py_node._children = filter (lambda x: x, py_node._children)
556     
557     for c in py_node._children:
558         c._parent = py_node
559
560     for (k,v) in node.items ():
561         py_node.__dict__[k] = v
562         py_node._attribute_dict[k] = v
563
564     return py_node
565
566 def minidom_demarshal_node (node):
567     name = node.nodeName
568
569     klass = get_class (name)
570     py_node = klass()
571     py_node._name = name
572     py_node._children = [minidom_demarshal_node (cn) for cn in node.childNodes]
573     for c in py_node._children:
574         c._parent = py_node
575
576     if node.attributes:
577         for (nm, value) in node.attributes.items():
578             py_node.__dict__[nm] = value
579             py_node._attribute_dict[nm] = value
580             
581     py_node._data = None
582     if node.nodeType == node.TEXT_NODE and node.data:
583         py_node._data = node.data 
584
585     py_node._original = node
586     return py_node
587
588
589 if __name__  == '__main__':
590         import lxml.etree
591         
592         tree = lxml.etree.parse ('beethoven.xml')
593         mxl_tree = lxml_demarshal_node (tree.getroot ())
594         ks = class_dict.keys()
595         ks.sort()
596         print '\n'.join (ks)