]> git.donarmstrong.com Git - lilypond.git/blob - python/musicxml.py
MusicXML: Implement elisions (multiple lyrics syllables assigned to one note)
[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_duration_info (self):
406         log = self.get_duration_log ()
407         if log != None:
408             dots = len (self.get_typed_children (Dot))
409             return (log, dots)
410         else:
411             return None
412
413     def get_factor (self):
414         return 1
415
416     def get_pitches (self):
417         return self.get_typed_children (get_class (u'pitch'))
418
419 class Part_list (Music_xml_node):
420     def __init__ (self):
421         Music_xml_node.__init__ (self)
422         self._id_instrument_name_dict = {}
423         
424     def generate_id_instrument_dict (self):
425
426         ## not empty to make sure this happens only once.
427         mapping = {1: 1}
428         for score_part in self.get_named_children ('score-part'):
429             for instr in score_part.get_named_children ('score-instrument'):
430                 id = instr.id
431                 name = instr.get_named_child ("instrument-name")
432                 mapping[id] = name.get_text ()
433
434         self._id_instrument_name_dict = mapping
435
436     def get_instrument (self, id):
437         if not self._id_instrument_name_dict:
438             self.generate_id_instrument_dict()
439
440         instrument_name = self._id_instrument_name_dict.get (id)
441         if instrument_name:
442             return instrument_name
443         else:
444             ly.stderr_write (_ ("Unable to find instrument for ID=%s\n") % id)
445             return "Grand Piano"
446
447 class Part_group (Music_xml_node):
448     pass
449 class Score_part (Music_xml_node):
450     pass
451         
452 class Measure (Music_xml_node):
453     def __init__ (self):
454         Music_xml_node.__init__ (self)
455         self.partial = 0
456     def is_implicit (self):
457         return hasattr (self, 'implicit') and self.implicit == 'yes'
458     def get_notes (self):
459         return self.get_typed_children (get_class (u'note'))
460
461 class Syllabic (Music_xml_node):
462     def continued (self):
463         text = self.get_text()
464         return (text == "begin") or (text == "middle")
465 class Elision (Music_xml_node):
466     pass
467 class Text (Music_xml_node):
468     pass
469
470 class Lyric (Music_xml_node):
471     def get_number (self):
472         if hasattr (self, 'number'):
473             return self.number
474         else:
475             return -1
476
477 class Musicxml_voice:
478     def __init__ (self):
479         self._elements = []
480         self._staves = {}
481         self._start_staff = None
482         self._lyrics = []
483         self._has_lyrics = False
484
485     def add_element (self, e):
486         self._elements.append (e)
487         if (isinstance (e, Note)
488             and e.get_maybe_exist_typed_child (Staff)):
489             name = e.get_maybe_exist_typed_child (Staff).get_text ()
490
491             if not self._start_staff and not e.get_maybe_exist_typed_child (Grace):
492                 self._start_staff = name
493             self._staves[name] = True
494
495         lyrics = e.get_typed_children (Lyric)
496         if not self._has_lyrics:
497           self.has_lyrics = len (lyrics) > 0
498
499         for l in lyrics:
500             nr = l.get_number()
501             if (nr > 0) and not (nr in self._lyrics):
502                 self._lyrics.append (nr)
503
504     def insert (self, idx, e):
505         self._elements.insert (idx, e)
506
507     def get_lyrics_numbers (self):
508         if (len (self._lyrics) == 0) and self._has_lyrics:
509             #only happens if none of the <lyric> tags has a number attribute
510             return [1]
511         else:
512             return self._lyrics
513
514
515 def graces_to_aftergraces (pending_graces):
516     for gr in pending_graces:
517         gr._when = gr._prev_when
518         gr._measure_position = gr._prev_measure_position
519         gr._after_grace = True
520
521
522 class Part (Music_xml_node):
523     def __init__ (self):
524         Music_xml_node.__init__ (self)
525         self._voices = {}
526         self._staff_attributes_dict = {}
527
528     def get_part_list (self):
529         n = self
530         while n and n.get_name() != 'score-partwise':
531             n = n._parent
532
533         return n.get_named_child ('part-list')
534        
535     def interpret (self):
536         """Set durations and starting points."""
537         """The starting point of the very first note is 0!"""
538         
539         part_list = self.get_part_list ()
540         
541         now = Rational (0)
542         factor = Rational (1)
543         attributes_dict = {}
544         attributes_object = None
545         measures = self.get_typed_children (Measure)
546         last_moment = Rational (-1)
547         last_measure_position = Rational (-1)
548         measure_position = Rational (0)
549         measure_start_moment = now
550         is_first_measure = True
551         previous_measure = None
552         # Graces at the end of a measure need to have their position set to the
553         # previous number!
554         pending_graces = []
555         for m in measures:
556             # implicit measures are used for artificial measures, e.g. when
557             # a repeat bar line splits a bar into two halves. In this case,
558             # don't reset the measure position to 0. They are also used for
559             # upbeats (initial value of 0 fits these, too).
560             # Also, don't reset the measure position at the end of the loop,
561             # but rather when starting the next measure (since only then do we
562             # know if the next measure is implicit and continues that measure)
563             if not m.is_implicit ():
564                 # Warn about possibly overfull measures and reset the position
565                 if attributes_object and previous_measure and previous_measure.partial == 0:
566                     length = attributes_object.get_measure_length ()
567                     new_now = measure_start_moment + length
568                     if now <> new_now:
569                         problem = 'incomplete'
570                         if now > new_now:
571                             problem = 'overfull'
572                         ## only for verbose operation.
573                         if problem <> 'incomplete' and previous_measure:
574                             previous_measure.message ('%s measure? Expected: %s, Difference: %s' % (problem, now, new_now - now))
575                     now = new_now
576                 measure_start_moment = now
577                 measure_position = Rational (0)
578
579             for n in m.get_all_children ():
580                 # figured bass has a duration, but applies to the next note
581                 # and should not change the current measure position!
582                 if isinstance (n, FiguredBass):
583                     n._divisions = factor.denominator ()
584                     n._when = now
585                     n._measure_position = measure_position
586                     continue
587
588                 if isinstance (n, Hash_text):
589                     continue
590                 dur = Rational (0)
591
592                 if n.__class__ == Attributes:
593                     n.set_attributes_from_previous (attributes_dict)
594                     n.read_self ()
595                     attributes_dict = n._dict.copy ()
596                     attributes_object = n
597                     
598                     factor = Rational (1,
599                                        int (attributes_dict.get ('divisions').get_text ()))
600
601                 
602                 if (n.get_maybe_exist_typed_child (Duration)):
603                     mxl_dur = n.get_maybe_exist_typed_child (Duration)
604                     dur = mxl_dur.get_length () * factor
605                     
606                     if n.get_name() == 'backup':
607                         dur = - dur
608                         # reset all graces before the backup to after-graces:
609                         graces_to_aftergraces (pending_graces)
610                         pending_graces = []
611                     if n.get_maybe_exist_typed_child (Grace):
612                         dur = Rational (0)
613
614                     rest = n.get_maybe_exist_typed_child (Rest)
615                     if (rest
616                         and attributes_object
617                         and attributes_object.get_measure_length () == dur):
618
619                         rest._is_whole_measure = True
620
621                 if (dur > Rational (0)
622                     and n.get_maybe_exist_typed_child (Chord)):
623                     now = last_moment
624                     measure_position = last_measure_position
625
626                 n._when = now
627                 n._measure_position = measure_position
628
629                 # For all grace notes, store the previous note,  in case need
630                 # to turn the grace note into an after-grace later on!
631                 if isinstance(n, Note) and n.is_grace ():
632                     n._prev_when = last_moment
633                     n._prev_measure_position = last_measure_position
634                 # After-graces are placed at the same position as the previous note
635                 if isinstance(n, Note) and  n.is_after_grace ():
636                     # TODO: We should do the same for grace notes at the end of 
637                     # a measure with no following note!!!
638                     n._when = last_moment
639                     n._measure_position = last_measure_position
640                 elif isinstance(n, Note) and n.is_grace ():
641                     pending_graces.append (n)
642                 elif (dur > Rational (0)):
643                     pending_graces = [];
644
645                 n._duration = dur
646                 if dur > Rational (0):
647                     last_moment = now
648                     last_measure_position = measure_position
649                     now += dur
650                     measure_position += dur
651                 elif dur < Rational (0):
652                     # backup element, reset measure position
653                     now += dur
654                     measure_position += dur
655                     if measure_position < 0:
656                         # backup went beyond the measure start => reset to 0
657                         now -= measure_position
658                         measure_position = 0
659                     last_moment = now
660                     last_measure_position = measure_position
661                 if n._name == 'note':
662                     instrument = n.get_maybe_exist_named_child ('instrument')
663                     if instrument:
664                         n.instrument_name = part_list.get_instrument (instrument.id)
665
666             # reset all graces at the end of the measure to after-graces:
667             graces_to_aftergraces (pending_graces)
668             pending_graces = []
669             # Incomplete first measures are not padded, but registered as partial
670             if is_first_measure:
671                 is_first_measure = False
672                 # upbeats are marked as implicit measures
673                 if attributes_object and m.is_implicit ():
674                     length = attributes_object.get_measure_length ()
675                     measure_end = measure_start_moment + length
676                     if measure_end <> now:
677                         m.partial = now
678             previous_measure = m
679
680     # modify attributes so that only those applying to the given staff remain
681     def extract_attributes_for_staff (part, attr, staff):
682         attributes = copy.copy (attr)
683         attributes._children = [];
684         attributes._dict = attr._dict.copy ()
685         attributes._original_tag = attr
686         # copy only the relevant children over for the given staff
687         for c in attr._children:
688             if (not (hasattr (c, 'number') and (c.number != staff)) and
689                 not (isinstance (c, Hash_text))):
690                 attributes._children.append (c)
691         if not attributes._children:
692             return None
693         else:
694             return attributes
695
696     def extract_voices (part):
697         voices = {}
698         measures = part.get_typed_children (Measure)
699         elements = []
700         for m in measures:
701             if m.partial > 0:
702                 elements.append (Partial (m.partial))
703             elements.extend (m.get_all_children ())
704         # make sure we know all voices already so that dynamics, clefs, etc.
705         # can be assigned to the correct voices
706         voice_to_staff_dict = {}
707         for n in elements:
708             voice_id = n.get_maybe_exist_named_child (u'voice')
709             vid = None
710             if voice_id:
711                 vid = voice_id.get_text ()
712             elif isinstance (n, Note):
713                 vid = "None"
714
715             staff_id = n.get_maybe_exist_named_child (u'staff')
716             sid = None
717             if staff_id:
718                 sid = staff_id.get_text ()
719             else:
720                 sid = "None"
721             if vid and not voices.has_key (vid):
722                 voices[vid] = Musicxml_voice()
723             if vid and sid and not n.get_maybe_exist_typed_child (Grace):
724                 if not voice_to_staff_dict.has_key (vid):
725                     voice_to_staff_dict[vid] = sid
726         # invert the voice_to_staff_dict into a staff_to_voice_dict (since we
727         # need to assign staff-assigned objects like clefs, times, etc. to
728         # all the correct voices. This will never work entirely correct due
729         # to staff-switches, but that's the best we can do!
730         staff_to_voice_dict = {}
731         for (v,s) in voice_to_staff_dict.items ():
732             if not staff_to_voice_dict.has_key (s):
733                 staff_to_voice_dict[s] = [v]
734             else:
735                 staff_to_voice_dict[s].append (v)
736
737
738         start_attr = None
739         assign_to_next_note = []
740         id = None
741         for n in elements:
742             voice_id = n.get_maybe_exist_typed_child (get_class ('voice'))
743             if voice_id:
744                 id = voice_id.get_text ()
745             else:
746                 id = "None"
747
748             # We don't need backup/forward any more, since we have already 
749             # assigned the correct onset times. 
750             # TODO: Let Grouping through. Also: link, print, bokmark sound
751             if not (isinstance (n, Note) or isinstance (n, Attributes) or
752                     isinstance (n, Direction) or isinstance (n, Partial) or
753                     isinstance (n, Barline) or isinstance (n, Harmony) or
754                     isinstance (n, FiguredBass) ):
755                 continue
756
757             if isinstance (n, Attributes) and not start_attr:
758                 start_attr = n
759                 continue
760
761             if isinstance (n, Attributes):
762                 # assign these only to the voices they really belongs to!
763                 for (s, vids) in staff_to_voice_dict.items ():
764                     staff_attributes = part.extract_attributes_for_staff (n, s)
765                     if staff_attributes:
766                         for v in vids:
767                             voices[v].add_element (staff_attributes)
768                 continue
769
770             if isinstance (n, Partial) or isinstance (n, Barline):
771                 for v in voices.keys ():
772                     voices[v].add_element (n)
773                 continue
774
775             if isinstance (n, Direction):
776                 staff_id = n.get_maybe_exist_named_child (u'staff')
777                 if staff_id:
778                     staff_id = staff_id.get_text ()
779                 if staff_id:
780                     dir_voices = staff_to_voice_dict.get (staff_id, voices.keys ())
781                 else:
782                     dir_voices = voices.keys ()
783                 for v in dir_voices:
784                     voices[v].add_element (n)
785                 continue
786
787             if isinstance (n, Harmony) or isinstance (n, FiguredBass):
788                 # store the harmony or figured bass element until we encounter 
789                 # the next note and assign it only to that one voice.
790                 assign_to_next_note.append (n)
791                 continue
792
793             if hasattr (n, 'print-object') and getattr (n, 'print-object') == "no":
794                 #Skip this note. 
795                 pass
796             else:
797                 for i in assign_to_next_note:
798                     voices[id].add_element (i)
799                 assign_to_next_note = []
800                 voices[id].add_element (n)
801
802         # Assign all remaining elements from assign_to_next_note to the voice
803         # of the previous note:
804         for i in assign_to_next_note:
805             voices[id].add_element (i)
806         assign_to_next_note = []
807
808         if start_attr:
809             for (s, vids) in staff_to_voice_dict.items ():
810                 staff_attributes = part.extract_attributes_for_staff (start_attr, s)
811                 staff_attributes.read_self ()
812                 part._staff_attributes_dict[s] = staff_attributes
813                 for v in vids:
814                     voices[v].insert (0, staff_attributes)
815                     voices[v]._elements[0].read_self()
816
817         part._voices = voices
818
819     def get_voices (self):
820         return self._voices
821     def get_staff_attributes (self):
822         return self._staff_attributes_dict
823
824 class Notations (Music_xml_node):
825     def get_tie (self):
826         ts = self.get_named_children ('tied')
827         starts = [t for t in ts if t.type == 'start']
828         if starts:
829             return starts[0]
830         else:
831             return None
832
833     def get_tuplets (self):
834         return self.get_typed_children (Tuplet)
835
836 class Time_modification(Music_xml_node):
837     def get_fraction (self):
838         b = self.get_maybe_exist_named_child ('actual-notes')
839         a = self.get_maybe_exist_named_child ('normal-notes')
840         return (int(a.get_text ()), int (b.get_text ()))
841
842     def get_normal_type (self):
843         tuplet_type = self.get_maybe_exist_named_child ('normal-type')
844         if tuplet_type:
845             dots = self.get_named_children ('normal-dot')
846             log = musicxml_duration_to_log (tuplet_type.get_text ().strip ())
847             return (log , len (dots))
848         else:
849             return None
850
851
852 class Accidental (Music_xml_node):
853     def __init__ (self):
854         Music_xml_node.__init__ (self)
855         self.editorial = False
856         self.cautionary = False
857
858 class Music_xml_spanner (Music_xml_node):
859     def get_type (self):
860         if hasattr (self, 'type'):
861             return self.type
862         else:
863             return 0
864     def get_size (self):
865         if hasattr (self, 'size'):
866             return string.atoi (self.size)
867         else:
868             return 0
869
870 class Wedge (Music_xml_spanner):
871     pass
872
873 class Tuplet (Music_xml_spanner):
874     def duration_info_from_tuplet_note (self, tuplet_note):
875         tuplet_type = tuplet_note.get_maybe_exist_named_child ('tuplet-type')
876         if tuplet_type:
877             dots = tuplet_note.get_named_children ('tuplet-dot')
878             log = musicxml_duration_to_log (tuplet_type.get_text ().strip ())
879             return (log, len (dots))
880         else:
881             return None
882
883     # Return tuplet note type as (log, dots)
884     def get_normal_type (self):
885         tuplet = self.get_maybe_exist_named_child ('tuplet-normal')
886         if tuplet:
887             return self.duration_info_from_tuplet_note (tuplet)
888         else:
889             return None
890
891     def get_actual_type (self):
892         tuplet = self.get_maybe_exist_named_child ('tuplet-actual')
893         if tuplet:
894             return self.duration_info_from_tuplet_note (tuplet)
895         else:
896             return None
897
898     def get_tuplet_note_count (self, tuplet_note):
899         if tuplet_note:
900             tuplet_nr = tuplet_note.get_maybe_exist_named_child ('tuplet-number')
901             if tuplet_nr: 
902                 return int (tuplet_nr.get_text ())
903         return None
904     def get_normal_nr (self):
905         return self.get_tuplet_note_count (self.get_maybe_exist_named_child ('tuplet-normal'))
906     def get_actual_nr (self):
907         return self.get_tuplet_note_count (self.get_maybe_exist_named_child ('tuplet-actual'))
908
909 class Bracket (Music_xml_spanner):
910     pass
911
912 class Dashes (Music_xml_spanner):
913     pass
914
915 class Slur (Music_xml_spanner):
916     def get_type (self):
917         return self.type
918
919 class Beam (Music_xml_spanner):
920     def get_type (self):
921         return self.get_text ()
922     def is_primary (self):
923         return self.number == "1"
924
925 class Wavy_line (Music_xml_spanner):
926     pass
927     
928 class Pedal (Music_xml_spanner):
929     pass
930
931 class Glissando (Music_xml_spanner):
932     pass
933
934 class Slide (Music_xml_spanner):
935     pass
936
937 class Octave_shift (Music_xml_spanner):
938     # default is 8 for the octave-shift!
939     def get_size (self):
940         if hasattr (self, 'size'):
941             return string.atoi (self.size)
942         else:
943             return 8
944
945 class Chord (Music_xml_node):
946     pass
947
948 class Dot (Music_xml_node):
949     pass
950
951 # Rests in MusicXML are <note> blocks with a <rest> inside. This class is only
952 # for the inner <rest> element, not the whole rest block.
953 class Rest (Music_xml_node):
954     def __init__ (self):
955         Music_xml_node.__init__ (self)
956         self._is_whole_measure = False
957     def is_whole_measure (self):
958         return self._is_whole_measure
959     def get_step (self):
960         ch = self.get_maybe_exist_typed_child (get_class (u'display-step'))
961         if ch:
962             step = ch.get_text ().strip ()
963             return step
964         else:
965             return None
966     def get_octave (self):
967         ch = self.get_maybe_exist_typed_child (get_class (u'display-octave'))
968         if ch:
969             step = ch.get_text ().strip ()
970             return int (step)
971         else:
972             return None
973
974 class Type (Music_xml_node):
975     pass
976 class Grace (Music_xml_node):
977     pass
978 class Staff (Music_xml_node):
979     pass
980
981 class Direction (Music_xml_node):
982     pass
983 class DirType (Music_xml_node):
984     pass
985
986 class Bend (Music_xml_node):
987     def bend_alter (self):
988         alter = self.get_maybe_exist_named_child ('bend-alter')
989         if alter:
990             return alter.get_text()
991         else:
992             return 0
993
994 class Words (Music_xml_node):
995     pass
996
997 class Harmony (Music_xml_node):
998     pass
999
1000 class ChordPitch (Music_xml_node):
1001     def step_class_name (self):
1002         return u'root-step'
1003     def alter_class_name (self):
1004         return u'root-alter'
1005     def get_step (self):
1006         ch = self.get_unique_typed_child (get_class (self.step_class_name ()))
1007         return ch.get_text ().strip ()
1008     def get_alteration (self):
1009         ch = self.get_maybe_exist_typed_child (get_class (self.alter_class_name ()))
1010         alter = 0
1011         if ch:
1012             alter = int (ch.get_text ().strip ())
1013         return alter
1014
1015 class Root (ChordPitch):
1016     pass
1017
1018 class Bass (ChordPitch):
1019     def step_class_name (self):
1020         return u'bass-step'
1021     def alter_class_name (self):
1022         return u'bass-alter'
1023
1024 class ChordModification (Music_xml_node):
1025     def get_type (self):
1026         ch = self.get_maybe_exist_typed_child (get_class (u'degree-type'))
1027         return {'add': 1, 'alter': 1, 'subtract': -1}.get (ch.get_text ().strip (), 0)
1028     def get_value (self):
1029         ch = self.get_maybe_exist_typed_child (get_class (u'degree-value'))
1030         value = 0
1031         if ch:
1032             value = int (ch.get_text ().strip ())
1033         return value
1034     def get_alter (self):
1035         ch = self.get_maybe_exist_typed_child (get_class (u'degree-alter'))
1036         value = 0
1037         if ch:
1038             value = int (ch.get_text ().strip ())
1039         return value
1040
1041
1042 class Frame (Music_xml_node):
1043     def get_frets (self):
1044         return self.get_named_child_value_number ('frame-frets', 4)
1045     def get_strings (self):
1046         return self.get_named_child_value_number ('frame-strings', 6)
1047     def get_first_fret (self):
1048         return self.get_named_child_value_number ('first-fret', 1)
1049
1050 class Frame_Note (Music_xml_node):
1051     def get_string (self):
1052         return self.get_named_child_value_number ('string', 1)
1053     def get_fret (self):
1054         return self.get_named_child_value_number ('fret', 0)
1055     def get_fingering (self):
1056         return self.get_named_child_value_number ('fingering', -1)
1057     def get_barre (self):
1058         n = self.get_maybe_exist_named_child ('barre')
1059         if n:
1060             return getattr (n, 'type', '')
1061         else:
1062             return ''
1063
1064 class FiguredBass (Music_xml_node):
1065     pass
1066
1067 class BeatUnit (Music_xml_node):
1068     pass
1069
1070 class BeatUnitDot (Music_xml_node):
1071     pass
1072
1073 class PerMinute (Music_xml_node):
1074     pass
1075
1076
1077
1078 ## need this, not all classes are instantiated
1079 ## for every input file. Only add those classes, that are either directly
1080 ## used by class name or extend Music_xml_node in some way!
1081 class_dict = {
1082         '#comment': Hash_comment,
1083         '#text': Hash_text,
1084         'accidental': Accidental,
1085         'attributes': Attributes,
1086         'barline': Barline,
1087         'bar-style': BarStyle,
1088         'bass': Bass,
1089         'beam' : Beam,
1090         'beat-unit': BeatUnit,
1091         'beat-unit-dot': BeatUnitDot,
1092         'bend' : Bend,
1093         'bracket' : Bracket,
1094         'chord': Chord,
1095         'dashes' : Dashes,
1096         'degree' : ChordModification,
1097         'dot': Dot,
1098         'direction': Direction,
1099         'direction-type': DirType,
1100         'duration': Duration,
1101         'elision': Elision,
1102         'frame': Frame,
1103         'frame-note': Frame_Note,
1104         'figured-bass': FiguredBass,
1105         'glissando': Glissando,
1106         'grace': Grace,
1107         'harmony': Harmony,
1108         'identification': Identification,
1109         'lyric': Lyric,
1110         'measure': Measure,
1111         'notations': Notations,
1112         'note': Note,
1113         'octave-shift': Octave_shift,
1114         'part': Part,
1115     'part-group': Part_group,
1116         'part-list': Part_list,
1117         'pedal': Pedal,
1118         'per-minute': PerMinute,
1119         'pitch': Pitch,
1120         'rest': Rest,
1121         'root': Root,
1122         'score-part': Score_part,
1123         'slide': Slide,
1124         'slur': Slur,
1125         'staff': Staff,
1126         'syllabic': Syllabic,
1127         'text': Text,
1128         'time-modification': Time_modification,
1129         'tuplet': Tuplet,
1130         'type': Type,
1131         'unpitched': Unpitched,
1132         'wavy-line': Wavy_line,
1133         'wedge': Wedge,
1134         'words': Words,
1135         'work': Work,
1136 }
1137
1138 def name2class_name (name):
1139     name = name.replace ('-', '_')
1140     name = name.replace ('#', 'hash_')
1141     name = name[0].upper() + name[1:].lower()
1142
1143     return str (name)
1144
1145 def get_class (name):
1146     classname = class_dict.get (name)
1147     if classname:
1148         return classname
1149     else:
1150         class_name = name2class_name (name)
1151         klass = new.classobj (class_name, (Music_xml_node,) , {})
1152         class_dict[name] = klass
1153         return klass
1154         
1155 def lxml_demarshal_node (node):
1156     name = node.tag
1157
1158     # Ignore comment nodes, which are also returned by the etree parser!
1159     if name is None or node.__class__.__name__ == "_Comment":
1160         return None
1161     klass = get_class (name)
1162     py_node = klass()
1163     
1164     py_node._original = node
1165     py_node._name = name
1166     py_node._data = node.text
1167     py_node._children = [lxml_demarshal_node (cn) for cn in node.getchildren()]
1168     py_node._children = filter (lambda x: x, py_node._children)
1169     
1170     for c in py_node._children:
1171         c._parent = py_node
1172
1173     for (k, v) in node.items ():
1174         py_node.__dict__[k] = v
1175         py_node._attribute_dict[k] = v
1176
1177     return py_node
1178
1179 def minidom_demarshal_node (node):
1180     name = node.nodeName
1181
1182     klass = get_class (name)
1183     py_node = klass ()
1184     py_node._name = name
1185     py_node._children = [minidom_demarshal_node (cn) for cn in node.childNodes]
1186     for c in py_node._children:
1187         c._parent = py_node
1188
1189     if node.attributes:
1190         for (nm, value) in node.attributes.items ():
1191             py_node.__dict__[nm] = value
1192             py_node._attribute_dict[nm] = value
1193             
1194     py_node._data = None
1195     if node.nodeType == node.TEXT_NODE and node.data:
1196         py_node._data = node.data 
1197
1198     py_node._original = node
1199     return py_node
1200
1201
1202 if __name__  == '__main__':
1203     import lxml.etree
1204         
1205     tree = lxml.etree.parse ('beethoven.xml')
1206     mxl_tree = lxml_demarshal_node (tree.getroot ())
1207     ks = class_dict.keys ()
1208     ks.sort ()
1209     print '\n'.join (ks)