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