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