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