]> git.donarmstrong.com Git - lilypond.git/blob - python/musicxml.py
MusicXML: Use "None" voice only for notes w/o voice, not for directions or attrs
[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             elif isinstance (n, Note):
709                 vid = "None"
710
711             staff_id = n.get_maybe_exist_named_child (u'staff')
712             sid = None
713             if staff_id:
714                 sid = staff_id.get_text ()
715             else:
716                 sid = "None"
717             if vid and not voices.has_key (vid):
718                 voices[vid] = Musicxml_voice()
719             if vid and sid and not n.get_maybe_exist_typed_child (Grace):
720                 if not voice_to_staff_dict.has_key (vid):
721                     voice_to_staff_dict[vid] = sid
722         # invert the voice_to_staff_dict into a staff_to_voice_dict (since we
723         # need to assign staff-assigned objects like clefs, times, etc. to
724         # all the correct voices. This will never work entirely correct due
725         # to staff-switches, but that's the best we can do!
726         staff_to_voice_dict = {}
727         for (v,s) in voice_to_staff_dict.items ():
728             if not staff_to_voice_dict.has_key (s):
729                 staff_to_voice_dict[s] = [v]
730             else:
731                 staff_to_voice_dict[s].append (v)
732
733
734         start_attr = None
735         assign_to_next_note = []
736         id = None
737         for n in elements:
738             voice_id = n.get_maybe_exist_typed_child (get_class ('voice'))
739             if voice_id:
740                 id = voice_id.get_text ()
741             else:
742                 id = "None"
743
744             # We don't need backup/forward any more, since we have already 
745             # assigned the correct onset times. 
746             # TODO: Let Grouping through. Also: link, print, bokmark sound
747             if not (isinstance (n, Note) or isinstance (n, Attributes) or
748                     isinstance (n, Direction) or isinstance (n, Partial) or
749                     isinstance (n, Barline) or isinstance (n, Harmony) or
750                     isinstance (n, FiguredBass) ):
751                 continue
752
753             if isinstance (n, Attributes) and not start_attr:
754                 start_attr = n
755                 continue
756
757             if isinstance (n, Attributes):
758                 # assign these only to the voices they really belongs to!
759                 for (s, vids) in staff_to_voice_dict.items ():
760                     staff_attributes = part.extract_attributes_for_staff (n, s)
761                     if staff_attributes:
762                         for v in vids:
763                             voices[v].add_element (staff_attributes)
764                 continue
765
766             if isinstance (n, Partial) or isinstance (n, Barline):
767                 for v in voices.keys ():
768                     voices[v].add_element (n)
769                 continue
770
771             if isinstance (n, Direction):
772                 staff_id = n.get_maybe_exist_named_child (u'staff')
773                 if staff_id:
774                     staff_id = staff_id.get_text ()
775                 if staff_id:
776                     dir_voices = staff_to_voice_dict.get (staff_id, voices.keys ())
777                 else:
778                     dir_voices = voices.keys ()
779                 for v in dir_voices:
780                     voices[v].add_element (n)
781                 continue
782
783             if isinstance (n, Harmony) or isinstance (n, FiguredBass):
784                 # store the harmony or figured bass element until we encounter 
785                 # the next note and assign it only to that one voice.
786                 assign_to_next_note.append (n)
787                 continue
788
789             if hasattr (n, 'print-object') and getattr (n, 'print-object') == "no":
790                 #Skip this note. 
791                 pass
792             else:
793                 for i in assign_to_next_note:
794                     voices[id].add_element (i)
795                 assign_to_next_note = []
796                 voices[id].add_element (n)
797
798         # Assign all remaining elements from assign_to_next_note to the voice
799         # of the previous note:
800         for i in assign_to_next_note:
801             voices[id].add_element (i)
802         assign_to_next_note = []
803
804         if start_attr:
805             for (s, vids) in staff_to_voice_dict.items ():
806                 staff_attributes = part.extract_attributes_for_staff (start_attr, s)
807                 staff_attributes.read_self ()
808                 part._staff_attributes_dict[s] = staff_attributes
809                 for v in vids:
810                     voices[v].insert (0, staff_attributes)
811                     voices[v]._elements[0].read_self()
812
813         part._voices = voices
814
815     def get_voices (self):
816         return self._voices
817     def get_staff_attributes (self):
818         return self._staff_attributes_dict
819
820 class Notations (Music_xml_node):
821     def get_tie (self):
822         ts = self.get_named_children ('tied')
823         starts = [t for t in ts if t.type == 'start']
824         if starts:
825             return starts[0]
826         else:
827             return None
828
829     def get_tuplets (self):
830         return self.get_typed_children (Tuplet)
831
832 class Time_modification(Music_xml_node):
833     def get_fraction (self):
834         b = self.get_maybe_exist_named_child ('actual-notes')
835         a = self.get_maybe_exist_named_child ('normal-notes')
836         return (int(a.get_text ()), int (b.get_text ()))
837
838 class Accidental (Music_xml_node):
839     def __init__ (self):
840         Music_xml_node.__init__ (self)
841         self.editorial = False
842         self.cautionary = False
843
844 class Music_xml_spanner (Music_xml_node):
845     def get_type (self):
846         if hasattr (self, 'type'):
847             return self.type
848         else:
849             return 0
850     def get_size (self):
851         if hasattr (self, 'size'):
852             return string.atoi (self.size)
853         else:
854             return 0
855
856 class Wedge (Music_xml_spanner):
857     pass
858
859 class Tuplet (Music_xml_spanner):
860     pass
861
862 class Bracket (Music_xml_spanner):
863     pass
864
865 class Dashes (Music_xml_spanner):
866     pass
867
868 class Slur (Music_xml_spanner):
869     def get_type (self):
870         return self.type
871
872 class Beam (Music_xml_spanner):
873     def get_type (self):
874         return self.get_text ()
875     def is_primary (self):
876         return self.number == "1"
877
878 class Wavy_line (Music_xml_spanner):
879     pass
880     
881 class Pedal (Music_xml_spanner):
882     pass
883
884 class Glissando (Music_xml_spanner):
885     pass
886
887 class Slide (Music_xml_spanner):
888     pass
889
890 class Octave_shift (Music_xml_spanner):
891     # default is 8 for the octave-shift!
892     def get_size (self):
893         if hasattr (self, 'size'):
894             return string.atoi (self.size)
895         else:
896             return 8
897
898 class Chord (Music_xml_node):
899     pass
900
901 class Dot (Music_xml_node):
902     pass
903
904 # Rests in MusicXML are <note> blocks with a <rest> inside. This class is only
905 # for the inner <rest> element, not the whole rest block.
906 class Rest (Music_xml_node):
907     def __init__ (self):
908         Music_xml_node.__init__ (self)
909         self._is_whole_measure = False
910     def is_whole_measure (self):
911         return self._is_whole_measure
912     def get_step (self):
913         ch = self.get_maybe_exist_typed_child (get_class (u'display-step'))
914         if ch:
915             step = ch.get_text ().strip ()
916             return step
917         else:
918             return None
919     def get_octave (self):
920         ch = self.get_maybe_exist_typed_child (get_class (u'display-octave'))
921         if ch:
922             step = ch.get_text ().strip ()
923             return int (step)
924         else:
925             return None
926
927 class Type (Music_xml_node):
928     pass
929 class Grace (Music_xml_node):
930     pass
931 class Staff (Music_xml_node):
932     pass
933
934 class Direction (Music_xml_node):
935     pass
936 class DirType (Music_xml_node):
937     pass
938
939 class Bend (Music_xml_node):
940     def bend_alter (self):
941         alter = self.get_maybe_exist_named_child ('bend-alter')
942         if alter:
943             return alter.get_text()
944         else:
945             return 0
946
947 class Words (Music_xml_node):
948     pass
949
950 class Harmony (Music_xml_node):
951     pass
952
953 class ChordPitch (Music_xml_node):
954     def step_class_name (self):
955         return u'root-step'
956     def alter_class_name (self):
957         return u'root-alter'
958     def get_step (self):
959         ch = self.get_unique_typed_child (get_class (self.step_class_name ()))
960         return ch.get_text ().strip ()
961     def get_alteration (self):
962         ch = self.get_maybe_exist_typed_child (get_class (self.alter_class_name ()))
963         alter = 0
964         if ch:
965             alter = int (ch.get_text ().strip ())
966         return alter
967
968 class Root (ChordPitch):
969     pass
970
971 class Bass (ChordPitch):
972     def step_class_name (self):
973         return u'bass-step'
974     def alter_class_name (self):
975         return u'bass-alter'
976
977 class ChordModification (Music_xml_node):
978     def get_type (self):
979         ch = self.get_maybe_exist_typed_child (get_class (u'degree-type'))
980         return {'add': 1, 'alter': 1, 'subtract': -1}.get (ch.get_text ().strip (), 0)
981     def get_value (self):
982         ch = self.get_maybe_exist_typed_child (get_class (u'degree-value'))
983         value = 0
984         if ch:
985             value = int (ch.get_text ().strip ())
986         return value
987     def get_alter (self):
988         ch = self.get_maybe_exist_typed_child (get_class (u'degree-alter'))
989         value = 0
990         if ch:
991             value = int (ch.get_text ().strip ())
992         return value
993
994
995 class Frame (Music_xml_node):
996     def get_frets (self):
997         return self.get_named_child_value_number ('frame-frets', 4)
998     def get_strings (self):
999         return self.get_named_child_value_number ('frame-strings', 6)
1000     def get_first_fret (self):
1001         return self.get_named_child_value_number ('first-fret', 1)
1002
1003 class Frame_Note (Music_xml_node):
1004     def get_string (self):
1005         return self.get_named_child_value_number ('string', 1)
1006     def get_fret (self):
1007         return self.get_named_child_value_number ('fret', 0)
1008     def get_fingering (self):
1009         return self.get_named_child_value_number ('fingering', -1)
1010     def get_barre (self):
1011         n = self.get_maybe_exist_named_child ('barre')
1012         if n:
1013             return getattr (n, 'type', '')
1014         else:
1015             return ''
1016
1017 class FiguredBass (Music_xml_node):
1018     pass
1019
1020 class BeatUnit (Music_xml_node):
1021     pass
1022
1023 class BeatUnitDot (Music_xml_node):
1024     pass
1025
1026 class PerMinute (Music_xml_node):
1027     pass
1028
1029
1030
1031 ## need this, not all classes are instantiated
1032 ## for every input file. Only add those classes, that are either directly
1033 ## used by class name or extend Music_xml_node in some way!
1034 class_dict = {
1035         '#comment': Hash_comment,
1036         '#text': Hash_text,
1037         'accidental': Accidental,
1038         'attributes': Attributes,
1039         'barline': Barline,
1040         'bar-style': BarStyle,
1041         'bass': Bass,
1042         'beam' : Beam,
1043         'beat-unit': BeatUnit,
1044         'beat-unit-dot': BeatUnitDot,
1045         'bend' : Bend,
1046         'bracket' : Bracket,
1047         'chord': Chord,
1048         'dashes' : Dashes,
1049         'degree' : ChordModification,
1050         'dot': Dot,
1051         'direction': Direction,
1052         'direction-type': DirType,
1053         'duration': Duration,
1054         'frame': Frame,
1055         'frame-note': Frame_Note,
1056         'figured-bass': FiguredBass,
1057         'glissando': Glissando,
1058         'grace': Grace,
1059         'harmony': Harmony,
1060         'identification': Identification,
1061         'lyric': Lyric,
1062         'measure': Measure,
1063         'notations': Notations,
1064         'note': Note,
1065         'octave-shift': Octave_shift,
1066         'part': Part,
1067     'part-group': Part_group,
1068         'part-list': Part_list,
1069         'pedal': Pedal,
1070         'per-minute': PerMinute,
1071         'pitch': Pitch,
1072         'rest': Rest,
1073         'root': Root,
1074         'score-part': Score_part,
1075         'slide': Slide,
1076         'slur': Slur,
1077         'staff': Staff,
1078         'syllabic': Syllabic,
1079         'text': Text,
1080         'time-modification': Time_modification,
1081         'tuplet': Tuplet,
1082         'type': Type,
1083         'unpitched': Unpitched,
1084         'wavy-line': Wavy_line,
1085         'wedge': Wedge,
1086         'words': Words,
1087         'work': Work,
1088 }
1089
1090 def name2class_name (name):
1091     name = name.replace ('-', '_')
1092     name = name.replace ('#', 'hash_')
1093     name = name[0].upper() + name[1:].lower()
1094
1095     return str (name)
1096
1097 def get_class (name):
1098     classname = class_dict.get (name)
1099     if classname:
1100         return classname
1101     else:
1102         class_name = name2class_name (name)
1103         klass = new.classobj (class_name, (Music_xml_node,) , {})
1104         class_dict[name] = klass
1105         return klass
1106         
1107 def lxml_demarshal_node (node):
1108     name = node.tag
1109
1110     # Ignore comment nodes, which are also returned by the etree parser!
1111     if name is None or node.__class__.__name__ == "_Comment":
1112         return None
1113     klass = get_class (name)
1114     py_node = klass()
1115     
1116     py_node._original = node
1117     py_node._name = name
1118     py_node._data = node.text
1119     py_node._children = [lxml_demarshal_node (cn) for cn in node.getchildren()]
1120     py_node._children = filter (lambda x: x, py_node._children)
1121     
1122     for c in py_node._children:
1123         c._parent = py_node
1124
1125     for (k, v) in node.items ():
1126         py_node.__dict__[k] = v
1127         py_node._attribute_dict[k] = v
1128
1129     return py_node
1130
1131 def minidom_demarshal_node (node):
1132     name = node.nodeName
1133
1134     klass = get_class (name)
1135     py_node = klass ()
1136     py_node._name = name
1137     py_node._children = [minidom_demarshal_node (cn) for cn in node.childNodes]
1138     for c in py_node._children:
1139         c._parent = py_node
1140
1141     if node.attributes:
1142         for (nm, value) in node.attributes.items ():
1143             py_node.__dict__[nm] = value
1144             py_node._attribute_dict[nm] = value
1145             
1146     py_node._data = None
1147     if node.nodeType == node.TEXT_NODE and node.data:
1148         py_node._data = node.data 
1149
1150     py_node._original = node
1151     return py_node
1152
1153
1154 if __name__  == '__main__':
1155     import lxml.etree
1156         
1157     tree = lxml.etree.parse ('beethoven.xml')
1158     mxl_tree = lxml_demarshal_node (tree.getroot ())
1159     ks = class_dict.keys ()
1160     ks.sort ()
1161     print '\n'.join (ks)