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