]> git.donarmstrong.com Git - lilypond.git/blob - python/musicexp.py
* scripts/musicxml2ly.py (NonDentedHeadingFormatter.format_headi):
[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 class Output_printer:
16
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                 pass
216
217         def get_length(self):
218                 return Rational (0)
219         
220         
221         def get_properties (self):
222                 return ''
223         
224         def has_children (self):
225                 return False
226         
227         def get_index (self):
228                 if self.parent:
229                         return self.parent.elements.index (self)
230                 else:
231                         return None
232         def name (self):
233                 return self.__class__.__name__
234         
235         def lisp_expression (self):
236                 name = self.name()
237
238                 props = self.get_properties ()
239 #               props += 'start %f ' % self.start
240                 
241                 return "(make-music '%s %s)" % (name,  props)
242
243         def set_start (self, start):
244                 self.start = start
245
246         def find_first (self, predicate):
247                 if predicate (self):
248                         return self
249                 return None
250
251         def print_ly (self, printer):
252                 printer (self.ly_expression ())
253
254
255 class Comment (Music):
256         def __name__ (self):
257                 self.text = ''
258         def print_ly (self, printer):
259                 if isinstance (printer, Output_printer):
260                         lines = string.split (self.text, '\n')
261                         for l in lines:
262                                 if l:
263                                         printer.print_verbatim ('% ' + l)
264                                 printer.newline ()
265                 else:
266                         printer ('% ' + re.sub ('\n', '\n% ', self.text))
267                         printer ('\n')
268                         
269         
270
271 class MusicWrapper (Music):
272         def __init__ (self):
273                 Music.__init__(self)
274                 self.element = None
275         def print_ly (self, func):
276                 self.element.print_ly (func)
277
278 class TimeScaledMusic (MusicWrapper):
279         def print_ly (self, func):
280                 if isinstance(func, Output_printer):
281                         func ('\\times %d/%d ' %
282                               (self.numerator, self.denominator))
283                         func.add_factor (Rational (self.numerator, self.denominator))
284                         MusicWrapper.print_ly (self, func)
285                         func.revert ()
286                 else:
287                         func (r'\times 1/1 ')
288                         MusicWrapper.print_ly (self, func)
289
290 class NestedMusic(Music):
291         def __init__ (self):
292                 Music.__init__ (self)
293                 self.elements = [] 
294         def has_children (self):
295                 return self.elements
296
297
298         def insert_around (self, succ, elt, dir):
299                 assert elt.parent == None
300                 assert succ == None or succ in self.elements
301
302                 
303                 idx = 0
304                 if succ:
305                         idx = self.elements.index (succ)
306                         if dir > 0:
307                                 idx += 1
308                 else:
309                         if dir < 0:
310                                 idx = 0
311                         elif dir > 0:
312                                 idx = len (self.elements)
313
314                 self.elements.insert (idx, elt)
315                 elt.parent = self
316                 
317         def get_properties (self):
318                 return ("'elements (list %s)"
319                         % string.join (map (lambda x: x.lisp_expression(),
320                                             self.elements)))
321
322         def get_subset_properties (self, predicate):
323                 return ("'elements (list %s)"
324                         % string.join (map (lambda x: x.lisp_expression(),
325                                             filter ( predicate,  self.elements))))
326         def get_neighbor (self, music, dir):
327                 assert music.parent == self
328                 idx = self.elements.index (music)
329                 idx += dir
330                 idx = min (idx, len (self.elements) -1)
331                 idx = max (idx, 0)
332
333                 return self.elements[idx]
334
335         def delete_element (self, element):
336                 assert element in self.elements
337                 
338                 self.elements.remove (element)
339                 element.parent = None
340                 
341         def set_start (self, start):
342                 self.start = start
343                 for e in self.elements:
344                         e.set_start (start)
345
346         def find_first (self, predicate):
347                 r = Music.find_first (self, predicate)
348                 if r:
349                         return r
350                 
351                 for e in self.elements:
352                         r = e.find_first (predicate)
353                         if r:
354                                 return r
355                 return None
356                 
357 class SequentialMusic (NestedMusic):
358         def print_ly (self, printer):
359                 printer ('{')
360                 for e in self.elements:
361                         e.print_ly (printer)
362                 printer ('}')
363
364         def lisp_sub_expression (self, pred):
365                 name = self.name()
366
367
368                 props = self.get_subset_properties (pred)
369                 
370                 return "(make-music '%s %s)" % (name,  props)
371         
372         def set_start (self, start):
373                 for e in self.elements:
374                         e.set_start (start)
375                         start += e.get_length()
376                         
377 class EventChord(NestedMusic):
378         def get_length (self):
379                 l = Rational (0)
380                 for e in self.elements:
381                         l = max(l, e.get_length())
382                 return l
383         
384         def print_ly (self, printer):
385                 note_events = [e for e in self.elements if
386                                isinstance (e, NoteEvent)]
387
388                 rest_events = [e for e in self.elements if
389                                isinstance (e, RhythmicEvent)
390                                and not isinstance (e, NoteEvent)]
391                 
392                 other_events = [e for e in self.elements if
393                                 not isinstance (e, RhythmicEvent)]
394
395                 if rest_events:
396                         rest_events[0].print_ly (printer)
397                 elif len (note_events) == 1:
398                         note_events[0].print_ly (printer)
399                 elif note_events:
400                         pitches = [x.pitch.ly_expression () for x in note_events]
401                         printer ('<%s>' % string.join (pitches))
402                         note_events[0].duration.print_ly (printer)
403                 else:
404                         pass
405                 
406                 #       print  'huh', rest_events, note_events, other_events
407                 for e in other_events:
408                         e.print_ly (printer)
409                 
410                         
411 class Event(Music):
412         pass
413
414 class SpanEvent (Event):
415         def __init__(self):
416                 Event.__init__ (self)
417                 self.span_direction = 0
418         def get_properties(self):
419                 return "'span-direction  %d" % self.span_direction
420 class SlurEvent (SpanEvent):
421         def ly_expression (self):
422                 return {-1: '(',
423                         0:'',
424                         1:')'}[self.span_direction]
425
426 class ArpeggioEvent(Music):
427         def ly_expression (self):
428                 return ('\\arpeggio')
429         
430 class RhythmicEvent(Event):
431         def __init__ (self):
432                 Event.__init__ (self)
433                 self.duration = Duration()
434                 
435         def get_length (self):
436                 return self.duration.get_length()
437                 
438         def get_properties (self):
439                 return ("'duration %s"
440                         % self.duration.lisp_expression ())
441         
442 class RestEvent (RhythmicEvent):
443         def ly_expression (self):
444                 return 'r%s' % self.duration.ly_expression ()
445         
446         def print_ly (self, printer):
447                 printer('r')
448                 if isinstance(printer, Output_printer):
449                         printer.skipspace()
450                 self.duration.print_ly (printer)
451
452 class SkipEvent (RhythmicEvent):
453         def ly_expression (self):
454                 return 's%s' % self.duration.ly_expression () 
455
456 class NoteEvent(RhythmicEvent):
457         def  __init__ (self):
458                 RhythmicEvent.__init__ (self)
459                 self.pitch = Pitch()
460
461         def get_properties (self):
462                 return ("'pitch %s\n 'duration %s"
463                         % (self.pitch.lisp_expression (),
464                            self.duration.lisp_expression ()))
465
466         def ly_expression (self):
467                 return '%s%s' % (self.pitch.ly_expression (),
468                                  self.duration.ly_expression ())
469
470         def print_ly (self, printer):
471                 self.pitch.print_ly (printer)
472                 self.duration.print_ly (printer)
473
474 class KeySignatureChange (Music):
475         def __init__ (self):
476                 Music.__init__ (self)
477                 self.scale = []
478                 self.tonic = Pitch()
479                 self.mode = 'major'
480                 
481         def ly_expression (self):
482                 return '\\key %s \\%s' % (self.tonic.ly_step_expression (),
483                                           self.mode)
484         
485         def lisp_expression (self):
486                 pairs = ['(%d . %d)' % (i , self.scale[i]) for i in range (0,7)]
487                 scale_str = ("'(%s)" % string.join (pairs))
488
489                 return """ (make-music 'KeyChangeEvent
490           'pitch-alist %s) """ % scale_str
491
492 class TimeSignatureChange (Music):
493         def __init__ (self):
494                 Music.__init__ (self)
495                 self.fraction = (4,4)
496         def ly_expression (self):
497                 return '\\time %d/%d ' % self.fraction
498         
499 class ClefChange (Music):
500         def __init__ (self):
501                 Music.__init__ (self)
502                 self.type = 'G'
503                 
504         
505         def ly_expression (self):
506                 return '\\clef "%s"' % self.type
507         clef_dict = {
508                 "G": ("clefs.G", -2, -6),
509                 "C": ("clefs.C", 0, 0),
510                 "F": ("clefs.F", 2, 6),
511                 }
512         
513         def lisp_expression (self):
514                 (glyph, pos, c0) = self.clef_dict [self.type]
515                 clefsetting = """
516                 (make-music 'SequentialMusic
517                 'elements (list
518       (context-spec-music
519        (make-property-set 'clefGlyph "%s") 'Staff)
520       (context-spec-music
521        (make-property-set 'clefPosition %d) 'Staff)
522       (context-spec-music
523        (make-property-set 'middleCPosition %d) 'Staff)))
524 """ % (glyph, pos, c0)
525                 return clefsetting
526
527
528 def test_pitch ():
529         bflat = Pitch()
530         bflat.alteration = -1
531         bflat.step =  6
532         bflat.octave = -1
533         fifth = Pitch()
534         fifth.step = 4
535         down = Pitch ()
536         down.step = -4
537         down.normalize ()
538         
539         
540         print bflat.semitones()
541         print bflat.transposed (fifth),  bflat.transposed (fifth).transposed (fifth)
542         print bflat.transposed (fifth).transposed (fifth).transposed (fifth)
543
544         print bflat.semitones(), 'down'
545         print bflat.transposed (down)
546         print bflat.transposed (down).transposed (down)
547         print bflat.transposed (down).transposed (down).transposed (down)
548
549 def test_expr ():
550         m = SequentialMusic()
551         l = 2  
552         evc = EventChord()
553         n = NoteEvent()
554         n.duration.duration_log = l
555         n.pitch.step = 1
556         evc.insert_around (None, n, 0)
557         m.insert_around (None, evc, 0)
558
559         evc = EventChord()
560         n = NoteEvent()
561         n.duration.duration_log = l
562         n.pitch.step = 3
563         evc.insert_around (None, n, 0)
564         m.insert_around (None, evc, 0)
565
566         evc = EventChord()
567         n = NoteEvent()
568         n.duration.duration_log = l
569         n.pitch.step = 2 
570         evc.insert_around (None, n, 0)
571         m.insert_around (None, evc, 0)
572
573         evc = ClefChange("G")
574         m.insert_around (None, evc, 0)
575
576         evc = EventChord()
577         tonic = Pitch ()
578         tonic.step = 2
579         tonic.alteration = -2
580         n = KeySignatureEvent(tonic, [0, 0, -2, 0, 0,-2,-2]  )
581         evc.insert_around (None, n, 0)
582         m.insert_around (None, evc, 0)
583
584         return m
585
586
587 if __name__ == '__main__':
588         test_pitch()
589         raise 1
590         expr = test_expr()
591         expr.set_start (Rational (0))
592         print expr.ly_expression()
593         start = Rational (0,4)
594         stop = Rational (4,2)
595         def sub(x, start=start, stop=stop):
596                 ok = x.start >= start and x.start +x.get_length() <= stop
597                 return ok
598         
599         print expr.lisp_sub_expression(sub)
600