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