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