]> git.donarmstrong.com Git - lilypond.git/blob - python/musicexp.py
* scripts/musicxml2ly.py (progress): new function
[lilypond.git] / python / musicexp.py
1 import inspect
2 import sys
3 import string
4 import re
5
6 from rational import Rational
7
8 class Output_stack_element:
9         def __init__ (self):
10                 self.factor = Rational (1)
11         def copy (self):
12                 o = Output_stack_element()
13                 o.factor = self.factor
14                 return o
15
16 class Output_printer:
17         ## TODO: support for \relative.
18         
19         def __init__ (self):
20                 self.line = ''
21                 self.indent = 4
22                 self.nesting = 0
23                 self.file = sys.stdout
24                 self.line_len = 72
25                 self.output_state_stack = [Output_stack_element()]
26                 self._skipspace = False
27                 self.last_duration = None
28                 
29         def get_indent (self):
30                 return self.nesting * self.indent
31         
32         def override (self):
33                 last = self.output_state_stack[-1]
34                 self.output_state_stack.append (last.copy())
35                 
36         def add_factor (self, factor):
37                 self.override()
38                 self.output_state_stack[-1].factor *=  factor
39
40         def revert (self):
41                 del self.output_state_stack[-1]
42                 if not self.output_state_stack:
43                         raise 'empty'
44
45         def duration_factor (self):
46                 return self.output_state_stack[-1].factor
47
48         def print_verbatim (self, str):
49                 self.line += str
50                 
51         def print_duration_string (self, str):
52                 if self.last_duration == str:
53                         return
54                 
55                 self.print_verbatim (str)
56                                      
57         def add_word (self, str):
58                 if (len (str) + 1 + len (self.line) > self.line_len):
59                         self.newline()
60                         self._skipspace = True
61
62                 self.nesting += str.count ('<') + str.count ('{')
63                 self.nesting -= str.count ('>') + str.count ('}')
64
65                 if not self._skipspace:
66                         self.line += ' '
67                 self.line += str
68                 self._skipspace = False
69                 
70         def newline (self):
71                 self.file.write (self.line + '\n')
72                 self.line = ' ' * self.indent * self.nesting
73                 self._skipspace = True
74
75         def skipspace (self):
76                 self._skipspace = True
77                 
78         def __call__(self, arg):
79                 self.dump (arg)
80         
81         def dump (self, str):
82                 if self._skipspace:
83                         self._skipspace = False
84                         self.print_verbatim (str)
85                 else:
86                         words = string.split (str)
87                         for w in words:
88                                 self.add_word (w)
89
90 class Duration:
91         def __init__ (self):
92                 self.duration_log = 0
93                 self.dots = 0
94                 self.factor = Rational (1)
95                 
96         def lisp_expression (self):
97                 return '(ly:make-duration %d %d %d %d)' % (self.duration_log,
98                                                            self.dots,
99                                                            self.factor.numerator (),
100                                                            self.factor.denominator ())
101
102
103         def ly_expression (self, factor = None):
104                 if not factor:
105                         factor = self.factor
106                         
107                 str = '%d%s' % (1 << self.duration_log, '.'*self.dots)
108
109                 if factor <> Rational (1,1):
110                         str += '*%d/%d' % (factor.numerator (), factor.denominator ())
111
112                 return str
113         
114         def print_ly (self, outputter):
115                 if isinstance (outputter, Output_printer):
116                         str = self.ly_expression (self.factor / outputter.duration_factor ())
117                         outputter.print_duration_string (str)
118                 else:
119                         outputter (self.ly_expression ())
120                 
121         def __repr__(self):
122                 return self.ly_expression()
123                 
124         def copy (self):
125                 d = Duration ()
126                 d.dots = self.dots
127                 d.duration_log = self.duration_log
128                 d.factor = self.factor
129                 return d
130
131         def get_length (self):
132                 dot_fact = Rational( (1 << (1 + self.dots))-1,
133                                      1 << self.dots)
134
135                 log = abs (self.duration_log)
136                 dur = 1 << log
137                 if self.duration_log < 0:
138                         base = Rational (dur)
139                 else:
140                         base = Rational (1, dur)
141
142                 return base * dot_fact * self.factor
143
144         
145 class Pitch:
146         def __init__ (self):
147                 self.alteration = 0
148                 self.step = 0
149                 self.octave = 0
150                 
151         def __repr__(self):
152                 return self.ly_expression()
153
154         def transposed (self, interval):
155                 c = self.copy ()
156                 c.alteration  += interval.alteration
157                 c.step += interval.step
158                 c.octave += interval.octave
159                 c.normalize ()
160                 
161                 target_st = self.semitones()  + interval.semitones()
162                 c.alteration += target_st - c.semitones()
163                 return c
164
165         def normalize (c):
166                 while c.step < 0:
167                         c.step += 7
168                         c.octave -= 1
169                 c.octave += c.step / 7
170                 c.step = c.step  % 7
171
172         
173         def lisp_expression (self):
174                 return '(ly:make-pitch %d %d %d)' % (self.octave,
175                                                      self.step,
176                                                      self.alteration)
177
178         def copy (self):
179                 p = Pitch ()
180                 p.alteration = self.alteration
181                 p.step = self.step
182                 p.octave = self.octave 
183                 return p
184
185         def steps (self):
186                 return self.step + self.octave *7
187
188         def semitones (self):
189                 return self.octave * 12 + [0,2,4,5,7,9,11][self.step] + self.alteration
190         
191         def ly_step_expression (self): 
192                 str = 'cdefgab'[self.step]
193                 if self.alteration > 0:
194                         str += 'is'* (self.alteration)
195                 elif self.alteration < 0:
196                         str += 'es'* (-self.alteration)
197
198                 return str.replace ('aes', 'as').replace ('ees', 'es')
199         
200         def ly_expression (self):
201                 str = self.ly_step_expression ()
202                 if self.octave >= 0:
203                         str += "'" * (self.octave + 1) 
204                 elif self.octave < -1:
205                         str += "," * (-self.octave - 1) 
206                         
207                 return str
208         def print_ly (self, outputter):
209                 outputter (self.ly_expression())
210         
211 class Music:
212         def __init__ (self):
213                 self.parent = None
214                 self.start = Rational (0)
215                 self.comment = ''
216
217         def get_length(self):
218                 return Rational (0)
219         
220         def get_properties (self):
221                 return ''
222         
223         def has_children (self):
224                 return False
225         
226         def get_index (self):
227                 if self.parent:
228                         return self.parent.elements.index (self)
229                 else:
230                         return None
231         def name (self):
232                 return self.__class__.__name__
233         
234         def lisp_expression (self):
235                 name = self.name()
236
237                 props = self.get_properties ()
238 #               props += 'start %f ' % self.start
239                 
240                 return "(make-music '%s %s)" % (name,  props)
241
242         def set_start (self, start):
243                 self.start = start
244
245         def find_first (self, predicate):
246                 if predicate (self):
247                         return self
248                 return None
249
250         def print_comment (self, printer, text = None):
251                 if not text:
252                         text = self.comment
253
254                 if not text:
255                         return
256
257                         
258                 if isinstance (printer, Output_printer):
259                         if text == '\n':
260                                 printer.newline ()
261                                 return
262
263                         lines = string.split (text, '\n')
264                         for l in lines:
265                                 if l:
266                                         printer.print_verbatim ('% ' + l)
267                                 printer.newline ()
268                 else:
269                         printer ('% ' + re.sub ('\n', '\n% ', text))
270                         printer ('\n')
271                         
272
273         def print_ly (self, printer):
274                 printer (self.ly_expression ())
275
276 class MusicWrapper (Music):
277         def __init__ (self):
278                 Music.__init__(self)
279                 self.element = None
280         def print_ly (self, func):
281                 self.element.print_ly (func)
282
283 class TimeScaledMusic (MusicWrapper):
284         def print_ly (self, func):
285                 if isinstance(func, Output_printer):
286                         func ('\\times %d/%d ' %
287                               (self.numerator, self.denominator))
288                         func.add_factor (Rational (self.numerator, self.denominator))
289                         MusicWrapper.print_ly (self, func)
290                         func.revert ()
291                 else:
292                         func (r'\times 1/1 ')
293                         MusicWrapper.print_ly (self, func)
294
295 class NestedMusic(Music):
296         def __init__ (self):
297                 Music.__init__ (self)
298                 self.elements = []
299
300         def append (self, what):
301                 if what:
302                         self.elements.append (what)
303                         
304         def has_children (self):
305                 return self.elements
306
307         def insert_around (self, succ, elt, dir):
308                 assert elt.parent == None
309                 assert succ == None or succ in self.elements
310
311                 
312                 idx = 0
313                 if succ:
314                         idx = self.elements.index (succ)
315                         if dir > 0:
316                                 idx += 1
317                 else:
318                         if dir < 0:
319                                 idx = 0
320                         elif dir > 0:
321                                 idx = len (self.elements)
322
323                 self.elements.insert (idx, elt)
324                 elt.parent = self
325                 
326         def get_properties (self):
327                 return ("'elements (list %s)"
328                         % string.join (map (lambda x: x.lisp_expression(),
329                                             self.elements)))
330
331         def get_subset_properties (self, predicate):
332                 return ("'elements (list %s)"
333                         % string.join (map (lambda x: x.lisp_expression(),
334                                             filter ( predicate,  self.elements))))
335         def get_neighbor (self, music, dir):
336                 assert music.parent == self
337                 idx = self.elements.index (music)
338                 idx += dir
339                 idx = min (idx, len (self.elements) -1)
340                 idx = max (idx, 0)
341
342                 return self.elements[idx]
343
344         def delete_element (self, element):
345                 assert element in self.elements
346                 
347                 self.elements.remove (element)
348                 element.parent = None
349                 
350         def set_start (self, start):
351                 self.start = start
352                 for e in self.elements:
353                         e.set_start (start)
354
355         def find_first (self, predicate):
356                 r = Music.find_first (self, predicate)
357                 if r:
358                         return r
359                 
360                 for e in self.elements:
361                         r = e.find_first (predicate)
362                         if r:
363                                 return r
364                 return None
365                 
366 class SequentialMusic (NestedMusic):
367         def print_ly (self, printer):
368                 printer ('{')
369                 if self.comment:
370                         self.print_comment (printer)
371                 elif isinstance (printer, Output_printer):
372                         printer.newline()
373                 else:
374                         printer ('\n')
375                         
376                 for e in self.elements:
377                         e.print_ly (printer)
378
379                 printer ('}')
380
381         def lisp_sub_expression (self, pred):
382                 name = self.name()
383
384
385                 props = self.get_subset_properties (pred)
386                 
387                 return "(make-music '%s %s)" % (name,  props)
388         
389         def set_start (self, start):
390                 for e in self.elements:
391                         e.set_start (start)
392                         start += e.get_length()
393                         
394 class EventChord(NestedMusic):
395         def get_length (self):
396                 l = Rational (0)
397                 for e in self.elements:
398                         l = max(l, e.get_length())
399                 return l
400         
401         def print_ly (self, printer):
402                 note_events = [e for e in self.elements if
403                                isinstance (e, NoteEvent)]
404
405                 rest_events = [e for e in self.elements if
406                                isinstance (e, RhythmicEvent)
407                                and not isinstance (e, NoteEvent)]
408                 
409                 other_events = [e for e in self.elements if
410                                 not isinstance (e, RhythmicEvent)]
411
412                 if rest_events:
413                         rest_events[0].print_ly (printer)
414                 elif len (note_events) == 1:
415                         note_events[0].print_ly (printer)
416                 elif note_events:
417                         pitches = [x.pitch.ly_expression () for x in note_events]
418                         printer ('<%s>' % string.join (pitches))
419                         note_events[0].duration.print_ly (printer)
420                 else:
421                         pass
422                 
423                 #       print  'huh', rest_events, note_events, other_events
424                 for e in other_events:
425                         e.print_ly (printer)
426
427                 self.print_comment (printer)
428                         
429 class Event(Music):
430         pass
431
432 class SpanEvent (Event):
433         def __init__(self):
434                 Event.__init__ (self)
435                 self.span_direction = 0
436         def get_properties(self):
437                 return "'span-direction  %d" % self.span_direction
438         
439 class SlurEvent (SpanEvent):
440         def ly_expression (self):
441                 return {-1: '(',
442                         0:'',
443                         1:')'}[self.span_direction]
444
445 class BeamEvent (SpanEvent):
446         def ly_expression (self):
447                 return {-1: '[',
448                         0:'',
449                         1:']'}[self.span_direction]
450
451 class ArpeggioEvent(Event):
452         def ly_expression (self):
453                 return ('\\arpeggio')
454
455
456 class TieEvent(Event):
457         def ly_expression (self):
458                 return '~'
459
460         
461 class RhythmicEvent(Event):
462         def __init__ (self):
463                 Event.__init__ (self)
464                 self.duration = Duration()
465                 
466         def get_length (self):
467                 return self.duration.get_length()
468                 
469         def get_properties (self):
470                 return ("'duration %s"
471                         % self.duration.lisp_expression ())
472         
473 class RestEvent (RhythmicEvent):
474         def ly_expression (self):
475                 return 'r%s' % self.duration.ly_expression ()
476         
477         def print_ly (self, printer):
478                 printer('r')
479                 self.duration.print_ly (printer)
480
481 class SkipEvent (RhythmicEvent):
482         def ly_expression (self):
483                 return 's%s' % self.duration.ly_expression () 
484
485 class NoteEvent(RhythmicEvent):
486         def  __init__ (self):
487                 RhythmicEvent.__init__ (self)
488                 self.pitch = Pitch()
489                 self.cautionary = False
490                 self.forced_accidental = False
491                 
492         def get_properties (self):
493                 return ("'pitch %s\n 'duration %s"
494                         % (self.pitch.lisp_expression (),
495                            self.duration.lisp_expression ()))
496
497         def pitch_mods (self):
498                 excl_question = ''
499                 if self.cautionary:
500                         excl_question += '?'
501                 if self.forced_accidental:
502                         excl_question += '!'
503
504                 return excl_question
505         
506         def ly_expression (self):
507                 return '%s%s%s' % (self.pitch.ly_expression (),
508                                    self.pitch_mods(),
509                                    self.duration.ly_expression ())
510
511         def print_ly (self, printer):
512                 self.pitch.print_ly (printer)
513                 printer (self.pitch_mods ())  
514                 self.duration.print_ly (printer)
515
516 class KeySignatureChange (Music):
517         def __init__ (self):
518                 Music.__init__ (self)
519                 self.scale = []
520                 self.tonic = Pitch()
521                 self.mode = 'major'
522                 
523         def ly_expression (self):
524                 return '\\key %s \\%s' % (self.tonic.ly_step_expression (),
525                                           self.mode)
526         
527         def lisp_expression (self):
528                 pairs = ['(%d . %d)' % (i , self.scale[i]) for i in range (0,7)]
529                 scale_str = ("'(%s)" % string.join (pairs))
530
531                 return """ (make-music 'KeyChangeEvent
532           'pitch-alist %s) """ % scale_str
533
534 class TimeSignatureChange (Music):
535         def __init__ (self):
536                 Music.__init__ (self)
537                 self.fraction = (4,4)
538         def ly_expression (self):
539                 return '\\time %d/%d ' % self.fraction
540         
541 class ClefChange (Music):
542         def __init__ (self):
543                 Music.__init__ (self)
544                 self.type = 'G'
545                 
546         
547         def ly_expression (self):
548                 return '\\clef "%s"' % self.type
549         clef_dict = {
550                 "G": ("clefs.G", -2, -6),
551                 "C": ("clefs.C", 0, 0),
552                 "F": ("clefs.F", 2, 6),
553                 }
554         
555         def lisp_expression (self):
556                 (glyph, pos, c0) = self.clef_dict [self.type]
557                 clefsetting = """
558                 (make-music 'SequentialMusic
559                 'elements (list
560       (context-spec-music
561        (make-property-set 'clefGlyph "%s") 'Staff)
562       (context-spec-music
563        (make-property-set 'clefPosition %d) 'Staff)
564       (context-spec-music
565        (make-property-set 'middleCPosition %d) 'Staff)))
566 """ % (glyph, pos, c0)
567                 return clefsetting
568
569
570 def test_pitch ():
571         bflat = Pitch()
572         bflat.alteration = -1
573         bflat.step =  6
574         bflat.octave = -1
575         fifth = Pitch()
576         fifth.step = 4
577         down = Pitch ()
578         down.step = -4
579         down.normalize ()
580         
581         
582         print bflat.semitones()
583         print bflat.transposed (fifth),  bflat.transposed (fifth).transposed (fifth)
584         print bflat.transposed (fifth).transposed (fifth).transposed (fifth)
585
586         print bflat.semitones(), 'down'
587         print bflat.transposed (down)
588         print bflat.transposed (down).transposed (down)
589         print bflat.transposed (down).transposed (down).transposed (down)
590
591 def test_expr ():
592         m = SequentialMusic()
593         l = 2  
594         evc = EventChord()
595         n = NoteEvent()
596         n.duration.duration_log = l
597         n.pitch.step = 1
598         evc.insert_around (None, n, 0)
599         m.insert_around (None, evc, 0)
600
601         evc = EventChord()
602         n = NoteEvent()
603         n.duration.duration_log = l
604         n.pitch.step = 3
605         evc.insert_around (None, n, 0)
606         m.insert_around (None, evc, 0)
607
608         evc = EventChord()
609         n = NoteEvent()
610         n.duration.duration_log = l
611         n.pitch.step = 2 
612         evc.insert_around (None, n, 0)
613         m.insert_around (None, evc, 0)
614
615         evc = ClefChange("G")
616         m.insert_around (None, evc, 0)
617
618         evc = EventChord()
619         tonic = Pitch ()
620         tonic.step = 2
621         tonic.alteration = -2
622         n = KeySignatureEvent(tonic, [0, 0, -2, 0, 0,-2,-2]  )
623         evc.insert_around (None, n, 0)
624         m.insert_around (None, evc, 0)
625
626         return m
627
628
629 if __name__ == '__main__':
630         test_pitch()
631         raise 1
632         expr = test_expr()
633         expr.set_start (Rational (0))
634         print expr.ly_expression()
635         start = Rational (0,4)
636         stop = Rational (4,2)
637         def sub(x, start=start, stop=stop):
638                 ok = x.start >= start and x.start +x.get_length() <= stop
639                 return ok
640         
641         print expr.lisp_sub_expression(sub)
642