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