]> git.donarmstrong.com Git - lilypond.git/blob - python/musicexp.py
Merge branch 'master' into lilypond/translation
[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         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)
104
105
106     def close (self):
107         self.newline ()
108         self._file.close ()
109         self._file = None
110         
111         
112 class Duration:
113     def __init__ (self):
114         self.duration_log = 0
115         self.dots = 0
116         self.factor = Rational (1)
117         
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 ())
123
124
125     def ly_expression (self, factor = None):
126         if not factor:
127             factor = self.factor
128             
129         str = '%d%s' % (1 << self.duration_log, '.'*self.dots)
130
131         if factor <> Rational (1,1):
132             str += '*%d/%d' % (factor.numerator (), factor.denominator ())
133
134         return str
135     
136     def print_ly (self, outputter):
137         str = self.ly_expression (self.factor / outputter.duration_factor ())
138         outputter.print_duration_string (str)
139         
140     def __repr__(self):
141         return self.ly_expression()
142         
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
149
150     def get_length (self):
151         dot_fact = Rational( (1 << (1 + self.dots))-1,
152                              1 << self.dots)
153
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)
160
161         return base * dot_fact * self.factor
162
163     
164 class Pitch:
165     def __init__ (self):
166         self.alteration = 0
167         self.step = 0
168         self.octave = 0
169         
170     def __repr__(self):
171         return self.ly_expression()
172
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 ()
179         
180         target_st = self.semitones()  + interval.semitones()
181         c.alteration += target_st - c.semitones()
182         return c
183
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
190
191     def lisp_expression (self):
192         return '(ly:make-pitch %d %d %d)' % (self.octave,
193                                              self.step,
194                                              self.alteration)
195
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
202
203     def steps (self):
204         return self.step + self.octave *7
205
206     def semitones (self):
207         return self.octave * 12 + [0,2,4,5,7,9,11][self.step] + self.alteration
208     
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)
215
216         return str.replace ('aes', 'as').replace ('ees', 'es')
217     
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) 
224             
225         return str
226     
227     def print_ly (self, outputter):
228         outputter (self.ly_expression())
229     
230 class Music:
231     def __init__ (self):
232         self.parent = None
233         self.start = Rational (0)
234         self.comment = ''
235         self.identifier = None
236         
237     def get_length(self):
238         return Rational (0)
239     
240     def get_properties (self):
241         return ''
242     
243     def has_children (self):
244         return False
245     
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__
253     
254     def lisp_expression (self):
255         name = self.name()
256
257         props = self.get_properties ()
258         
259         return "(make-music '%s %s)" % (name,  props)
260
261     def set_start (self, start):
262         self.start = start
263
264     def find_first (self, predicate):
265         if predicate (self):
266             return self
267         return None
268
269     def print_comment (self, printer, text = None):
270         if not text:
271             text = self.comment
272
273         if not text:
274             return
275             
276         if text == '\n':
277             printer.newline ()
278             return
279         
280         lines = string.split (text, '\n')
281         for l in lines:
282             if l:
283                 printer.unformatted_output ('% ' + l)
284             printer.newline ()
285             
286
287     def print_with_identifier (self, printer):
288         if self.identifier: 
289             printer ("\\%s" % self.identifier)
290         else:
291             self.print_ly (printer)
292
293     def print_ly (self, printer):
294         printer (self.ly_expression ())
295
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)
302
303 class ModeChangingMusicWrapper (MusicWrapper):
304     def __init__ (self):
305         MusicWrapper.__init__ (self)
306         self.mode = 'notemode'
307
308     def print_ly (self, func):
309         func ('\\%s' % self.mode)
310         MusicWrapper.print_ly (self, func)
311
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 ()
319
320 class NestedMusic(Music):
321     def __init__ (self):
322         Music.__init__ (self)
323         self.elements = []
324
325     def append (self, what):
326         if what:
327             self.elements.append (what)
328             
329     def has_children (self):
330         return self.elements
331
332     def insert_around (self, succ, elt, dir):
333         assert elt.parent == None
334         assert succ == None or succ in self.elements
335
336         
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)
347
348         self.elements.insert (idx, elt)
349         elt.parent = self
350         
351     def get_properties (self):
352         return ("'elements (list %s)"
353             % string.join (map (lambda x: x.lisp_expression(),
354                       self.elements)))
355
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)
366
367         return self.elements[idx]
368
369     def delete_element (self, element):
370         assert element in self.elements
371         
372         self.elements.remove (element)
373         element.parent = None
374         
375     def set_start (self, start):
376         self.start = start
377         for e in self.elements:
378             e.set_start (start)
379
380     def find_first (self, predicate):
381         r = Music.find_first (self, predicate)
382         if r:
383             return r
384         
385         for e in self.elements:
386             r = e.find_first (predicate)
387             if r:
388                 return r
389         return None
390         
391 class SequentialMusic (NestedMusic):
392     def print_ly (self, printer):
393         printer ('{')
394         if self.comment:
395             self.print_comment (printer)
396
397         printer.newline()
398         for e in self.elements:
399             e.print_ly (printer)
400
401         printer ('}')
402         printer.newline()
403             
404     def lisp_sub_expression (self, pred):
405         name = self.name()
406
407
408         props = self.get_subset_properties (pred)
409         
410         return "(make-music '%s %s)" % (name,  props)
411     
412     def set_start (self, start):
413         for e in self.elements:
414             e.set_start (start)
415             start += e.get_length()
416             
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
423     
424     def print_ly (self, printer):
425         note_events = [e for e in self.elements if
426                isinstance (e, NoteEvent)]
427
428         rest_events = [e for e in self.elements if
429                isinstance (e, RhythmicEvent)
430                and not isinstance (e, NoteEvent)]
431         
432         other_events = [e for e in self.elements if
433                 not isinstance (e, RhythmicEvent)]
434
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
445         
446         for e in other_events:
447             e.print_ly (printer)
448
449         self.print_comment (printer)
450             
451
452 class BarCheck (Music):
453     def __init__ (self):
454         Music.__init__ (self)
455         self.bar_number = 0
456         
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 ()
465  
466
467     def ly_expression (self):
468         return " | "
469
470 class Event(Music):
471     pass
472
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
479     
480 class SlurEvent (SpanEvent):
481     def ly_expression (self):
482         return {-1: '(',
483             0:'',
484             1:')'}[self.span_direction]
485
486 class BeamEvent (SpanEvent):
487     def ly_expression (self):
488         return {-1: '[',
489             0:'',
490             1:']'}[self.span_direction]
491
492 class ArpeggioEvent(Event):
493     def ly_expression (self):
494         return ('\\arpeggio')
495
496
497 class TieEvent(Event):
498     def ly_expression (self):
499         return '~'
500
501
502 class HairpinEvent (Event):
503     def __init__ (self, type):
504         self.type = type
505     def hairpin_to_ly (self):
506         val = ''
507         tp = { 0: '\!', 1: '\<', -1: '\>' }.get (self.type)
508         if tp:
509             val += tp
510         return val
511     
512     def ly_expression (self):
513         return self.hairpin_to_ly ()
514     
515     def print_ly (self, printer):
516         val = self.hairpin_to_ly ()
517         if val:
518             printer.dump (val)
519
520
521
522 class DynamicsEvent (Event):
523     def __init__ (self):
524         self.type = None
525         self.available_commands = [ "ppppp", "pppp", "ppp", "pp", "p", 
526                                     "mp", "mf", 
527                                     "f", "ff", "fff", "ffff", 
528                                     "fp", "sf", "sff", "sp", "spp", "sfz", "rfz" ];
529     def ly_expression (self):
530         if self.type == None:
531             return;
532         elif self.type in self.available_commands:
533             return '\%s' % self.type
534         else:
535             return '\markup{ \dynamic %s }' % self.type
536         
537     def print_ly (self, printer):
538         if self.type == None:
539             return
540         elif self.type in self.available_commands:
541             printer.dump ("\\%s" % self.type)
542         else:
543             printer.dump ("\\markup{ \\dynamic %s }" % self.type)
544
545
546 class ArticulationEvent (Event):
547     def __init__ (self):
548         self.type = None
549         self.force_direction = None
550
551     def direction_mod (self):
552         dirstr = { 1: '^', -1: '_', 0: '-' }.get (self.force_direction)
553         if dirstr:
554             return dirstr
555         else:
556             return ''
557
558     def ly_expression (self):
559         return '%s\\%s' % (self.direction_mod (), self.type)
560
561
562 class TremoloEvent (Event):
563     def __init__ (self):
564         self.bars = 0;
565
566     def ly_expression (self):
567         str=''
568         if self.bars > 0:
569             str += ':%s' % (2 ** (2 + string.atoi (self.bars)))
570         return str
571
572
573 class RhythmicEvent(Event):
574     def __init__ (self):
575         Event.__init__ (self)
576         self.duration = Duration()
577         
578     def get_length (self):
579         return self.duration.get_length()
580         
581     def get_properties (self):
582         return ("'duration %s"
583                 % self.duration.lisp_expression ())
584     
585 class RestEvent (RhythmicEvent):
586     def ly_expression (self):
587         return 'r%s' % self.duration.ly_expression ()
588     
589     def print_ly (self, printer):
590         printer('r')
591         self.duration.print_ly (printer)
592
593 class SkipEvent (RhythmicEvent):
594     def ly_expression (self):
595         return 's%s' % self.duration.ly_expression () 
596
597 class NoteEvent(RhythmicEvent):
598     def  __init__ (self):
599         RhythmicEvent.__init__ (self)
600         self.pitch = None
601         self.drum_type = None
602         self.cautionary = False
603         self.forced_accidental = False
604         
605     def get_properties (self):
606         str = RhythmicEvent.get_properties ()
607         
608         if self.pitch:
609             str += self.pitch.lisp_expression ()
610         elif self.drum_type:
611             str += "'drum-type '%s" % self.drum_type
612
613         return str
614     
615     def pitch_mods (self):
616         excl_question = ''
617         if self.cautionary:
618             excl_question += '?'
619         if self.forced_accidental:
620             excl_question += '!'
621
622         return excl_question
623     
624     def ly_expression (self):
625         if self.pitch:
626             return '%s%s%s' % (self.pitch.ly_expression (),
627                                self.pitch_mods(),
628                                self.duration.ly_expression ())
629         elif self.drum_type:
630             return '%s%s' (self.drum_type,
631                            self.duration.ly_expression ())
632
633     def print_ly (self, printer):
634         if self.pitch:
635             self.pitch.print_ly (printer)
636             printer (self.pitch_mods ())
637         else:
638             printer (self.drum_type)
639
640         self.duration.print_ly (printer)
641
642 class KeySignatureChange (Music):
643     def __init__ (self):
644         Music.__init__ (self)
645         self.scale = []
646         self.tonic = Pitch()
647         self.mode = 'major'
648         
649     def ly_expression (self):
650         return '\\key %s \\%s' % (self.tonic.ly_step_expression (),
651                      self.mode)
652     
653     def lisp_expression (self):
654         pairs = ['(%d . %d)' % (i , self.scale[i]) for i in range (0,7)]
655         scale_str = ("'(%s)" % string.join (pairs))
656
657         return """ (make-music 'KeyChangeEvent
658      'pitch-alist %s) """ % scale_str
659
660 class TimeSignatureChange (Music):
661     def __init__ (self):
662         Music.__init__ (self)
663         self.fraction = (4,4)
664     def ly_expression (self):
665         return '\\time %d/%d ' % self.fraction
666     
667 class ClefChange (Music):
668     def __init__ (self):
669         Music.__init__ (self)
670         self.type = 'G'
671         
672     
673     def ly_expression (self):
674         return '\\clef "%s"' % self.type
675     clef_dict = {
676         "G": ("clefs.G", -2, -6),
677         "C": ("clefs.C", 0, 0),
678         "F": ("clefs.F", 2, 6),
679         }
680     
681     def lisp_expression (self):
682         (glyph, pos, c0) = self.clef_dict [self.type]
683         clefsetting = """
684         (make-music 'SequentialMusic
685         'elements (list
686    (context-spec-music
687    (make-property-set 'clefGlyph "%s") 'Staff)
688    (context-spec-music
689    (make-property-set 'clefPosition %d) 'Staff)
690    (context-spec-music
691    (make-property-set 'middleCPosition %d) 'Staff)))
692 """ % (glyph, pos, c0)
693         return clefsetting
694
695
696 class MultiMeasureRest(Music):
697
698     def lisp_expression (self):
699         return """
700 (make-music
701   'MultiMeasureRestMusicGroup
702   'elements
703   (list (make-music (quote BarCheck))
704         (make-music
705           'EventChord
706           'elements
707           (list (make-music
708                   'MultiMeasureRestEvent
709                   'duration
710                   %s)))
711         (make-music (quote BarCheck))))
712 """ % self.duration.lisp_expression ()
713
714     def ly_expression (self):
715         return 'R%s' % self.duration.ly_expression ()
716
717
718 def test_pitch ():
719     bflat = Pitch()
720     bflat.alteration = -1
721     bflat.step =  6
722     bflat.octave = -1
723     fifth = Pitch()
724     fifth.step = 4
725     down = Pitch ()
726     down.step = -4
727     down.normalize ()
728     
729     
730     print bflat.semitones()
731     print bflat.transposed (fifth),  bflat.transposed (fifth).transposed (fifth)
732     print bflat.transposed (fifth).transposed (fifth).transposed (fifth)
733
734     print bflat.semitones(), 'down'
735     print bflat.transposed (down)
736     print bflat.transposed (down).transposed (down)
737     print bflat.transposed (down).transposed (down).transposed (down)
738
739
740
741 def test_printer ():
742     def make_note ():
743         evc = EventChord()
744         n = NoteEvent()
745         evc.append (n)
746         return n
747
748     def make_tup ():
749         m = SequentialMusic()
750         m.append (make_note ())
751         m.append (make_note ())
752         m.append (make_note ())
753
754         
755         t = TimeScaledMusic ()
756         t.numerator = 2
757         t.denominator = 3
758         t.element = m
759         return t
760
761     m = SequentialMusic ()
762     m.append (make_tup ())
763     m.append (make_tup ())
764     m.append (make_tup ())
765     
766     printer = Output_printer()
767     m.print_ly (printer)
768     printer.newline ()
769     
770 def test_expr ():
771     m = SequentialMusic()
772     l = 2  
773     evc = EventChord()
774     n = NoteEvent()
775     n.duration.duration_log = l
776     n.pitch.step = 1
777     evc.insert_around (None, n, 0)
778     m.insert_around (None, evc, 0)
779
780     evc = EventChord()
781     n = NoteEvent()
782     n.duration.duration_log = l
783     n.pitch.step = 3
784     evc.insert_around (None, n, 0)
785     m.insert_around (None, evc, 0)
786
787     evc = EventChord()
788     n = NoteEvent()
789     n.duration.duration_log = l
790     n.pitch.step = 2 
791     evc.insert_around (None, n, 0)
792     m.insert_around (None, evc, 0)
793
794     evc = ClefChange()
795     evc.type = 'treble'
796     m.insert_around (None, evc, 0)
797
798     evc = EventChord()
799     tonic = Pitch ()
800     tonic.step = 2
801     tonic.alteration = -2
802     n = KeySignatureChange()
803     n.tonic=tonic.copy()
804     n.scale = [0, 0, -2, 0, 0,-2,-2]
805     
806     evc.insert_around (None, n, 0)
807     m.insert_around (None, evc, 0)
808
809     return m
810
811
812 if __name__ == '__main__':
813     test_printer ()
814     raise 'bla'
815     test_pitch()
816     
817     expr = test_expr()
818     expr.set_start (Rational (0))
819     print expr.ly_expression()
820     start = Rational (0,4)
821     stop = Rational (4,2)
822     def sub(x, start=start, stop=stop):
823         ok = x.start >= start and x.start +x.get_length() <= stop
824         return ok
825     
826     print expr.lisp_sub_expression(sub)
827