]> git.donarmstrong.com Git - lilypond.git/blob - python/musicxml.py
MusicXML: Conversion of various types of spanners (octave, pedals, trills)
[lilypond.git] / python / musicxml.py
1 import new
2 import string
3 from rational import *
4
5 class Xml_node:
6     def __init__ (self):
7         self._children = []
8         self._data = None
9         self._original = None
10         self._name = 'xml_node'
11         self._parent = None
12         self._attribute_dict = {}
13         
14     def get_parent (self):
15         return self._parent
16     
17     def is_first (self):
18         return self._parent.get_typed_children (self.__class__)[0] == self
19
20     def original (self):
21         return self._original 
22     def get_name (self):
23         return self._name
24
25     def get_text (self):
26         if self._data:
27             return self._data
28
29         if not self._children:
30             return ''
31
32         return ''.join ([c.get_text () for c in self._children])
33
34     def message (self, msg):
35         print msg
36
37         p = self
38         while p:
39             print '  In: <%s %s>' % (p._name, ' '.join (['%s=%s' % item for item in p._attribute_dict.items()]))
40             p = p.get_parent ()
41         
42     def get_typed_children (self, klass):
43         if not klass:
44             return []
45         else:
46             return [c for c in self._children if isinstance(c, klass)]
47
48     def get_named_children (self, nm):
49         return self.get_typed_children (get_class (nm))
50
51     def get_named_child (self, nm):
52         return self.get_maybe_exist_named_child (nm)
53
54     def get_children (self, predicate):
55         return [c for c in self._children if predicate(c)]
56
57     def get_all_children (self):
58         return self._children
59
60     def get_maybe_exist_named_child (self, name):
61         return self.get_maybe_exist_typed_child (get_class (name))
62
63     def get_maybe_exist_typed_child (self, klass):
64         cn = self.get_typed_children (klass)
65         if len (cn)==0:
66             return None
67         elif len (cn) == 1:
68             return cn[0]
69         else:
70             raise "More than 1 child", klass
71
72     def get_unique_typed_child (self, klass):
73         cn = self.get_typed_children(klass)
74         if len (cn) <> 1:
75             print self.__dict__ 
76             raise 'Child is not unique for', (klass, 'found', cn)
77
78         return cn[0]
79
80 class Music_xml_node (Xml_node):
81     def __init__ (self):
82         Xml_node.__init__ (self)
83         self.duration = Rational (0)
84         self.start = Rational (0)
85
86 class Work (Xml_node):
87     def get_work_information (self, tag):
88         wt = self.get_maybe_exist_named_child (tag)
89         if wt:
90             return wt.get_text ()
91         else:
92             return ''
93       
94     def get_work_title (self):
95         return self.get_work_information ('work-title')
96     def get_work_number (self):
97         return self.get_work_information ('work-number')
98     def get_opus (self):
99         return self.get_work_information ('opus')
100
101 class Identification (Xml_node):
102     def get_rights (self):
103         rights = self.get_maybe_exist_named_child ('rights')
104         if rights:
105             return rights.get_text ()
106         else:
107             return ''
108
109     def get_creator (self, type):
110         creators = self.get_named_children ('creator')
111         # return the first creator tag that has type 'editor'
112         for i in creators:
113             if hasattr (i, 'type') and i.type == type:
114                 return i.get_text ()
115             else:
116                 return ''
117
118     def get_composer (self):
119         c = self.get_creator ('composer')
120         if c:
121             return c
122         creators = self.get_named_children ('creator')
123         # return the first creator tag that has no type at all
124         for i in creators:
125             if not hasattr (i, 'type'):
126                 return i.get_text ()
127         return c
128     def get_arranger (self):
129         return self.get_creator ('arranger')
130     def get_editor (self):
131         return self.get_creator ('editor')
132     def get_poet (self):
133         return self.get_creator ('poet')
134     
135     def get_encoding_information (self, type):
136         enc = self.get_named_children ('encoding')
137         if enc:
138             children = enc[0].get_named_children (type)
139             if children:
140                 return children[0].get_text ()
141         else:
142             return ''
143       
144     def get_encoding_software (self):
145         return self.get_encoding_information ('software')
146     def get_encoding_date (self):
147         return self.get_encoding_information ('encoding-date')
148     def get_encoding_person (self):
149         return self.get_encoding_information ('encoder')
150     def get_encoding_description (self):
151         return self.get_encoding_information ('encoding-description')
152
153
154 class Duration (Music_xml_node):
155     def get_length (self):
156         dur = int (self.get_text ()) * Rational (1,4)
157         return dur
158
159 class Hash_comment (Music_xml_node):
160     pass
161
162 class Pitch (Music_xml_node):
163     def get_step (self):
164         ch = self.get_unique_typed_child (get_class (u'step'))
165         step = ch.get_text ().strip ()
166         return step
167     def get_octave (self):
168         ch = self.get_unique_typed_child (get_class (u'octave'))
169
170         step = ch.get_text ().strip ()
171         return int (step)
172
173     def get_alteration (self):
174         ch = self.get_maybe_exist_typed_child (get_class (u'alter'))
175         alter = 0
176         if ch:
177             alter = int (ch.get_text ().strip ())
178         return alter
179
180 class Measure_element (Music_xml_node):
181     def get_voice_id (self):
182         voice_id = self.get_maybe_exist_named_child ('voice')
183         if voice_id:
184             return voice_id.get_text ()
185         else:
186             return None
187
188     def is_first (self):
189         cn = self._parent.get_typed_children (self.__class__)
190         cn = [c for c in cn if c.get_voice_id () == self.get_voice_id ()]
191         return cn[0] == self
192
193 class Attributes (Measure_element):
194     def __init__ (self):
195         Measure_element.__init__ (self)
196         self._dict = {}
197
198     def set_attributes_from_previous (self, dict):
199         self._dict.update (dict)
200         
201     def read_self (self):
202         for c in self.get_all_children ():
203             self._dict[c.get_name()] = c
204
205     def get_named_attribute (self, name):
206         return self._dict.get (name)
207
208     def get_measure_length (self):
209         (n,d) = self.get_time_signature ()
210         return Rational (n,d)
211         
212     def get_time_signature (self):
213         "return time sig as a (beat, beat-type) tuple"
214
215         try:
216             mxl = self.get_named_attribute ('time')
217             
218             beats = mxl.get_maybe_exist_named_child ('beats')
219             type = mxl.get_maybe_exist_named_child ('beat-type')
220             return (int (beats.get_text ()),
221                     int (type.get_text ()))
222         except KeyError:
223             print 'error: requested time signature, but time sig unknown'
224             return (4, 4)
225
226     def get_clef_sign (self):
227         mxl = self.get_named_attribute ('clef')
228         sign = mxl.get_maybe_exist_named_child ('sign')
229         if sign:
230             return sign.get_text ()
231         else:
232             print 'clef requested, but unknow'
233             return 'G'
234
235     def get_key_signature (self):
236         "return (fifths, mode) tuple"
237
238         key = self.get_named_attribute ('key')
239         mode_node = key.get_maybe_exist_named_child ('mode')
240         mode = 'major'
241         if mode_node:
242             mode = mode_node.get_text ()
243
244         fifths = int (key.get_maybe_exist_named_child ('fifths').get_text ())
245         return (fifths, mode)
246                 
247
248 class Note (Measure_element):
249     def __init__ (self):
250         Measure_element.__init__ (self)
251         self.instrument_name = ''
252         
253     def get_duration_log (self):
254         ch = self.get_maybe_exist_typed_child (get_class (u'type'))
255
256         if ch:
257             log = ch.get_text ().strip()
258             return {'eighth': 3,
259                     'quarter': 2,
260                     'half': 1,
261                     '16th': 4,
262                     '32nd': 5,
263                      'breve': -1,
264                     'long': -2,
265                     'whole': 0}.get (log)
266         else:
267             return 0
268
269     def get_factor (self):
270         return 1
271
272     def get_pitches (self):
273         return self.get_typed_children (get_class (u'pitch'))
274
275 class Part_list (Music_xml_node):
276     def __init__ (self):
277         Music_xml_node.__init__ (self)
278         self._id_instrument_name_dict = {}
279         
280     def generate_id_instrument_dict (self):
281
282         ## not empty to make sure this happens only once.
283         mapping = {1: 1}
284         for score_part in self.get_named_children ('score-part'):
285             for instr in score_part.get_named_children ('score-instrument'):
286                 id = instr.id
287                 name = instr.get_named_child ("instrument-name")
288                 mapping[id] = name.get_text ()
289
290         self._id_instrument_name_dict = mapping
291
292     def get_instrument (self, id):
293         if not self._id_instrument_name_dict:
294             self.generate_id_instrument_dict()
295
296         instrument_name = self._id_instrument_name_dict.get (id)
297         if instrument_name:
298             return instrument_name
299         else:
300             print "Opps, couldn't find instrument for ID=", id
301             return "Grand Piano"
302         
303 class Measure (Music_xml_node):
304     def get_notes (self):
305         return self.get_typed_children (get_class (u'note'))
306
307 class Syllabic (Music_xml_node):
308     def continued (self):
309         text = self.get_text()
310         return (text == "begin") or (text == "middle")
311 class Text (Music_xml_node):
312     pass
313
314 class Lyric (Music_xml_node):
315     def get_number (self):
316         if hasattr (self, 'number'):
317             return self.number
318         else:
319             return -1
320
321     def lyric_to_text (self):
322         continued = False
323         syllabic = self.get_maybe_exist_typed_child (Syllabic)
324         if syllabic:
325             continued = syllabic.continued ()
326         text = self.get_maybe_exist_typed_child (Text)
327         
328         if text:
329             text = text.get_text()
330             # We need to convert soft hyphens to -, otherwise the ascii codec as well
331             # as lilypond will barf on that character
332             text = string.replace( text, u'\xad', '-' )
333         
334         if text == "-" and continued:
335             return "--"
336         elif text == "_" and continued:
337             return "__"
338         elif continued and text:
339             return "\"" + text + "\" --"
340         elif continued:
341             return "--"
342         elif text:
343             return "\"" + text + "\""
344         else:
345             return ""
346
347 class Musicxml_voice:
348     def __init__ (self):
349         self._elements = []
350         self._staves = {}
351         self._start_staff = None
352         self._lyrics = []
353         self._has_lyrics = False
354
355     def add_element (self, e):
356         self._elements.append (e)
357         if (isinstance (e, Note)
358             and e.get_maybe_exist_typed_child (Staff)):
359             name = e.get_maybe_exist_typed_child (Staff).get_text ()
360
361             if not self._start_staff:
362                 self._start_staff = name
363             self._staves[name] = True
364
365         lyrics = e.get_typed_children (Lyric)
366         if not self._has_lyrics:
367           self.has_lyrics = len (lyrics) > 0
368
369         for l in lyrics:
370             nr = l.get_number()
371             if (nr > 0) and not (nr in self._lyrics):
372                 self._lyrics.append (nr)
373
374     def insert (self, idx, e):
375         self._elements.insert (idx, e)
376
377     def get_lyrics_numbers (self):
378         if (len (self._lyrics) == 0) and self._has_lyrics:
379             #only happens if none of the <lyric> tags has a number attribute
380             return [1]
381         else:
382             return self._lyrics
383
384
385
386 class Part (Music_xml_node):
387     def __init__ (self):
388         Music_xml_node.__init__ (self)
389         self._voices = []
390
391     def get_part_list (self):
392         n = self
393         while n and n.get_name() != 'score-partwise':
394             n = n._parent
395
396         return n.get_named_child ('part-list')
397         
398     def interpret (self):
399         """Set durations and starting points."""
400         
401         part_list = self.get_part_list ()
402         
403         now = Rational (0)
404         factor = Rational (1)
405         attributes_dict = {}
406         attributes_object = None
407         measures = self.get_typed_children (Measure)
408         last_moment = Rational (-1)
409         last_measure_position = Rational (-1)
410         for m in measures:
411             measure_start_moment = now
412             measure_position = Rational (0)
413             for n in m.get_all_children ():
414                 dur = Rational (0)
415
416                 if n.__class__ == Attributes:
417                     n.set_attributes_from_previous (attributes_dict)
418                     n.read_self ()
419                     attributes_dict = n._dict.copy ()
420                     attributes_object = n
421                     
422                     factor = Rational (1,
423                                        int (attributes_dict.get ('divisions').get_text ()))
424
425                 
426                 if (n.get_maybe_exist_typed_child (Duration)):
427                     mxl_dur = n.get_maybe_exist_typed_child (Duration)
428                     dur = mxl_dur.get_length () * factor
429                     
430                     if n.get_name() == 'backup':
431                         dur = - dur
432                     if n.get_maybe_exist_typed_child (Grace):
433                         dur = Rational (0)
434
435                     rest = n.get_maybe_exist_typed_child (Rest)
436                     if (rest
437                         and attributes_object
438                         and attributes_object.get_measure_length () == dur):
439
440                         rest._is_whole_measure = True
441
442                 if (dur > Rational (0) 
443                     and n.get_maybe_exist_typed_child (Chord)):
444                     now = last_moment
445                     measure_position = last_measure_position
446                     
447                 last_moment = now
448                 last_measure_position = measure_position
449
450                 n._when = now
451                 n._measure_position = measure_position
452                 n._duration = dur
453                 now += dur
454                 measure_position += dur
455                 if n._name == 'note':
456                     instrument = n.get_maybe_exist_named_child ('instrument')
457                     if instrument:
458                         n.instrument_name = part_list.get_instrument (instrument.id)
459
460             if attributes_object:
461                 length = attributes_object.get_measure_length ()
462                 new_now = measure_start_moment + length
463                 
464                 if now <> new_now:
465                     problem = 'incomplete'
466                     if now > new_now:
467                         problem = 'overfull'
468
469                     ## only for verbose operation.
470                     if problem <> 'incomplete':
471                         m.message ('%s measure? Expected: %s, Difference: %s' % (problem, now, new_now - now))
472
473                 now = new_now
474
475     def extract_voices (part):
476         voices = {}
477         measures = part.get_typed_children (Measure)
478         elements = []
479         for m in measures:
480             elements.extend (m.get_all_children ())
481
482         start_attr = None
483         for n in elements:
484             voice_id = n.get_maybe_exist_typed_child (get_class ('voice'))
485
486             # TODO: If the first element of a voice is a dynamics entry,
487             #       then voice_id is not yet set! Thus it will currently be ignored
488             if not (voice_id or isinstance (n, Attributes) or isinstance (n, Direction) ):
489                 continue
490
491             if isinstance (n, Attributes) and not start_attr:
492                 start_attr = n
493                 continue
494
495             if isinstance (n, Attributes) or isinstance (n, Direction):
496                 for v in voices.values ():
497                     v.add_element (n)
498                 continue
499
500             id = voice_id.get_text ()
501             if not voices.has_key (id):
502                 voices[id] = Musicxml_voice()
503
504             voices[id].add_element (n)
505
506         if start_attr:
507             for (k,v) in voices.items ():
508                 v.insert (0, start_attr)
509
510         part._voices = voices
511
512     def get_voices (self):
513         return self._voices
514
515 class Notations (Music_xml_node):
516     def get_tie (self):
517         ts = self.get_named_children ('tied')
518         starts = [t for t in ts if t.type == 'start']
519         if starts:
520             return starts[0]
521         else:
522             return None
523
524     def get_tuplet (self):
525         return self.get_maybe_exist_typed_child (Tuplet)
526
527 class Time_modification(Music_xml_node):
528     def get_fraction (self):
529         b = self.get_maybe_exist_named_child ('actual-notes')
530         a = self.get_maybe_exist_named_child ('normal-notes')
531         return (int(a.get_text ()), int (b.get_text ()))
532
533 class Accidental (Music_xml_node):
534     def __init__ (self):
535         Music_xml_node.__init__ (self)
536         self.editorial = False
537         self.cautionary = False
538
539 class Music_xml_spanner (Music_xml_node):
540     def get_type (self):
541         if hasattr (self, 'type'):
542             return self.type
543         else:
544             return 0
545     def get_size (self):
546         if hasattr (self, 'size'):
547             return string.atoi (self.size)
548         else:
549             return 0
550
551 class Tuplet(Music_xml_spanner):
552     pass
553
554 class Slur (Music_xml_spanner):
555     def get_type (self):
556         return self.type
557
558 class Beam (Music_xml_spanner):
559     def get_type (self):
560         return self.get_text ()
561     def is_primary (self):
562         return self.number == "1"
563
564 class Wavy_line (Music_xml_spanner):
565     pass
566     
567 class Pedal (Music_xml_spanner):
568     pass
569
570 class Glissando (Music_xml_spanner):
571     pass
572
573 class Octave_shift (Music_xml_spanner):
574     # default is 8 for the octave-shift!
575     def get_size (self):
576         if hasattr (self, 'size'):
577             return string.atoi (self.size)
578         else:
579             return 8
580
581 class Chord (Music_xml_node):
582     pass
583
584 class Dot (Music_xml_node):
585     pass
586
587 class Rest (Music_xml_node):
588     def __init__ (self):
589         Music_xml_node.__init__ (self)
590         self._is_whole_measure = False
591     def is_whole_measure (self):
592         return self._is_whole_measure
593
594 class Type (Music_xml_node):
595     pass
596 class Grace (Music_xml_node):
597     pass
598 class Staff (Music_xml_node):
599     pass
600
601 class Direction (Music_xml_node):
602     pass
603 class DirType (Music_xml_node):
604     pass
605
606 class Bend (Music_xml_node):
607     def bend_alter (self):
608         alter = self.get_maybe_exist_named_child ('bend-alter')
609         if alter:
610             return alter.get_text()
611         else:
612             return 0
613
614
615
616 ## need this, not all classes are instantiated
617 ## for every input file. Only add those classes, that are either directly
618 ## used by class name or extend Music_xml_node in some way!
619 class_dict = {
620         '#comment': Hash_comment,
621         'accidental': Accidental,
622         'attributes': Attributes,
623         'beam' : Beam,
624         'bend' : Bend,
625         'chord': Chord,
626         'dot': Dot,
627         'direction': Direction,
628         'direction-type': DirType,
629         'duration': Duration,
630         'glissando': Glissando,
631         'grace': Grace,
632         'identification': Identification,
633         'lyric': Lyric,
634         'measure': Measure,
635         'notations': Notations,
636         'note': Note,
637         'octave-shift': Octave_shift,
638         'part': Part,
639         'part-list': Part_list,
640         'pedal': Pedal,
641         'pitch': Pitch,
642         'rest': Rest,
643         'slur': Slur,
644         'syllabic': Syllabic,
645         'text': Text,
646         'time-modification': Time_modification,
647         'tuplet': Tuplet,
648         'type': Type,
649         'wavy-line': Wavy_line,
650         'work': Work,
651 }
652
653 def name2class_name (name):
654     name = name.replace ('-', '_')
655     name = name.replace ('#', 'hash_')
656     name = name[0].upper() + name[1:].lower()
657
658     return str (name)
659
660 def get_class (name):
661     classname = class_dict.get (name)
662     if classname:
663         return classname
664     else:
665         class_name = name2class_name (name)
666         klass = new.classobj (class_name, (Music_xml_node,) , {})
667         class_dict[name] = klass
668         return klass
669         
670 def lxml_demarshal_node (node):
671     name = node.tag
672
673     if name is None:
674         return None
675     klass = get_class (name)
676     py_node = klass()
677     
678     py_node._original = node
679     py_node._name = name
680     py_node._data = node.text
681     py_node._children = [lxml_demarshal_node (cn) for cn in node.getchildren()]
682     py_node._children = filter (lambda x: x, py_node._children)
683     
684     for c in py_node._children:
685         c._parent = py_node
686
687     for (k,v) in node.items ():
688         py_node.__dict__[k] = v
689         py_node._attribute_dict[k] = v
690
691     return py_node
692
693 def minidom_demarshal_node (node):
694     name = node.nodeName
695
696     klass = get_class (name)
697     py_node = klass()
698     py_node._name = name
699     py_node._children = [minidom_demarshal_node (cn) for cn in node.childNodes]
700     for c in py_node._children:
701         c._parent = py_node
702
703     if node.attributes:
704         for (nm, value) in node.attributes.items():
705             py_node.__dict__[nm] = value
706             py_node._attribute_dict[nm] = value
707             
708     py_node._data = None
709     if node.nodeType == node.TEXT_NODE and node.data:
710         py_node._data = node.data 
711
712     py_node._original = node
713     return py_node
714
715
716 if __name__  == '__main__':
717         import lxml.etree
718         
719         tree = lxml.etree.parse ('beethoven.xml')
720         mxl_tree = lxml_demarshal_node (tree.getroot ())
721         ks = class_dict.keys()
722         ks.sort()
723         print '\n'.join (ks)