* lexer-gcc-3.1.sh: Remove.
[lilypond/patrick.git] / python / musicexp.py
blobe7b3870425cabd4344322035742174c9b533c6d5
1 import inspect
2 import sys
3 import string
4 import re
6 from rational import Rational
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
16 class Output_printer:
18 """A class that takes care of formatting (eg.: indenting) a
19 Music expression as a .ly file.
21 """
22 ## TODO: support for \relative.
24 def __init__ (self):
25 self._line = ''
26 self._indent = 4
27 self._nesting = 0
28 self._file = sys.stdout
29 self._line_len = 72
30 self._output_state_stack = [Output_stack_element()]
31 self._skipspace = False
32 self._last_duration = None
34 def set_file (self, file):
35 self._file = file
37 def dump_version (self):
38 self.newline ()
39 self.print_verbatim ('\\version "@TOPLEVEL_VERSION@"')
40 self.newline ()
42 def get_indent (self):
43 return self._nesting * self._indent
45 def override (self):
46 last = self._output_state_stack[-1]
47 self._output_state_stack.append (last.copy())
49 def add_factor (self, factor):
50 self.override()
51 self._output_state_stack[-1].factor *= factor
53 def revert (self):
54 del self._output_state_stack[-1]
55 if not self._output_state_stack:
56 raise 'empty'
58 def duration_factor (self):
59 return self._output_state_stack[-1].factor
61 def print_verbatim (self, str):
62 self._line += str
64 def unformatted_output (self, str):
65 self._nesting += str.count ('<') + str.count ('{')
66 self._nesting -= str.count ('>') + str.count ('}')
67 self.print_verbatim (str)
69 def print_duration_string (self, str):
70 if self._last_duration == str:
71 return
73 self.unformatted_output (str)
75 def add_word (self, str):
76 if (len (str) + 1 + len (self._line) > self._line_len):
77 self.newline()
78 self._skipspace = True
80 if not self._skipspace:
81 self._line += ' '
82 self.unformatted_output (str)
83 self._skipspace = False
85 def newline (self):
86 self._file.write (self._line + '\n')
87 self._line = ' ' * self._indent * self._nesting
88 self._skipspace = True
90 def skipspace (self):
91 self._skipspace = True
93 def __call__(self, arg):
94 self.dump (arg)
96 def dump (self, str):
97 if self._skipspace:
98 self._skipspace = False
99 self.unformatted_output (str)
100 else:
101 words = string.split (str)
102 for w in words:
103 self.add_word (w)
106 def close (self):
107 self.newline ()
108 self._file.close ()
109 self._file = None
112 class Duration:
113 def __init__ (self):
114 self.duration_log = 0
115 self.dots = 0
116 self.factor = Rational (1)
118 def lisp_expression (self):
119 return '(ly:make-duration %d %d %d %d)' % (self.duration_log,
120 self.dots,
121 self.factor.numerator (),
122 self.factor.denominator ())
125 def ly_expression (self, factor = None):
126 if not factor:
127 factor = self.factor
129 str = '%d%s' % (1 << self.duration_log, '.'*self.dots)
131 if factor <> Rational (1,1):
132 str += '*%d/%d' % (factor.numerator (), factor.denominator ())
134 return str
136 def print_ly (self, outputter):
137 str = self.ly_expression (self.factor / outputter.duration_factor ())
138 outputter.print_duration_string (str)
140 def __repr__(self):
141 return self.ly_expression()
143 def copy (self):
144 d = Duration ()
145 d.dots = self.dots
146 d.duration_log = self.duration_log
147 d.factor = self.factor
148 return d
150 def get_length (self):
151 dot_fact = Rational( (1 << (1 + self.dots))-1,
152 1 << self.dots)
154 log = abs (self.duration_log)
155 dur = 1 << log
156 if self.duration_log < 0:
157 base = Rational (dur)
158 else:
159 base = Rational (1, dur)
161 return base * dot_fact * self.factor
164 class Pitch:
165 def __init__ (self):
166 self.alteration = 0
167 self.step = 0
168 self.octave = 0
170 def __repr__(self):
171 return self.ly_expression()
173 def transposed (self, interval):
174 c = self.copy ()
175 c.alteration += interval.alteration
176 c.step += interval.step
177 c.octave += interval.octave
178 c.normalize ()
180 target_st = self.semitones() + interval.semitones()
181 c.alteration += target_st - c.semitones()
182 return c
184 def normalize (c):
185 while c.step < 0:
186 c.step += 7
187 c.octave -= 1
188 c.octave += c.step / 7
189 c.step = c.step % 7
191 def lisp_expression (self):
192 return '(ly:make-pitch %d %d %d)' % (self.octave,
193 self.step,
194 self.alteration)
196 def copy (self):
197 p = Pitch ()
198 p.alteration = self.alteration
199 p.step = self.step
200 p.octave = self.octave
201 return p
203 def steps (self):
204 return self.step + self.octave *7
206 def semitones (self):
207 return self.octave * 12 + [0,2,4,5,7,9,11][self.step] + self.alteration
209 def ly_step_expression (self):
210 str = 'cdefgab'[self.step]
211 if self.alteration > 0:
212 str += 'is'* (self.alteration)
213 elif self.alteration < 0:
214 str += 'es'* (-self.alteration)
216 return str.replace ('aes', 'as').replace ('ees', 'es')
218 def ly_expression (self):
219 str = self.ly_step_expression ()
220 if self.octave >= 0:
221 str += "'" * (self.octave + 1)
222 elif self.octave < -1:
223 str += "," * (-self.octave - 1)
225 return str
227 def print_ly (self, outputter):
228 outputter (self.ly_expression())
230 class Music:
231 def __init__ (self):
232 self.parent = None
233 self.start = Rational (0)
234 self.comment = ''
235 self.identifier = None
237 def get_length(self):
238 return Rational (0)
240 def get_properties (self):
241 return ''
243 def has_children (self):
244 return False
246 def get_index (self):
247 if self.parent:
248 return self.parent.elements.index (self)
249 else:
250 return None
251 def name (self):
252 return self.__class__.__name__
254 def lisp_expression (self):
255 name = self.name()
257 props = self.get_properties ()
259 return "(make-music '%s %s)" % (name, props)
261 def set_start (self, start):
262 self.start = start
264 def find_first (self, predicate):
265 if predicate (self):
266 return self
267 return None
269 def print_comment (self, printer, text = None):
270 if not text:
271 text = self.comment
273 if not text:
274 return
276 if text == '\n':
277 printer.newline ()
278 return
280 lines = string.split (text, '\n')
281 for l in lines:
282 if l:
283 printer.unformatted_output ('% ' + l)
284 printer.newline ()
287 def print_with_identifier (self, printer):
288 if self.identifier:
289 printer ("\\%s" % self.identifier)
290 else:
291 self.print_ly (printer)
293 def print_ly (self, printer):
294 printer (self.ly_expression ())
296 class MusicWrapper (Music):
297 def __init__ (self):
298 Music.__init__(self)
299 self.element = None
300 def print_ly (self, func):
301 self.element.print_ly (func)
303 class ModeChangingMusicWrapper (MusicWrapper):
304 def __init__ (self):
305 MusicWrapper.__init__ (self)
306 self.mode = 'notemode'
308 def print_ly (self, func):
309 func ('\\%s' % self.mode)
310 MusicWrapper.print_ly (self, func)
312 class TimeScaledMusic (MusicWrapper):
313 def print_ly (self, func):
314 func ('\\times %d/%d ' %
315 (self.numerator, self.denominator))
316 func.add_factor (Rational (self.numerator, self.denominator))
317 MusicWrapper.print_ly (self, func)
318 func.revert ()
320 class NestedMusic(Music):
321 def __init__ (self):
322 Music.__init__ (self)
323 self.elements = []
325 def append (self, what):
326 if what:
327 self.elements.append (what)
329 def has_children (self):
330 return self.elements
332 def insert_around (self, succ, elt, dir):
333 assert elt.parent == None
334 assert succ == None or succ in self.elements
337 idx = 0
338 if succ:
339 idx = self.elements.index (succ)
340 if dir > 0:
341 idx += 1
342 else:
343 if dir < 0:
344 idx = 0
345 elif dir > 0:
346 idx = len (self.elements)
348 self.elements.insert (idx, elt)
349 elt.parent = self
351 def get_properties (self):
352 return ("'elements (list %s)"
353 % string.join (map (lambda x: x.lisp_expression(),
354 self.elements)))
356 def get_subset_properties (self, predicate):
357 return ("'elements (list %s)"
358 % string.join (map (lambda x: x.lisp_expression(),
359 filter ( predicate, self.elements))))
360 def get_neighbor (self, music, dir):
361 assert music.parent == self
362 idx = self.elements.index (music)
363 idx += dir
364 idx = min (idx, len (self.elements) -1)
365 idx = max (idx, 0)
367 return self.elements[idx]
369 def delete_element (self, element):
370 assert element in self.elements
372 self.elements.remove (element)
373 element.parent = None
375 def set_start (self, start):
376 self.start = start
377 for e in self.elements:
378 e.set_start (start)
380 def find_first (self, predicate):
381 r = Music.find_first (self, predicate)
382 if r:
383 return r
385 for e in self.elements:
386 r = e.find_first (predicate)
387 if r:
388 return r
389 return None
391 class SequentialMusic (NestedMusic):
392 def print_ly (self, printer):
393 printer ('{')
394 if self.comment:
395 self.print_comment (printer)
397 printer.newline()
398 for e in self.elements:
399 e.print_ly (printer)
401 printer ('}')
402 printer.newline()
404 def lisp_sub_expression (self, pred):
405 name = self.name()
408 props = self.get_subset_properties (pred)
410 return "(make-music '%s %s)" % (name, props)
412 def set_start (self, start):
413 for e in self.elements:
414 e.set_start (start)
415 start += e.get_length()
417 class EventChord(NestedMusic):
418 def get_length (self):
419 l = Rational (0)
420 for e in self.elements:
421 l = max(l, e.get_length())
422 return l
424 def print_ly (self, printer):
425 note_events = [e for e in self.elements if
426 isinstance (e, NoteEvent)]
428 rest_events = [e for e in self.elements if
429 isinstance (e, RhythmicEvent)
430 and not isinstance (e, NoteEvent)]
432 other_events = [e for e in self.elements if
433 not isinstance (e, RhythmicEvent)]
435 if rest_events:
436 rest_events[0].print_ly (printer)
437 elif len (note_events) == 1:
438 note_events[0].print_ly (printer)
439 elif note_events:
440 pitches = [x.pitch.ly_expression () for x in note_events]
441 printer ('<%s>' % string.join (pitches))
442 note_events[0].duration.print_ly (printer)
443 else:
444 pass
446 for e in other_events:
447 e.print_ly (printer)
449 self.print_comment (printer)
452 class BarCheck (Music):
453 def __init__ (self):
454 Music.__init__ (self)
455 self.bar_number = 0
457 def print_ly (self, printer):
458 if self.bar_number > 0 and (self.bar_number % 10) == 0:
459 printer.dump ("| \\barNumberCheck #%d " % self.bar_number)
460 printer.newline ()
461 else:
462 printer.dump ("| ")
463 printer.print_verbatim (' %% %d' % self.bar_number)
464 printer.newline ()
467 def ly_expression (self):
468 return " | "
470 class Event(Music):
471 pass
473 class SpanEvent (Event):
474 def __init__(self):
475 Event.__init__ (self)
476 self.span_direction = 0
477 def get_properties(self):
478 return "'span-direction %d" % self.span_direction
480 class SlurEvent (SpanEvent):
481 def ly_expression (self):
482 return {-1: '(',
483 0:'',
484 1:')'}[self.span_direction]
486 class BeamEvent (SpanEvent):
487 def ly_expression (self):
488 return {-1: '[',
489 0:'',
490 1:']'}[self.span_direction]
492 class ArpeggioEvent(Event):
493 def ly_expression (self):
494 return ('\\arpeggio')
497 class TieEvent(Event):
498 def ly_expression (self):
499 return '~'
502 class RhythmicEvent(Event):
503 def __init__ (self):
504 Event.__init__ (self)
505 self.duration = Duration()
507 def get_length (self):
508 return self.duration.get_length()
510 def get_properties (self):
511 return ("'duration %s"
512 % self.duration.lisp_expression ())
514 class RestEvent (RhythmicEvent):
515 def ly_expression (self):
516 return 'r%s' % self.duration.ly_expression ()
518 def print_ly (self, printer):
519 printer('r')
520 self.duration.print_ly (printer)
522 class SkipEvent (RhythmicEvent):
523 def ly_expression (self):
524 return 's%s' % self.duration.ly_expression ()
526 class NoteEvent(RhythmicEvent):
527 def __init__ (self):
528 RhythmicEvent.__init__ (self)
529 self.pitch = None
530 self.drum_type = None
531 self.cautionary = False
532 self.forced_accidental = False
534 def get_properties (self):
535 str = RhythmicEvent.get_properties ()
537 if self.pitch:
538 str += self.pitch.lisp_expression ()
539 elif self.drum_type:
540 str += "'drum-type '%s" % self.drum_type
542 return str
544 def pitch_mods (self):
545 excl_question = ''
546 if self.cautionary:
547 excl_question += '?'
548 if self.forced_accidental:
549 excl_question += '!'
551 return excl_question
553 def ly_expression (self):
554 if self.pitch:
555 return '%s%s%s' % (self.pitch.ly_expression (),
556 self.pitch_mods(),
557 self.duration.ly_expression ())
558 elif self.drum_type:
559 return '%s%s' (self.drum_type,
560 self.duration.ly_expression ())
562 def print_ly (self, printer):
563 if self.pitch:
564 self.pitch.print_ly (printer)
565 printer (self.pitch_mods ())
566 else:
567 printer (self.drum_type)
569 self.duration.print_ly (printer)
571 class KeySignatureChange (Music):
572 def __init__ (self):
573 Music.__init__ (self)
574 self.scale = []
575 self.tonic = Pitch()
576 self.mode = 'major'
578 def ly_expression (self):
579 return '\\key %s \\%s' % (self.tonic.ly_step_expression (),
580 self.mode)
582 def lisp_expression (self):
583 pairs = ['(%d . %d)' % (i , self.scale[i]) for i in range (0,7)]
584 scale_str = ("'(%s)" % string.join (pairs))
586 return """ (make-music 'KeyChangeEvent
587 'pitch-alist %s) """ % scale_str
589 class TimeSignatureChange (Music):
590 def __init__ (self):
591 Music.__init__ (self)
592 self.fraction = (4,4)
593 def ly_expression (self):
594 return '\\time %d/%d ' % self.fraction
596 class ClefChange (Music):
597 def __init__ (self):
598 Music.__init__ (self)
599 self.type = 'G'
602 def ly_expression (self):
603 return '\\clef "%s"' % self.type
604 clef_dict = {
605 "G": ("clefs.G", -2, -6),
606 "C": ("clefs.C", 0, 0),
607 "F": ("clefs.F", 2, 6),
610 def lisp_expression (self):
611 (glyph, pos, c0) = self.clef_dict [self.type]
612 clefsetting = """
613 (make-music 'SequentialMusic
614 'elements (list
615 (context-spec-music
616 (make-property-set 'clefGlyph "%s") 'Staff)
617 (context-spec-music
618 (make-property-set 'clefPosition %d) 'Staff)
619 (context-spec-music
620 (make-property-set 'middleCPosition %d) 'Staff)))
621 """ % (glyph, pos, c0)
622 return clefsetting
625 class MultiMeasureRest(Music):
627 def lisp_expression (self):
628 return """
629 (make-music
630 'MultiMeasureRestMusicGroup
631 'elements
632 (list (make-music (quote BarCheck))
633 (make-music
634 'EventChord
635 'elements
636 (list (make-music
637 'MultiMeasureRestEvent
638 'duration
639 %s)))
640 (make-music (quote BarCheck))))
641 """ % self.duration.lisp_expression ()
643 def ly_expression (self):
644 return 'R%s' % self.duration.ly_expression ()
647 def test_pitch ():
648 bflat = Pitch()
649 bflat.alteration = -1
650 bflat.step = 6
651 bflat.octave = -1
652 fifth = Pitch()
653 fifth.step = 4
654 down = Pitch ()
655 down.step = -4
656 down.normalize ()
659 print bflat.semitones()
660 print bflat.transposed (fifth), bflat.transposed (fifth).transposed (fifth)
661 print bflat.transposed (fifth).transposed (fifth).transposed (fifth)
663 print bflat.semitones(), 'down'
664 print bflat.transposed (down)
665 print bflat.transposed (down).transposed (down)
666 print bflat.transposed (down).transposed (down).transposed (down)
670 def test_printer ():
671 def make_note ():
672 evc = EventChord()
673 n = NoteEvent()
674 evc.append (n)
675 return n
677 def make_tup ():
678 m = SequentialMusic()
679 m.append (make_note ())
680 m.append (make_note ())
681 m.append (make_note ())
684 t = TimeScaledMusic ()
685 t.numerator = 2
686 t.denominator = 3
687 t.element = m
688 return t
690 m = SequentialMusic ()
691 m.append (make_tup ())
692 m.append (make_tup ())
693 m.append (make_tup ())
695 printer = Output_printer()
696 m.print_ly (printer)
697 printer.newline ()
699 def test_expr ():
700 m = SequentialMusic()
701 l = 2
702 evc = EventChord()
703 n = NoteEvent()
704 n.duration.duration_log = l
705 n.pitch.step = 1
706 evc.insert_around (None, n, 0)
707 m.insert_around (None, evc, 0)
709 evc = EventChord()
710 n = NoteEvent()
711 n.duration.duration_log = l
712 n.pitch.step = 3
713 evc.insert_around (None, n, 0)
714 m.insert_around (None, evc, 0)
716 evc = EventChord()
717 n = NoteEvent()
718 n.duration.duration_log = l
719 n.pitch.step = 2
720 evc.insert_around (None, n, 0)
721 m.insert_around (None, evc, 0)
723 evc = ClefChange()
724 evc.type = 'treble'
725 m.insert_around (None, evc, 0)
727 evc = EventChord()
728 tonic = Pitch ()
729 tonic.step = 2
730 tonic.alteration = -2
731 n = KeySignatureChange()
732 n.tonic=tonic.copy()
733 n.scale = [0, 0, -2, 0, 0,-2,-2]
735 evc.insert_around (None, n, 0)
736 m.insert_around (None, evc, 0)
738 return m
741 if __name__ == '__main__':
742 test_printer ()
743 raise 'bla'
744 test_pitch()
746 expr = test_expr()
747 expr.set_start (Rational (0))
748 print expr.ly_expression()
749 start = Rational (0,4)
750 stop = Rational (4,2)
751 def sub(x, start=start, stop=stop):
752 ok = x.start >= start and x.start +x.get_length() <= stop
753 return ok
755 print expr.lisp_sub_expression(sub)