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