]> git.donarmstrong.com Git - lilypond.git/blob - python/musicxml.py
Merge branch 'master' into lilypond/translation
[lilypond.git] / python / musicxml.py
1 # -*- coding: utf-8 -*-
2 import new
3 import string
4 from rational import *
5 import re
6 import sys
7 import copy
8 import lilylib as ly
9
10 _ = ly._
11
12 def error (str):
13     ly.stderr_write ((_ ("error: %s") % str) + "\n")
14
15
16 def escape_ly_output_string (input_string):
17     return_string = input_string
18     needs_quotes = not re.match (u"^[a-zA-ZäöüÜÄÖßñ]*$", return_string);
19     if needs_quotes:
20         return_string = "\"" + string.replace (return_string, "\"", "\\\"") + "\""
21     return return_string
22
23
24 def musicxml_duration_to_log (dur):
25     return  {'256th': 8,
26              '128th': 7,
27              '64th': 6,
28              '32nd': 5,
29              '16th': 4,
30              'eighth': 3,
31              'quarter': 2,
32              'half': 1,
33              'whole': 0,
34              'breve': -1,
35              'longa': -2,
36              'long': -2}.get (dur, 0)
37
38
39
40 class Xml_node:
41     def __init__ (self):
42         self._children = []
43         self._data = None
44         self._original = None
45         self._name = 'xml_node'
46         self._parent = None
47         self._attribute_dict = {}
48         
49     def get_parent (self):
50         return self._parent
51     
52     def is_first (self):
53         return self._parent.get_typed_children (self.__class__)[0] == self
54
55     def original (self):
56         return self._original 
57     def get_name (self):
58         return self._name
59
60     def get_text (self):
61         if self._data:
62             return self._data
63
64         if not self._children:
65             return ''
66
67         return ''.join ([c.get_text () for c in self._children])
68
69     def message (self, msg):
70         ly.stderr_write (msg+'\n')
71
72         p = self
73         while p:
74             sys.stderr.write ('  In: <%s %s>\n' % (p._name, ' '.join (['%s=%s' % item for item in p._attribute_dict.items ()])))
75             p = p.get_parent ()
76         
77     def dump (self, indent = ''):
78         sys.stderr.write ('%s<%s%s>' % (indent, self._name, ''.join ([' %s=%s' % item for item in self._attribute_dict.items ()])))
79         non_text_children = [c for c in self._children if not isinstance (c, Hash_text)]
80         if non_text_children:
81             sys.stderr.write ('\n')
82         for c in self._children:
83             c.dump (indent + "    ")
84         if non_text_children:
85             sys.stderr.write (indent)
86         sys.stderr.write ('</%s>\n' % self._name)
87
88         
89     def get_typed_children (self, klass):
90         if not klass:
91             return []
92         else:
93             return [c for c in self._children if isinstance(c, klass)]
94
95     def get_named_children (self, nm):
96         return self.get_typed_children (get_class (nm))
97
98     def get_named_child (self, nm):
99         return self.get_maybe_exist_named_child (nm)
100
101     def get_children (self, predicate):
102         return [c for c in self._children if predicate(c)]
103
104     def get_all_children (self):
105         return self._children
106
107     def get_maybe_exist_named_child (self, name):
108         return self.get_maybe_exist_typed_child (get_class (name))
109
110     def get_maybe_exist_typed_child (self, klass):
111         cn = self.get_typed_children (klass)
112         if len (cn)==0:
113             return None
114         elif len (cn) == 1:
115             return cn[0]
116         else:
117             raise "More than 1 child", klass
118
119     def get_unique_typed_child (self, klass):
120         cn = self.get_typed_children(klass)
121         if len (cn) <> 1:
122             sys.stderr.write (self.__dict__ + '\n')
123             raise 'Child is not unique for', (klass, 'found', cn)
124
125         return cn[0]
126
127     def get_named_child_value_number (self, name, default):
128         n = self.get_maybe_exist_named_child (name)
129         if n:
130             return string.atoi (n.get_text())
131         else:
132             return default
133
134
135 class Music_xml_node (Xml_node):
136     def __init__ (self):
137         Xml_node.__init__ (self)
138         self.duration = Rational (0)
139         self.start = Rational (0)
140
141 class Work (Xml_node):
142     def get_work_information (self, tag):
143         wt = self.get_maybe_exist_named_child (tag)
144         if wt:
145             return wt.get_text ()
146         else:
147             return ''
148       
149     def get_work_title (self):
150         return self.get_work_information ('work-title')
151     def get_work_number (self):
152         return self.get_work_information ('work-number')
153     def get_opus (self):
154         return self.get_work_information ('opus')
155
156 class Identification (Xml_node):
157     def get_rights (self):
158         rights = self.get_named_children ('rights')
159         ret = []
160         for r in rights:
161           ret.append (r.get_text ())
162         return string.join (ret, "\n")
163
164     def get_creator (self, type):
165         creators = self.get_named_children ('creator')
166         # return the first creator tag that has the particular type
167         for i in creators:
168             if hasattr (i, 'type') and i.type == type:
169                 return i.get_text ()
170         return None
171
172     def get_composer (self):
173         c = self.get_creator ('composer')
174         if c:
175             return c
176         creators = self.get_named_children ('creator')
177         # return the first creator tag that has no type at all
178         for i in creators:
179             if not hasattr (i, 'type'):
180                 return i.get_text ()
181         return None
182     def get_arranger (self):
183         return self.get_creator ('arranger')
184     def get_editor (self):
185         return self.get_creator ('editor')
186     def get_poet (self):
187         v = self.get_creator ('lyricist')
188         if v:
189             return v
190         v = self.get_creator ('poet')
191         return v
192     
193     def get_encoding_information (self, type):
194         enc = self.get_named_children ('encoding')
195         if enc:
196             children = enc[0].get_named_children (type)
197             if children:
198                 return children[0].get_text ()
199         else:
200             return None
201       
202     def get_encoding_software (self):
203         return self.get_encoding_information ('software')
204     def get_encoding_date (self):
205         return self.get_encoding_information ('encoding-date')
206     def get_encoding_person (self):
207         return self.get_encoding_information ('encoder')
208     def get_encoding_description (self):
209         return self.get_encoding_information ('encoding-description')
210     
211     def get_encoding_software_list (self):
212         enc = self.get_named_children ('encoding')
213         software = []
214         for e in enc:
215             softwares = e.get_named_children ('software')
216             for s in softwares:
217                 software.append (s.get_text ())
218         return software
219
220     def get_file_description (self):
221         misc = self.get_named_children ('miscellaneous')
222         for m in misc:
223             misc_fields = m.get_named_children ('miscellaneous-field')
224             for mf in misc_fields:
225                 if hasattr (mf, 'name') and mf.name == 'description':
226                     return mf.get_text () 
227         return None
228
229
230
231 class Duration (Music_xml_node):
232     def get_length (self):
233         dur = int (self.get_text ()) * Rational (1,4)
234         return dur
235
236 class Hash_comment (Music_xml_node):
237     pass
238 class Hash_text (Music_xml_node):
239     def dump (self, indent = ''):
240         sys.stderr.write ('%s' % string.strip (self._data))
241
242 class Pitch (Music_xml_node):
243     def get_step (self):
244         ch = self.get_unique_typed_child (get_class (u'step'))
245         step = ch.get_text ().strip ()
246         return step
247     def get_octave (self):
248         ch = self.get_unique_typed_child (get_class (u'octave'))
249
250         step = ch.get_text ().strip ()
251         return int (step)
252
253     def get_alteration (self):
254         ch = self.get_maybe_exist_typed_child (get_class (u'alter'))
255         alter = 0
256         if ch:
257             alter = int (ch.get_text ().strip ())
258         return alter
259
260 class Unpitched (Music_xml_node):
261     def get_step (self):
262         ch = self.get_unique_typed_child (get_class (u'display-step'))
263         step = ch.get_text ().strip ()
264         return step
265
266     def get_octave (self):
267         ch = self.get_unique_typed_child (get_class (u'display-octave'))
268
269         if ch:
270             octave = ch.get_text ().strip ()
271             return int (octave)
272         else:
273             return None
274
275 class Measure_element (Music_xml_node):
276     def get_voice_id (self):
277         voice_id = self.get_maybe_exist_named_child ('voice')
278         if voice_id:
279             return voice_id.get_text ()
280         else:
281             return None
282
283     def is_first (self):
284         # Look at all measure elements (previously we had self.__class__, which
285         # only looked at objects of the same type!
286         cn = self._parent.get_typed_children (Measure_element)
287         # But only look at the correct voice; But include Attributes, too, which
288         # are not tied to any particular voice
289         cn = [c for c in cn if (c.get_voice_id () == self.get_voice_id ()) or isinstance (c, Attributes)]
290         return cn[0] == self
291
292 class Attributes (Measure_element):
293     def __init__ (self):
294         Measure_element.__init__ (self)
295         self._dict = {}
296         self._original_tag = None
297
298     def is_first (self):
299         cn = self._parent.get_typed_children (self.__class__)
300         if self._original_tag:
301             return cn[0] == self._original_tag
302         else:
303             return cn[0] == self
304     
305     def set_attributes_from_previous (self, dict):
306         self._dict.update (dict)
307         
308     def read_self (self):
309         for c in self.get_all_children ():
310             self._dict[c.get_name()] = c
311
312     def get_named_attribute (self, name):
313         return self._dict.get (name)
314
315     def get_measure_length (self):
316         (n,d) = self.get_time_signature ()
317         return Rational (n,d)
318         
319     def get_time_signature (self):
320         "return time sig as a (beat, beat-type) tuple"
321
322         try:
323             mxl = self.get_named_attribute ('time')
324             if mxl:
325                 beats = mxl.get_maybe_exist_named_child ('beats')
326                 type = mxl.get_maybe_exist_named_child ('beat-type')
327                 return (int (beats.get_text ()),
328                         int (type.get_text ()))
329             else:
330                 return (4, 4)
331         except KeyError:
332             error (_ ("requested time signature, but time sig is unknown"))
333             return (4, 4)
334
335     # returns clef information in the form ("cleftype", position, octave-shift)
336     def get_clef_information (self):
337         clefinfo = ['G', 2, 0]
338         mxl = self.get_named_attribute ('clef')
339         if not mxl:
340             return clefinfo
341         sign = mxl.get_maybe_exist_named_child ('sign')
342         if sign:
343             clefinfo[0] = sign.get_text()
344         line = mxl.get_maybe_exist_named_child ('line')
345         if line:
346             clefinfo[1] = string.atoi (line.get_text ())
347         octave = mxl.get_maybe_exist_named_child ('clef-octave-change')
348         if octave:
349             clefinfo[2] = string.atoi (octave.get_text ())
350         return clefinfo
351
352     def get_key_signature (self):
353         "return (fifths, mode) tuple"
354
355         key = self.get_named_attribute ('key')
356         mode_node = key.get_maybe_exist_named_child ('mode')
357         mode = None
358         if mode_node:
359             mode = mode_node.get_text ()
360         if not mode or mode == '':
361             mode = 'major'
362
363         fifths = int (key.get_maybe_exist_named_child ('fifths').get_text ())
364         return (fifths, mode)
365         
366     def get_transposition (self):
367         return self.get_named_attribute ('transpose')
368         
369
370
371 class Barline (Measure_element):
372     pass
373 class BarStyle (Music_xml_node):
374     pass
375 class Partial (Measure_element):
376     def __init__ (self, partial):
377         Measure_element.__init__ (self)
378         self.partial = partial
379
380 class Note (Measure_element):
381     def __init__ (self):
382         Measure_element.__init__ (self)
383         self.instrument_name = ''
384         self._after_grace = False
385     def is_grace (self):
386         return self.get_maybe_exist_named_child (u'grace')
387     def is_after_grace (self):
388         if not self.is_grace():
389             return False;
390         gr = self.get_maybe_exist_typed_child (Grace)
391         return self._after_grace or hasattr (gr, 'steal-time-previous');
392
393     def get_duration_log (self):
394         ch = self.get_maybe_exist_named_child (u'type')
395
396         if ch:
397             log = ch.get_text ().strip()
398             return musicxml_duration_to_log (log)
399         elif self.get_maybe_exist_named_child (u'grace'):
400             # FIXME: is it ok to default to eight note for grace notes?
401             return 3
402         else:
403             return None
404
405     def get_factor (self):
406         return 1
407
408     def get_pitches (self):
409         return self.get_typed_children (get_class (u'pitch'))
410
411 class Part_list (Music_xml_node):
412     def __init__ (self):
413         Music_xml_node.__init__ (self)
414         self._id_instrument_name_dict = {}
415         
416     def generate_id_instrument_dict (self):
417
418         ## not empty to make sure this happens only once.
419         mapping = {1: 1}
420         for score_part in self.get_named_children ('score-part'):
421             for instr in score_part.get_named_children ('score-instrument'):
422                 id = instr.id
423                 name = instr.get_named_child ("instrument-name")
424                 mapping[id] = name.get_text ()
425
426         self._id_instrument_name_dict = mapping
427
428     def get_instrument (self, id):
429         if not self._id_instrument_name_dict:
430             self.generate_id_instrument_dict()
431
432         instrument_name = self._id_instrument_name_dict.get (id)
433         if instrument_name:
434             return instrument_name
435         else:
436             ly.stderr_write (_ ("Unable to find instrument for ID=%s\n") % id)
437             return "Grand Piano"
438
439 class Part_group (Music_xml_node):
440     pass
441 class Score_part (Music_xml_node):
442     pass
443         
444 class Measure (Music_xml_node):
445     def __init__ (self):
446         Music_xml_node.__init__ (self)
447         self.partial = 0
448     def is_implicit (self):
449         return hasattr (self, 'implicit') and self.implicit == 'yes'
450     def get_notes (self):
451         return self.get_typed_children (get_class (u'note'))
452
453 class Syllabic (Music_xml_node):
454     def continued (self):
455         text = self.get_text()
456         return (text == "begin") or (text == "middle")
457 class Text (Music_xml_node):
458     pass
459
460 class Lyric (Music_xml_node):
461     def get_number (self):
462         if hasattr (self, 'number'):
463             return self.number
464         else:
465             return -1
466
467     def lyric_to_text (self):
468         continued = False
469         syllabic = self.get_maybe_exist_typed_child (Syllabic)
470         if syllabic:
471             continued = syllabic.continued ()
472         text = self.get_maybe_exist_typed_child (Text)
473         
474         if text:
475             text = text.get_text()
476             # We need to convert soft hyphens to -, otherwise the ascii codec as well
477             # as lilypond will barf on that character
478             text = string.replace( text, u'\xad', '-' )
479         
480         if text == "-" and continued:
481             return "--"
482         elif text == "_" and continued:
483             return "__"
484         elif continued and text:
485             return escape_ly_output_string (text) + " --"
486         elif continued:
487             return "--"
488         elif text:
489             return escape_ly_output_string (text)
490         else:
491             return ""
492
493 class Musicxml_voice:
494     def __init__ (self):
495         self._elements = []
496         self._staves = {}
497         self._start_staff = None
498         self._lyrics = []
499         self._has_lyrics = False
500
501     def add_element (self, e):
502         self._elements.append (e)
503         if (isinstance (e, Note)
504             and e.get_maybe_exist_typed_child (Staff)):
505             name = e.get_maybe_exist_typed_child (Staff).get_text ()
506
507             if not self._start_staff and not e.get_maybe_exist_typed_child (Grace):
508                 self._start_staff = name
509             self._staves[name] = True
510
511         lyrics = e.get_typed_children (Lyric)
512         if not self._has_lyrics:
513           self.has_lyrics = len (lyrics) > 0
514
515         for l in lyrics:
516             nr = l.get_number()
517             if (nr > 0) and not (nr in self._lyrics):
518                 self._lyrics.append (nr)
519
520     def insert (self, idx, e):
521         self._elements.insert (idx, e)
522
523     def get_lyrics_numbers (self):
524         if (len (self._lyrics) == 0) and self._has_lyrics:
525             #only happens if none of the <lyric> tags has a number attribute
526             return [1]
527         else:
528             return self._lyrics
529
530
531 class Part (Music_xml_node):
532     def __init__ (self):
533         Music_xml_node.__init__ (self)
534         self._voices = {}
535         self._staff_attributes_dict = {}
536
537     def get_part_list (self):
538         n = self
539         while n and n.get_name() != 'score-partwise':
540             n = n._parent
541
542         return n.get_named_child ('part-list')
543         
544     def interpret (self):
545         """Set durations and starting points."""
546         """The starting point of the very first note is 0!"""
547         
548         part_list = self.get_part_list ()
549         
550         now = Rational (0)
551         factor = Rational (1)
552         attributes_dict = {}
553         attributes_object = None
554         measures = self.get_typed_children (Measure)
555         last_moment = Rational (-1)
556         last_measure_position = Rational (-1)
557         measure_position = Rational (0)
558         measure_start_moment = now
559         is_first_measure = True
560         previous_measure = None
561         # Graces at the end of a measure need to have their position set to the
562         # previous number!
563         pending_graces = []
564         for m in measures:
565             # implicit measures are used for artificial measures, e.g. when
566             # a repeat bar line splits a bar into two halves. In this case,
567             # don't reset the measure position to 0. They are also used for
568             # upbeats (initial value of 0 fits these, too).
569             # Also, don't reset the measure position at the end of the loop,
570             # but rather when starting the next measure (since only then do we
571             # know if the next measure is implicit and continues that measure)
572             if not m.is_implicit ():
573                 # Warn about possibly overfull measures and reset the position
574                 if attributes_object and previous_measure and previous_measure.partial == 0:
575                     length = attributes_object.get_measure_length ()
576                     new_now = measure_start_moment + length
577                     if now <> new_now:
578                         problem = 'incomplete'
579                         if now > new_now:
580                             problem = 'overfull'
581                         ## only for verbose operation.
582                         if problem <> 'incomplete' and previous_measure:
583                             previous_measure.message ('%s measure? Expected: %s, Difference: %s' % (problem, now, new_now - now))
584                     now = new_now
585                 measure_start_moment = now
586                 measure_position = Rational (0)
587
588             for n in m.get_all_children ():
589                 # figured bass has a duration, but applies to the next note
590                 # and should not change the current measure position!
591                 if isinstance (n, FiguredBass):
592                     n._divisions = factor.denominator ()
593                     n._when = now
594                     n._measure_position = measure_position
595                     continue
596
597                 if isinstance (n, Hash_text):
598                     continue
599                 dur = Rational (0)
600
601                 if n.__class__ == Attributes:
602                     n.set_attributes_from_previous (attributes_dict)
603                     n.read_self ()
604                     attributes_dict = n._dict.copy ()
605                     attributes_object = n
606                     
607                     factor = Rational (1,
608                                        int (attributes_dict.get ('divisions').get_text ()))
609
610                 
611                 if (n.get_maybe_exist_typed_child (Duration)):
612                     mxl_dur = n.get_maybe_exist_typed_child (Duration)
613                     dur = mxl_dur.get_length () * factor
614                     
615                     if n.get_name() == 'backup':
616                         dur = - dur
617                         # reset all graces before the backup to after-graces:
618                         for n in pending_graces:
619                             n._when = n._prev_when
620                             n._measure_position = n._prev_measure_position
621                             n._after_grace = True
622                         pending_graces = []
623                     if n.get_maybe_exist_typed_child (Grace):
624                         dur = Rational (0)
625
626                     rest = n.get_maybe_exist_typed_child (Rest)
627                     if (rest
628                         and attributes_object
629                         and attributes_object.get_measure_length () == dur):
630
631                         rest._is_whole_measure = True
632
633                 if (dur > Rational (0) 
634                     and n.get_maybe_exist_typed_child (Chord)):
635                     now = last_moment
636                     measure_position = last_measure_position
637
638                 n._when = now
639                 n._measure_position = measure_position
640
641                 # For all grace notes, store the previous note,  in case need
642                 # to turn the grace note into an after-grace later on!
643                 if isinstance(n, Note) and n.is_grace ():
644                     n._prev_when = last_moment
645                     n._prev_measure_position = last_measure_position
646                 # After-graces are placed at the same position as the previous note
647                 if isinstance(n, Note) and  n.is_after_grace ():
648                     # TODO: We should do the same for grace notes at the end of 
649                     # a measure with no following note!!!
650                     n._when = last_moment
651                     n._measure_position = last_measure_position
652                 elif isinstance(n, Note) and n.is_grace ():
653                     pending_graces.append (n)
654                 elif (dur > Rational (0)):
655                     pending_graces = [];
656
657                 n._duration = dur
658                 if dur > Rational (0):
659                     last_moment = now
660                     last_measure_position = measure_position
661                     now += dur
662                     measure_position += dur
663                 elif dur < Rational (0):
664                     # backup element, reset measure position
665                     now += dur
666                     measure_position += dur
667                     if measure_position < 0:
668                         # backup went beyond the measure start => reset to 0
669                         now -= measure_position
670                         measure_position = 0
671                     last_moment = now
672                     last_measure_position = measure_position
673                 if n._name == 'note':
674                     instrument = n.get_maybe_exist_named_child ('instrument')
675                     if instrument:
676                         n.instrument_name = part_list.get_instrument (instrument.id)
677
678             # reset all graces at the end of the measure to after-graces:
679             for n in pending_graces:
680                 n._when = n._prev_when
681                 n._measure_position = n._prev_measure_position
682                 n._after_grace = True
683             pending_graces = []
684             # Incomplete first measures are not padded, but registered as partial
685             if is_first_measure:
686                 is_first_measure = False
687                 # upbeats are marked as implicit measures
688                 if attributes_object and m.is_implicit ():
689                     length = attributes_object.get_measure_length ()
690                     measure_end = measure_start_moment + length
691                     if measure_end <> now:
692                         m.partial = now
693             previous_measure = m
694
695     # modify attributes so that only those applying to the given staff remain
696     def extract_attributes_for_staff (part, attr, staff):
697         attributes = copy.copy (attr)
698         attributes._children = [];
699         attributes._dict = attr._dict.copy ()
700         attributes._original_tag = attr
701         # copy only the relevant children over for the given staff
702         for c in attr._children:
703             if (not (hasattr (c, 'number') and (c.number != staff)) and
704                 not (isinstance (c, Hash_text))):
705                 attributes._children.append (c)
706         if not attributes._children:
707             return None
708         else:
709             return attributes
710
711     def extract_voices (part):
712         voices = {}
713         measures = part.get_typed_children (Measure)
714         elements = []
715         for m in measures:
716             if m.partial > 0:
717                 elements.append (Partial (m.partial))
718             elements.extend (m.get_all_children ())
719         # make sure we know all voices already so that dynamics, clefs, etc.
720         # can be assigned to the correct voices
721         voice_to_staff_dict = {}
722         for n in elements:
723             voice_id = n.get_maybe_exist_named_child (u'voice')
724             vid = None
725             if voice_id:
726                 vid = voice_id.get_text ()
727             elif isinstance (n, Note):
728                 vid = "None"
729
730             staff_id = n.get_maybe_exist_named_child (u'staff')
731             sid = None
732             if staff_id:
733                 sid = staff_id.get_text ()
734             else:
735                 sid = "None"
736             if vid and not voices.has_key (vid):
737                 voices[vid] = Musicxml_voice()
738             if vid and sid and not n.get_maybe_exist_typed_child (Grace):
739                 if not voice_to_staff_dict.has_key (vid):
740                     voice_to_staff_dict[vid] = sid
741         # invert the voice_to_staff_dict into a staff_to_voice_dict (since we
742         # need to assign staff-assigned objects like clefs, times, etc. to
743         # all the correct voices. This will never work entirely correct due
744         # to staff-switches, but that's the best we can do!
745         staff_to_voice_dict = {}
746         for (v,s) in voice_to_staff_dict.items ():
747             if not staff_to_voice_dict.has_key (s):
748                 staff_to_voice_dict[s] = [v]
749             else:
750                 staff_to_voice_dict[s].append (v)
751
752
753         start_attr = None
754         assign_to_next_note = []
755         id = None
756         for n in elements:
757             voice_id = n.get_maybe_exist_typed_child (get_class ('voice'))
758             if voice_id:
759                 id = voice_id.get_text ()
760             else:
761                 id = "None"
762
763             # We don't need backup/forward any more, since we have already 
764             # assigned the correct onset times. 
765             # TODO: Let Grouping through. Also: link, print, bokmark sound
766             if not (isinstance (n, Note) or isinstance (n, Attributes) or
767                     isinstance (n, Direction) or isinstance (n, Partial) or
768                     isinstance (n, Barline) or isinstance (n, Harmony) or
769                     isinstance (n, FiguredBass) ):
770                 continue
771
772             if isinstance (n, Attributes) and not start_attr:
773                 start_attr = n
774                 continue
775
776             if isinstance (n, Attributes):
777                 # assign these only to the voices they really belongs to!
778                 for (s, vids) in staff_to_voice_dict.items ():
779                     staff_attributes = part.extract_attributes_for_staff (n, s)
780                     if staff_attributes:
781                         for v in vids:
782                             voices[v].add_element (staff_attributes)
783                 continue
784
785             if isinstance (n, Partial) or isinstance (n, Barline):
786                 for v in voices.keys ():
787                     voices[v].add_element (n)
788                 continue
789
790             if isinstance (n, Direction):
791                 staff_id = n.get_maybe_exist_named_child (u'staff')
792                 if staff_id:
793                     staff_id = staff_id.get_text ()
794                 if staff_id:
795                     dir_voices = staff_to_voice_dict.get (staff_id, voices.keys ())
796                 else:
797                     dir_voices = voices.keys ()
798                 for v in dir_voices:
799                     voices[v].add_element (n)
800                 continue
801
802             if isinstance (n, Harmony) or isinstance (n, FiguredBass):
803                 # store the harmony or figured bass element until we encounter 
804                 # the next note and assign it only to that one voice.
805                 assign_to_next_note.append (n)
806                 continue
807
808             if hasattr (n, 'print-object') and getattr (n, 'print-object') == "no":
809                 #Skip this note. 
810                 pass
811             else:
812                 for i in assign_to_next_note:
813                     voices[id].add_element (i)
814                 assign_to_next_note = []
815                 voices[id].add_element (n)
816
817         # Assign all remaining elements from assign_to_next_note to the voice
818         # of the previous note:
819         for i in assign_to_next_note:
820             voices[id].add_element (i)
821         assign_to_next_note = []
822
823         if start_attr:
824             for (s, vids) in staff_to_voice_dict.items ():
825                 staff_attributes = part.extract_attributes_for_staff (start_attr, s)
826                 staff_attributes.read_self ()
827                 part._staff_attributes_dict[s] = staff_attributes
828                 for v in vids:
829                     voices[v].insert (0, staff_attributes)
830                     voices[v]._elements[0].read_self()
831
832         part._voices = voices
833
834     def get_voices (self):
835         return self._voices
836     def get_staff_attributes (self):
837         return self._staff_attributes_dict
838
839 class Notations (Music_xml_node):
840     def get_tie (self):
841         ts = self.get_named_children ('tied')
842         starts = [t for t in ts if t.type == 'start']
843         if starts:
844             return starts[0]
845         else:
846             return None
847
848     def get_tuplets (self):
849         return self.get_typed_children (Tuplet)
850
851 class Time_modification(Music_xml_node):
852     def get_fraction (self):
853         b = self.get_maybe_exist_named_child ('actual-notes')
854         a = self.get_maybe_exist_named_child ('normal-notes')
855         return (int(a.get_text ()), int (b.get_text ()))
856
857 class Accidental (Music_xml_node):
858     def __init__ (self):
859         Music_xml_node.__init__ (self)
860         self.editorial = False
861         self.cautionary = False
862
863 class Music_xml_spanner (Music_xml_node):
864     def get_type (self):
865         if hasattr (self, 'type'):
866             return self.type
867         else:
868             return 0
869     def get_size (self):
870         if hasattr (self, 'size'):
871             return string.atoi (self.size)
872         else:
873             return 0
874
875 class Wedge (Music_xml_spanner):
876     pass
877
878 class Tuplet (Music_xml_spanner):
879     pass
880
881 class Bracket (Music_xml_spanner):
882     pass
883
884 class Dashes (Music_xml_spanner):
885     pass
886
887 class Slur (Music_xml_spanner):
888     def get_type (self):
889         return self.type
890
891 class Beam (Music_xml_spanner):
892     def get_type (self):
893         return self.get_text ()
894     def is_primary (self):
895         return self.number == "1"
896
897 class Wavy_line (Music_xml_spanner):
898     pass
899     
900 class Pedal (Music_xml_spanner):
901     pass
902
903 class Glissando (Music_xml_spanner):
904     pass
905
906 class Slide (Music_xml_spanner):
907     pass
908
909 class Octave_shift (Music_xml_spanner):
910     # default is 8 for the octave-shift!
911     def get_size (self):
912         if hasattr (self, 'size'):
913             return string.atoi (self.size)
914         else:
915             return 8
916
917 class Chord (Music_xml_node):
918     pass
919
920 class Dot (Music_xml_node):
921     pass
922
923 # Rests in MusicXML are <note> blocks with a <rest> inside. This class is only
924 # for the inner <rest> element, not the whole rest block.
925 class Rest (Music_xml_node):
926     def __init__ (self):
927         Music_xml_node.__init__ (self)
928         self._is_whole_measure = False
929     def is_whole_measure (self):
930         return self._is_whole_measure
931     def get_step (self):
932         ch = self.get_maybe_exist_typed_child (get_class (u'display-step'))
933         if ch:
934             step = ch.get_text ().strip ()
935             return step
936         else:
937             return None
938     def get_octave (self):
939         ch = self.get_maybe_exist_typed_child (get_class (u'display-octave'))
940         if ch:
941             step = ch.get_text ().strip ()
942             return int (step)
943         else:
944             return None
945
946 class Type (Music_xml_node):
947     pass
948 class Grace (Music_xml_node):
949     pass
950 class Staff (Music_xml_node):
951     pass
952
953 class Direction (Music_xml_node):
954     pass
955 class DirType (Music_xml_node):
956     pass
957
958 class Bend (Music_xml_node):
959     def bend_alter (self):
960         alter = self.get_maybe_exist_named_child ('bend-alter')
961         if alter:
962             return alter.get_text()
963         else:
964             return 0
965
966 class Words (Music_xml_node):
967     pass
968
969 class Harmony (Music_xml_node):
970     pass
971
972 class ChordPitch (Music_xml_node):
973     def step_class_name (self):
974         return u'root-step'
975     def alter_class_name (self):
976         return u'root-alter'
977     def get_step (self):
978         ch = self.get_unique_typed_child (get_class (self.step_class_name ()))
979         return ch.get_text ().strip ()
980     def get_alteration (self):
981         ch = self.get_maybe_exist_typed_child (get_class (self.alter_class_name ()))
982         alter = 0
983         if ch:
984             alter = int (ch.get_text ().strip ())
985         return alter
986
987 class Root (ChordPitch):
988     pass
989
990 class Bass (ChordPitch):
991     def step_class_name (self):
992         return u'bass-step'
993     def alter_class_name (self):
994         return u'bass-alter'
995
996 class ChordModification (Music_xml_node):
997     def get_type (self):
998         ch = self.get_maybe_exist_typed_child (get_class (u'degree-type'))
999         return {'add': 1, 'alter': 1, 'subtract': -1}.get (ch.get_text ().strip (), 0)
1000     def get_value (self):
1001         ch = self.get_maybe_exist_typed_child (get_class (u'degree-value'))
1002         value = 0
1003         if ch:
1004             value = int (ch.get_text ().strip ())
1005         return value
1006     def get_alter (self):
1007         ch = self.get_maybe_exist_typed_child (get_class (u'degree-alter'))
1008         value = 0
1009         if ch:
1010             value = int (ch.get_text ().strip ())
1011         return value
1012
1013
1014 class Frame (Music_xml_node):
1015     def get_frets (self):
1016         return self.get_named_child_value_number ('frame-frets', 4)
1017     def get_strings (self):
1018         return self.get_named_child_value_number ('frame-strings', 6)
1019     def get_first_fret (self):
1020         return self.get_named_child_value_number ('first-fret', 1)
1021
1022 class Frame_Note (Music_xml_node):
1023     def get_string (self):
1024         return self.get_named_child_value_number ('string', 1)
1025     def get_fret (self):
1026         return self.get_named_child_value_number ('fret', 0)
1027     def get_fingering (self):
1028         return self.get_named_child_value_number ('fingering', -1)
1029     def get_barre (self):
1030         n = self.get_maybe_exist_named_child ('barre')
1031         if n:
1032             return getattr (n, 'type', '')
1033         else:
1034             return ''
1035
1036 class FiguredBass (Music_xml_node):
1037     pass
1038
1039 class BeatUnit (Music_xml_node):
1040     pass
1041
1042 class BeatUnitDot (Music_xml_node):
1043     pass
1044
1045 class PerMinute (Music_xml_node):
1046     pass
1047
1048
1049
1050 ## need this, not all classes are instantiated
1051 ## for every input file. Only add those classes, that are either directly
1052 ## used by class name or extend Music_xml_node in some way!
1053 class_dict = {
1054         '#comment': Hash_comment,
1055         '#text': Hash_text,
1056         'accidental': Accidental,
1057         'attributes': Attributes,
1058         'barline': Barline,
1059         'bar-style': BarStyle,
1060         'bass': Bass,
1061         'beam' : Beam,
1062         'beat-unit': BeatUnit,
1063         'beat-unit-dot': BeatUnitDot,
1064         'bend' : Bend,
1065         'bracket' : Bracket,
1066         'chord': Chord,
1067         'dashes' : Dashes,
1068         'degree' : ChordModification,
1069         'dot': Dot,
1070         'direction': Direction,
1071         'direction-type': DirType,
1072         'duration': Duration,
1073         'frame': Frame,
1074         'frame-note': Frame_Note,
1075         'figured-bass': FiguredBass,
1076         'glissando': Glissando,
1077         'grace': Grace,
1078         'harmony': Harmony,
1079         'identification': Identification,
1080         'lyric': Lyric,
1081         'measure': Measure,
1082         'notations': Notations,
1083         'note': Note,
1084         'octave-shift': Octave_shift,
1085         'part': Part,
1086     'part-group': Part_group,
1087         'part-list': Part_list,
1088         'pedal': Pedal,
1089         'per-minute': PerMinute,
1090         'pitch': Pitch,
1091         'rest': Rest,
1092         'root': Root,
1093         'score-part': Score_part,
1094         'slide': Slide,
1095         'slur': Slur,
1096         'staff': Staff,
1097         'syllabic': Syllabic,
1098         'text': Text,
1099         'time-modification': Time_modification,
1100         'tuplet': Tuplet,
1101         'type': Type,
1102         'unpitched': Unpitched,
1103         'wavy-line': Wavy_line,
1104         'wedge': Wedge,
1105         'words': Words,
1106         'work': Work,
1107 }
1108
1109 def name2class_name (name):
1110     name = name.replace ('-', '_')
1111     name = name.replace ('#', 'hash_')
1112     name = name[0].upper() + name[1:].lower()
1113
1114     return str (name)
1115
1116 def get_class (name):
1117     classname = class_dict.get (name)
1118     if classname:
1119         return classname
1120     else:
1121         class_name = name2class_name (name)
1122         klass = new.classobj (class_name, (Music_xml_node,) , {})
1123         class_dict[name] = klass
1124         return klass
1125         
1126 def lxml_demarshal_node (node):
1127     name = node.tag
1128
1129     # Ignore comment nodes, which are also returned by the etree parser!
1130     if name is None or node.__class__.__name__ == "_Comment":
1131         return None
1132     klass = get_class (name)
1133     py_node = klass()
1134     
1135     py_node._original = node
1136     py_node._name = name
1137     py_node._data = node.text
1138     py_node._children = [lxml_demarshal_node (cn) for cn in node.getchildren()]
1139     py_node._children = filter (lambda x: x, py_node._children)
1140     
1141     for c in py_node._children:
1142         c._parent = py_node
1143
1144     for (k, v) in node.items ():
1145         py_node.__dict__[k] = v
1146         py_node._attribute_dict[k] = v
1147
1148     return py_node
1149
1150 def minidom_demarshal_node (node):
1151     name = node.nodeName
1152
1153     klass = get_class (name)
1154     py_node = klass ()
1155     py_node._name = name
1156     py_node._children = [minidom_demarshal_node (cn) for cn in node.childNodes]
1157     for c in py_node._children:
1158         c._parent = py_node
1159
1160     if node.attributes:
1161         for (nm, value) in node.attributes.items ():
1162             py_node.__dict__[nm] = value
1163             py_node._attribute_dict[nm] = value
1164             
1165     py_node._data = None
1166     if node.nodeType == node.TEXT_NODE and node.data:
1167         py_node._data = node.data 
1168
1169     py_node._original = node
1170     return py_node
1171
1172
1173 if __name__  == '__main__':
1174     import lxml.etree
1175         
1176     tree = lxml.etree.parse ('beethoven.xml')
1177     mxl_tree = lxml_demarshal_node (tree.getroot ())
1178     ks = class_dict.keys ()
1179     ks.sort ()
1180     print '\n'.join (ks)