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