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