]> git.donarmstrong.com Git - lilypond.git/blob - python/musicexp.py
Merge branch 'master' of ssh://kainhofer@git.sv.gnu.org/srv/git/lilypond into kainhofer
[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
9 def escape_instrument_string (input_string):
10     retstring = string.replace (input_string, "\"", "\\\"")
11     if re.match ('.*[\r\n]+.*', retstring):
12         rx = re.compile (r'[\n\r]+')
13         strings = rx.split (retstring)
14         retstring = "\\markup { \\column { "
15         for s in strings:
16             retstring += "\\line {\"" + s + "\"} "
17         retstring += "} }"
18     else:
19         retstring = "\"" + retstring + "\""
20     return retstring
21
22 class Output_stack_element:
23     def __init__ (self):
24         self.factor = Rational (1)
25     def copy (self):
26         o = Output_stack_element()
27         o.factor = self.factor
28         return o
29
30 class Output_printer:
31
32     """A class that takes care of formatting (eg.: indenting) a
33     Music expression as a .ly file.
34     
35     """
36     ## TODO: support for \relative.
37     
38     def __init__ (self):
39         self._line = ''
40         self._indent = 4
41         self._nesting = 0
42         self._file = sys.stdout
43         self._line_len = 72
44         self._output_state_stack = [Output_stack_element()]
45         self._skipspace = False
46         self._last_duration = None
47
48     def set_file (self, file):
49         self._file = file
50         
51     def dump_version (self):
52         self.newline ()
53         self.print_verbatim ('\\version "@TOPLEVEL_VERSION@"')
54         self.newline ()
55         
56     def get_indent (self):
57         return self._nesting * self._indent
58     
59     def override (self):
60         last = self._output_state_stack[-1]
61         self._output_state_stack.append (last.copy())
62         
63     def add_factor (self, factor):
64         self.override()
65         self._output_state_stack[-1].factor *=  factor
66
67     def revert (self):
68         del self._output_state_stack[-1]
69         if not self._output_state_stack:
70             raise 'empty'
71
72     def duration_factor (self):
73         return self._output_state_stack[-1].factor
74
75     def print_verbatim (self, str):
76         self._line += str
77
78     def unformatted_output (self, str):
79         # don't indent on \< and indent only once on <<
80         self._nesting += ( str.count ('<') 
81                          - str.count ('\<') - str.count ('<<') 
82                          + str.count ('{') )
83         self._nesting -= ( str.count ('>') - str.count ('\>') - str.count ('>>')
84                                            - str.count ('->') - str.count ('_>')
85                                            - str.count ('^>')
86                          + str.count ('}') )
87         self.print_verbatim (str)
88         
89     def print_duration_string (self, str):
90         if self._last_duration == str:
91             return
92         
93         self.unformatted_output (str)
94                   
95     def add_word (self, str):
96         if (len (str) + 1 + len (self._line) > self._line_len):
97             self.newline()
98             self._skipspace = True
99
100         if not self._skipspace:
101             self._line += ' '
102         self.unformatted_output (str)
103         self._skipspace = False
104         
105     def newline (self):
106         self._file.write (self._line + '\n')
107         self._line = ' ' * self._indent * self._nesting
108         self._skipspace = True
109
110     def skipspace (self):
111         self._skipspace = True
112         
113     def __call__(self, arg):
114         self.dump (arg)
115     
116     def dump (self, str):
117         if self._skipspace:
118             self._skipspace = False
119             self.unformatted_output (str)
120         else:
121             words = string.split (str)
122             for w in words:
123                 self.add_word (w)
124
125
126     def close (self):
127         self.newline ()
128         self._file.close ()
129         self._file = None
130
131
132 class Duration:
133     def __init__ (self):
134         self.duration_log = 0
135         self.dots = 0
136         self.factor = Rational (1)
137         
138     def lisp_expression (self):
139         return '(ly:make-duration %d %d %d %d)' % (self.duration_log,
140                              self.dots,
141                              self.factor.numerator (),
142                              self.factor.denominator ())
143
144
145     def ly_expression (self, factor = None):
146         if not factor:
147             factor = self.factor
148             
149         str = '%d%s' % (1 << self.duration_log, '.'*self.dots)
150
151         if factor <> Rational (1,1):
152             if factor.denominator () <> 1:
153                 str += '*%d/%d' % (factor.numerator (), factor.denominator ())
154             else:
155                 str += '*%d' % factor.numerator ()
156
157         return str
158     
159     def print_ly (self, outputter):
160         str = self.ly_expression (self.factor / outputter.duration_factor ())
161         outputter.print_duration_string (str)
162         
163     def __repr__(self):
164         return self.ly_expression()
165         
166     def copy (self):
167         d = Duration ()
168         d.dots = self.dots
169         d.duration_log = self.duration_log
170         d.factor = self.factor
171         return d
172
173     def get_length (self):
174         dot_fact = Rational( (1 << (1 + self.dots))-1,
175                              1 << self.dots)
176
177         log = abs (self.duration_log)
178         dur = 1 << log
179         if self.duration_log < 0:
180             base = Rational (dur)
181         else:
182             base = Rational (1, dur)
183
184         return base * dot_fact * self.factor
185
186
187 # Implement the different note names for the various languages
188 def pitch_generic (pitch, notenames, accidentals):
189     str = notenames[pitch.step]
190     if pitch.alteration < 0:
191         str += accidentals[0] * (-pitch.alteration)
192     elif pitch.alteration > 0:
193         str += accidentals[3] * (pitch.alteration)
194     return str
195
196 def pitch_general (pitch):
197     str = pitch_generic (pitch, ['c', 'd', 'e', 'f', 'g', 'a', 'b'], ['es', 'eh', 'ih', 'is'])
198     return str.replace ('aes', 'as').replace ('ees', 'es')
199
200 def pitch_nederlands (pitch):
201     return pitch_general (pitch)
202
203 def pitch_english (pitch):
204     str = pitch_generic (pitch, ['c', 'd', 'e', 'f', 'g', 'a', 'b'], ['f', 'qf', 'qs', 's'])
205     return str.replace ('aes', 'as').replace ('ees', 'es')
206
207 def pitch_deutsch (pitch):
208     str = pitch_generic (pitch, ['c', 'd', 'e', 'f', 'g', 'a', 'h'], ['es', 'eh', 'ih', 'is'])
209     return str.replace ('hes', 'b').replace ('aes', 'as').replace ('ees', 'es')
210
211 def pitch_norsk (pitch):
212     return pitch_deutsch (pitch)
213
214 def pitch_svenska (pitch):
215     str = pitch_generic (pitch, ['c', 'd', 'e', 'f', 'g', 'a', 'h'], ['ess', '', '', 'iss'])
216     return str.replace ('hess', 'b').replace ('aes', 'as').replace ('ees', 'es')
217
218 def pitch_italiano (pitch):
219     str = pitch_generic (pitch, ['do', 're', 'mi', 'fa', 'sol', 'la', 'si'], ['b', 'sb', 'sd', 'd'])
220     return str
221
222 def pitch_catalan (pitch):
223     return pitch_italiano (pitch)
224
225 def pitch_espanol (pitch):
226     str = pitch_generic (pitch, ['do', 're', 'mi', 'fa', 'sol', 'la', 'si'], ['b', '', '', 's'])
227     return str
228
229 def pitch_vlaams (pitch):
230     str = pitch_generic (pitch, ['do', 're', 'mi', 'fa', 'sol', 'la', 'si'], ['b', '', '', 'k'])
231     return str
232
233 def set_pitch_language (language):
234     global pitch_generating_function
235     function_dict = {
236         "nederlands": pitch_nederlands,
237         "english": pitch_english,
238         "deutsch": pitch_deutsch,
239         "norsk": pitch_norsk,
240         "svenska": pitch_svenska,
241         "italiano": pitch_italiano,
242         "catalan": pitch_catalan,
243         "espanol": pitch_espanol,
244         "vlaams": pitch_vlaams}
245     pitch_generating_function = function_dict.get (language, pitch_general)
246
247 # global variable to hold the formatting function.
248 pitch_generating_function = pitch_general
249
250
251 class Pitch:
252     def __init__ (self):
253         self.alteration = 0
254         self.step = 0
255         self.octave = 0
256         
257     def __repr__(self):
258         return self.ly_expression()
259
260     def transposed (self, interval):
261         c = self.copy ()
262         c.alteration  += interval.alteration
263         c.step += interval.step
264         c.octave += interval.octave
265         c.normalize ()
266         
267         target_st = self.semitones()  + interval.semitones()
268         c.alteration += target_st - c.semitones()
269         return c
270
271     def normalize (c):
272         while c.step < 0:
273             c.step += 7
274             c.octave -= 1
275         c.octave += c.step / 7
276         c.step = c.step  % 7
277
278     def lisp_expression (self):
279         return '(ly:make-pitch %d %d %d)' % (self.octave,
280                                              self.step,
281                                              self.alteration)
282
283     def copy (self):
284         p = Pitch ()
285         p.alteration = self.alteration
286         p.step = self.step
287         p.octave = self.octave 
288         return p
289
290     def steps (self):
291         return self.step + self.octave *7
292
293     def semitones (self):
294         return self.octave * 12 + [0,2,4,5,7,9,11][self.step] + self.alteration
295     
296     def ly_step_expression (self): 
297         return pitch_generating_function (self)
298     
299     def ly_expression (self):
300         str = self.ly_step_expression ()
301         if self.octave >= 0:
302             str += "'" * (self.octave + 1) 
303         elif self.octave < -1:
304             str += "," * (-self.octave - 1) 
305             
306         return str
307     
308     def print_ly (self, outputter):
309         outputter (self.ly_expression())
310     
311 class Music:
312     def __init__ (self):
313         self.parent = None
314         self.start = Rational (0)
315         self.comment = ''
316         self.identifier = None
317         
318     def get_length(self):
319         return Rational (0)
320     
321     def get_properties (self):
322         return ''
323     
324     def has_children (self):
325         return False
326     
327     def get_index (self):
328         if self.parent:
329             return self.parent.elements.index (self)
330         else:
331             return None
332     def name (self):
333         return self.__class__.__name__
334     
335     def lisp_expression (self):
336         name = self.name()
337
338         props = self.get_properties ()
339         
340         return "(make-music '%s %s)" % (name,  props)
341
342     def set_start (self, start):
343         self.start = start
344
345     def find_first (self, predicate):
346         if predicate (self):
347             return self
348         return None
349
350     def print_comment (self, printer, text = None):
351         if not text:
352             text = self.comment
353
354         if not text:
355             return
356             
357         if text == '\n':
358             printer.newline ()
359             return
360         
361         lines = string.split (text, '\n')
362         for l in lines:
363             if l:
364                 printer.unformatted_output ('% ' + l)
365             printer.newline ()
366             
367
368     def print_with_identifier (self, printer):
369         if self.identifier: 
370             printer ("\\%s" % self.identifier)
371         else:
372             self.print_ly (printer)
373
374     def print_ly (self, printer):
375         printer (self.ly_expression ())
376
377 class MusicWrapper (Music):
378     def __init__ (self):
379         Music.__init__(self)
380         self.element = None
381     def print_ly (self, func):
382         self.element.print_ly (func)
383
384 class ModeChangingMusicWrapper (MusicWrapper):
385     def __init__ (self):
386         MusicWrapper.__init__ (self)
387         self.mode = 'notemode'
388
389     def print_ly (self, func):
390         func ('\\%s' % self.mode)
391         MusicWrapper.print_ly (self, func)
392
393 class TimeScaledMusic (MusicWrapper):
394     def print_ly (self, func):
395         func ('\\times %d/%d ' %
396            (self.numerator, self.denominator))
397         func.add_factor (Rational (self.numerator, self.denominator))
398         MusicWrapper.print_ly (self, func)
399         func.revert ()
400
401 class NestedMusic(Music):
402     def __init__ (self):
403         Music.__init__ (self)
404         self.elements = []
405
406     def append (self, what):
407         if what:
408             self.elements.append (what)
409             
410     def has_children (self):
411         return self.elements
412
413     def insert_around (self, succ, elt, dir):
414         assert elt.parent == None
415         assert succ == None or succ in self.elements
416
417         
418         idx = 0
419         if succ:
420             idx = self.elements.index (succ)
421             if dir > 0:
422                 idx += 1
423         else:
424             if dir < 0:
425                 idx = 0
426             elif dir > 0:
427                 idx = len (self.elements)
428
429         self.elements.insert (idx, elt)
430         elt.parent = self
431         
432     def get_properties (self):
433         return ("'elements (list %s)"
434             % string.join (map (lambda x: x.lisp_expression(),
435                       self.elements)))
436
437     def get_subset_properties (self, predicate):
438         return ("'elements (list %s)"
439             % string.join (map (lambda x: x.lisp_expression(),
440                       filter ( predicate,  self.elements))))
441     def get_neighbor (self, music, dir):
442         assert music.parent == self
443         idx = self.elements.index (music)
444         idx += dir
445         idx = min (idx, len (self.elements) -1)
446         idx = max (idx, 0)
447
448         return self.elements[idx]
449
450     def delete_element (self, element):
451         assert element in self.elements
452         
453         self.elements.remove (element)
454         element.parent = None
455         
456     def set_start (self, start):
457         self.start = start
458         for e in self.elements:
459             e.set_start (start)
460
461     def find_first (self, predicate):
462         r = Music.find_first (self, predicate)
463         if r:
464             return r
465         
466         for e in self.elements:
467             r = e.find_first (predicate)
468             if r:
469                 return r
470         return None
471         
472 class SequentialMusic (NestedMusic):
473     def get_last_event_chord (self):
474         value = None
475         at = len( self.elements ) - 1
476         while (at >= 0 and
477                not isinstance (self.elements[at], EventChord) and
478                not isinstance (self.elements[at], BarLine)):
479             at -= 1
480
481         if (at >= 0 and isinstance (self.elements[at], EventChord)):
482             value = self.elements[at]
483         return value
484
485     def print_ly (self, printer, newline = True):
486         printer ('{')
487         if self.comment:
488             self.print_comment (printer)
489
490         if newline:
491             printer.newline()
492         for e in self.elements:
493             e.print_ly (printer)
494
495         printer ('}')
496         if newline:
497             printer.newline()
498             
499     def lisp_sub_expression (self, pred):
500         name = self.name()
501
502
503         props = self.get_subset_properties (pred)
504         
505         return "(make-music '%s %s)" % (name,  props)
506     
507     def set_start (self, start):
508         for e in self.elements:
509             e.set_start (start)
510             start += e.get_length()
511
512 class RepeatedMusic:
513     def __init__ (self):
514         self.repeat_type = "volta"
515         self.repeat_count = 2
516         self.endings = []
517         self.music = None
518     def set_music (self, music):
519         if isinstance (music, Music):
520             self.music = music
521         elif isinstance (music, list):
522             self.music = SequentialMusic ()
523             self.music.elements = music
524         else:
525             sys.stderr.write ("WARNING: Unable to set the music %s for the repeat %s" % (music, self))
526     def add_ending (self, music):
527         self.endings.append (music)
528     def print_ly (self, printer):
529         printer.dump ('\\repeat %s %s' % (self.repeat_type, self.repeat_count))
530         if self.music:
531             self.music.print_ly (printer)
532         else:
533             sys.stderr.write ("WARNING: Encountered repeat without body\n")
534             printer.dump ('{}')
535         if self.endings:
536             printer.dump ('\\alternative {')
537             for e in self.endings:
538                 e.print_ly (printer)
539             printer.dump ('}')
540
541
542 class Lyrics:
543     def __init__ (self):
544         self.lyrics_syllables = []
545
546     def print_ly (self, printer):
547         printer.dump ("\lyricmode {")
548         for l in self.lyrics_syllables:
549             printer.dump ( "%s " % l )
550         printer.dump ("}")
551
552     def ly_expression (self):
553         lstr = "\lyricmode {\n  "
554         for l in self.lyrics_syllables:
555             lstr += l + " "
556         lstr += "\n}"
557         return lstr
558
559
560 class Header:
561     def __init__ (self):
562         self.header_fields = {}
563     def set_field (self, field, value):
564         self.header_fields[field] = value
565
566     def print_ly (self, printer):
567         printer.dump ("\header {")
568         printer.newline ()
569         for (k,v) in self.header_fields.items ():
570             if v:
571                 printer.dump ('%s = %s' % (k,v))
572                 printer.newline ()
573         printer.dump ("}")
574         printer.newline ()
575         printer.newline ()
576
577
578 class Paper:
579     def __init__ (self):
580         self.global_staff_size = -1
581         # page size
582         self.page_width = -1
583         self.page_height = -1
584         # page margins
585         self.top_margin = -1
586         self.bottom_margin = -1
587         self.left_margin = -1
588         self.right_margin = -1
589         self.system_left_margin = -1
590         self.system_right_margin = -1
591         self.system_distance = -1
592         self.top_system_distance = -1
593
594     def print_length_field (self, printer, field, value):
595         if value >= 0:
596             printer.dump ("%s = %s\\cm" % (field, value))
597             printer.newline ()
598     def print_ly (self, printer):
599         if self.global_staff_size > 0:
600             printer.dump ('#(set-global-staff-size %s)' % self.global_staff_size)
601             printer.newline ()
602         printer.dump ('\\paper {')
603         printer.newline ()
604         self.print_length_field (printer, "paper-width", self.page_width)
605         self.print_length_field (printer, "paper-height", self.page_height)
606         self.print_length_field (printer, "top-margin", self.top_margin)
607         self.print_length_field (printer, "botton-margin", self.bottom_margin)
608         self.print_length_field (printer, "left-margin", self.left_margin)
609         # TODO: maybe set line-width instead of right-margin?
610         self.print_length_field (printer, "right-margin", self.right_margin)
611         # TODO: What's the corresponding setting for system_left_margin and
612         #        system_right_margin in Lilypond?
613         self.print_length_field (printer, "between-system-space", self.system_distance)
614         self.print_length_field (printer, "page-top-space", self.top_system_distance)
615
616         printer.dump ('}')
617         printer.newline ()
618
619
620 class EventChord (NestedMusic):
621     def __init__ (self):
622         NestedMusic.__init__ (self)
623         self.grace_elements = None
624         self.grace_type = None
625     def append_grace (self, element):
626         if element:
627             if not self.grace_elements:
628                 self.grace_elements = SequentialMusic ()
629             self.grace_elements.append (element)
630
631     def get_length (self):
632         l = Rational (0)
633         for e in self.elements:
634             l = max(l, e.get_length())
635         return l
636     
637     def print_ly (self, printer):
638         note_events = [e for e in self.elements if
639                isinstance (e, NoteEvent)]
640
641         rest_events = [e for e in self.elements if
642                isinstance (e, RhythmicEvent)
643                and not isinstance (e, NoteEvent)]
644         
645         other_events = [e for e in self.elements if
646                 not isinstance (e, RhythmicEvent)]
647
648         if self.grace_elements and self.elements:
649             if self.grace_type:
650                 printer ('\\%s' % self.grace_type)
651             else:
652                 printer ('\\grace')
653             # don't print newlines after the { and } braces
654             self.grace_elements.print_ly (printer, False)
655
656         if rest_events:
657             rest_events[0].print_ly (printer)
658         elif len (note_events) == 1:
659             note_events[0].print_ly (printer)
660         elif note_events:
661             pitches = [x.pitch.ly_expression () for x in note_events]
662             printer ('<%s>' % string.join (pitches))
663             note_events[0].duration.print_ly (printer)
664         else:
665             pass
666         
667         for e in other_events:
668             e.print_ly (printer)
669
670         self.print_comment (printer)
671             
672 class Partial (Music):
673     def __init__ (self):
674         Music.__init__ (self)
675         self.partial = None
676     def print_ly (self, printer):
677         if self.partial:
678             printer.dump ("\\partial %s" % self.partial.ly_expression ())
679
680 class BarLine (Music):
681     def __init__ (self):
682         Music.__init__ (self)
683         self.bar_number = 0
684         self.type = None
685         
686     def print_ly (self, printer):
687         bar_symbol = { 'regular': "|", 'dotted': ":", 'dashed': ":",
688                        'heavy': "|", 'light-light': "||", 'light-heavy': "|.",
689                        'heavy-light': ".|", 'heavy-heavy': ".|.", 'tick': "'",
690                        'short': "'", 'none': "" }.get (self.type, None)
691         if bar_symbol <> None:
692             printer.dump ('\\bar "%s"' % bar_symbol)
693         else:
694             printer.dump ("|")
695
696         if self.bar_number > 0 and (self.bar_number % 10) == 0:
697             printer.dump ("\\barNumberCheck #%d " % self.bar_number)
698         else:
699             printer.print_verbatim (' %% %d' % self.bar_number)
700         printer.newline ()
701
702     def ly_expression (self):
703         return " | "
704
705 class Event(Music):
706     pass
707
708 class SpanEvent (Event):
709     def __init__ (self):
710         Event.__init__ (self)
711         self.span_direction = 0 # start/stop
712         self.line_type = 'solid'
713         self.span_type = 0 # e.g. cres/decrescendo, ottava up/down
714         self.size = 0 # size of e.g. ocrave shift
715     def wait_for_note (self):
716         return True
717     def get_properties(self):
718         return "'span-direction  %d" % self.span_direction
719     def set_span_type (self, type):
720         self.span_type = type
721
722 class SlurEvent (SpanEvent):
723     def ly_expression (self):
724         before = ''
725         after = ''
726         # TODO: setting dashed/dotted line style does not work, because that
727         #       command needs to be written before the note, not when the
728         #       event is observed after the note!
729         #before = {'dotted': '\\slurDotted', 
730         #          'dashed' : '\\slurDashed'}.get (self.line_type, '')
731         #if before:
732             #after = '\\slurSolid'
733
734         return {-1: before + '(' + after,
735             1:')'}.get (self.span_direction, '')
736
737 class BeamEvent (SpanEvent):
738     def ly_expression (self):
739         return {-1: '[',
740             1:']'}.get (self.span_direction, '')
741
742 class PedalEvent (SpanEvent):
743     def ly_expression (self):
744         return {-1: '\\sustainDown',
745             1:'\\sustainUp'}.get (self.span_direction, '')
746
747 # type==-1 means octave up, type==-2 means octave down
748 class OctaveShiftEvent (SpanEvent):
749     def wait_for_note (self):
750         return False;
751     def set_span_type (self, type):
752         self.span_type = {'up': 1, 'down': -1}.get (type, 0)
753     def ly_octave_shift_indicator (self):
754         # convert 8/15 to lilypond indicators (+-1/+-2)
755         value = {8: 1, 15: 2}.get (self.size, 0)
756         # negative values go up!
757         value *= -1*self.span_type
758         return value
759     def ly_expression (self):
760         dir = self.ly_octave_shift_indicator ()
761         value = ''
762         if dir:
763             value = '#(set-octavation %s)' % dir
764         return { 
765             -1: value,
766             1: '#(set-octavation 0)'}.get (self.span_direction, '')
767
768 class TrillSpanEvent (SpanEvent):
769     def ly_expression (self):
770         return {-1: '\\startTrillSpan',
771             0: '', # no need to write out anything for type='continue'
772             1:'\\stopTrillSpan'}.get (self.span_direction, '')
773
774 class GlissandoEvent (SpanEvent):
775     def ly_expression (self):
776         style = ''
777         # TODO: wavy-line glissandos don't work, becasue the style has to be
778         #       set before the note, at the \glissando it's already too late!
779         #if self.line_type == 'wavy':
780             #style = "\once\override Glissando #'style = #'zigzag"
781         # In lilypond, glissando is NOT a spanner, unlike MusicXML.
782         return {-1: style + '\\glissando',
783             1:''}.get (self.span_direction, '')
784
785 class ArpeggioEvent(Event):
786     def __init__ (self):
787         Event.__init__ (self)
788         self.direction = 0
789     def wait_for_note (self):
790         return True;
791     def ly_expression (self):
792         # TODO: Use self.direction for up/down arpeggios
793         return ('\\arpeggio')
794
795
796 class TieEvent(Event):
797     def ly_expression (self):
798         return '~'
799
800
801 class HairpinEvent (SpanEvent):
802     def set_span_type (self, type):
803         self.span_type = {'crescendo' : 1, 'decrescendo' : -1, 'diminuendo' : -1 }.get (type, 0)
804     def hairpin_to_ly (self):
805         if self.span_direction == 1:
806             return '\!'
807         else:
808             return {1: '\<', -1: '\>'}.get (self.span_type, '')
809     
810     def ly_expression (self):
811         return self.hairpin_to_ly ()
812     
813     def print_ly (self, printer):
814         val = self.hairpin_to_ly ()
815         if val:
816             printer.dump (val)
817
818
819
820 class DynamicsEvent (Event):
821     def __init__ (self):
822         self.type = None
823         self.available_commands = [ "ppppp", "pppp", "ppp", "pp", "p", 
824                                     "mp", "mf", 
825                                     "f", "ff", "fff", "ffff", 
826                                     "fp", "sf", "sff", "sp", "spp", "sfz", "rfz" ];
827     def wait_for_note (self):
828         return True;
829     def ly_expression (self):
830         if self.type == None:
831             return;
832         elif self.type in self.available_commands:
833             return '\%s' % self.type
834         else:
835             return '-\markup{ \dynamic %s }' % self.type
836         
837     def print_ly (self, printer):
838         if self.type == None:
839             return
840         elif self.type in self.available_commands:
841             printer.dump ("\\%s" % self.type)
842         else:
843             printer.dump ("-\\markup{ \\dynamic %s }" % self.type)
844
845
846 class TextEvent (Event):
847     def __init__ (self):
848         self.Text = None
849         self.force_direction = None
850         self.markup = ''
851     def wait_for_note (self):
852         return True
853
854     def direction_mod (self):
855         return { 1: '^', -1: '_', 0: '-' }.get (self.force_direction, '-')
856
857     def ly_expression (self):
858         base_string = '%s\"%s\"'
859         if self.markup:
860             base_string = '%s\markup{ ' + self.markup + ' {%s} }'
861         return base_string % (self.direction_mod (), self.text)
862
863 class ArticulationEvent (Event):
864     def __init__ (self):
865         self.type = None
866         self.force_direction = None
867     def wait_for_note (self):
868         return True;
869
870     def direction_mod (self):
871         return { 1: '^', -1: '_', 0: '-' }.get (self.force_direction, '')
872
873     def ly_expression (self):
874         return '%s\\%s' % (self.direction_mod (), self.type)
875
876 class ShortArticulationEvent (ArticulationEvent):
877     def direction_mod (self):
878         # default is -
879         return { 1: '^', -1: '_', 0: '-' }.get (self.force_direction, '-')
880     def ly_expression (self):
881         if self.type:
882             return '%s%s' % (self.direction_mod (), self.type)
883         else:
884             return ''
885
886 class NoDirectionArticulationEvent (ArticulationEvent):
887     def ly_expression (self):
888         if self.type:
889             return '\\%s' % self.type
890         else:
891             return ''
892
893 class MarkupEvent (ShortArticulationEvent):
894     def __init__ (self):
895         ArticulationEvent.__init__ (self)
896         self.contents = None
897     def ly_expression (self):
898         if self.contents:
899             return "%s\\markup { %s }" % (self.direction_mod (), self.contents)
900         else:
901             return ''
902
903 class FretEvent (MarkupEvent):
904     def __init__ (self):
905         MarkupEvent.__init__ (self)
906         self.force_direction = 1
907         self.strings = 6
908         self.frets = 4
909         self.barre = None
910         self.elements = []
911     def ly_expression (self):
912         val = ""
913         if self.strings <> 6:
914             val += "w:%s;" % self.strings
915         if self.frets <> 4:
916             val += "h:%s;" % self.frets
917         if self.barre and len (self.barre) >= 3:
918             val += "c:%s-%s-%s;" % (self.barre[0], self.barre[1], self.barre[2])
919         have_fingering = False
920         for i in self.elements:
921             if len (i) > 1:
922                 val += "%s-%s" % (i[0], i[1])
923             if len (i) > 2:
924                 have_fingering = True
925                 val += "-%s" % i[2]
926             val += ";"
927         if have_fingering:
928             val = "f:1;" + val
929         if val:
930             return "%s\\markup { \\fret-diagram #\"%s\" }" % (self.direction_mod (), val)
931         else:
932             return ''
933
934 class TremoloEvent (ArticulationEvent):
935     def __init__ (self):
936         Event.__init__ (self)
937         self.bars = 0
938
939     def ly_expression (self):
940         str=''
941         if self.bars and self.bars > 0:
942             str += ':%s' % (2 ** (2 + string.atoi (self.bars)))
943         return str
944
945 class BendEvent (ArticulationEvent):
946     def __init__ (self):
947         Event.__init__ (self)
948         self.alter = 0
949     def ly_expression (self):
950         if self.alter:
951             return "-\\bendAfter #%s" % self.alter
952         else:
953             return ''
954
955 class RhythmicEvent(Event):
956     def __init__ (self):
957         Event.__init__ (self)
958         self.duration = Duration()
959         
960     def get_length (self):
961         return self.duration.get_length()
962         
963     def get_properties (self):
964         return ("'duration %s"
965                 % self.duration.lisp_expression ())
966     
967 class RestEvent (RhythmicEvent):
968     def __init__ (self):
969         RhythmicEvent.__init__ (self)
970         self.pitch = None
971     def ly_expression (self):
972         if self.pitch:
973             return "%s%s\\rest" % (self.pitch.ly_expression (), self.duration.ly_expression ())
974         else:
975             return 'r%s' % self.duration.ly_expression ()
976     
977     def print_ly (self, printer):
978         if self.pitch:
979             self.pitch.print_ly (printer)
980             self.duration.print_ly (printer)
981             printer ('\\rest')
982         else:
983             printer('r')
984             self.duration.print_ly (printer)
985
986 class SkipEvent (RhythmicEvent):
987     def ly_expression (self):
988         return 's%s' % self.duration.ly_expression () 
989
990 class NoteEvent(RhythmicEvent):
991     def  __init__ (self):
992         RhythmicEvent.__init__ (self)
993         self.pitch = None
994         self.drum_type = None
995         self.cautionary = False
996         self.forced_accidental = False
997         
998     def get_properties (self):
999         str = RhythmicEvent.get_properties (self)
1000         
1001         if self.pitch:
1002             str += self.pitch.lisp_expression ()
1003         elif self.drum_type:
1004             str += "'drum-type '%s" % self.drum_type
1005
1006         return str
1007     
1008     def pitch_mods (self):
1009         excl_question = ''
1010         if self.cautionary:
1011             excl_question += '?'
1012         if self.forced_accidental:
1013             excl_question += '!'
1014
1015         return excl_question
1016     
1017     def ly_expression (self):
1018         if self.pitch:
1019             return '%s%s%s' % (self.pitch.ly_expression (),
1020                                self.pitch_mods(),
1021                                self.duration.ly_expression ())
1022         elif self.drum_type:
1023             return '%s%s' (self.drum_type,
1024                            self.duration.ly_expression ())
1025
1026     def print_ly (self, printer):
1027         if self.pitch:
1028             self.pitch.print_ly (printer)
1029             printer (self.pitch_mods ())
1030         else:
1031             printer (self.drum_type)
1032
1033         self.duration.print_ly (printer)
1034
1035 class KeySignatureChange (Music):
1036     def __init__ (self):
1037         Music.__init__ (self)
1038         self.scale = []
1039         self.tonic = Pitch()
1040         self.mode = 'major'
1041         
1042     def ly_expression (self):
1043         return '\\key %s \\%s' % (self.tonic.ly_step_expression (),
1044                      self.mode)
1045     
1046     def lisp_expression (self):
1047         pairs = ['(%d . %d)' % (i , self.scale[i]) for i in range (0,7)]
1048         scale_str = ("'(%s)" % string.join (pairs))
1049
1050         return """ (make-music 'KeyChangeEvent
1051      'pitch-alist %s) """ % scale_str
1052
1053 class TimeSignatureChange (Music):
1054     def __init__ (self):
1055         Music.__init__ (self)
1056         self.fraction = (4,4)
1057     def ly_expression (self):
1058         return '\\time %d/%d ' % self.fraction
1059     
1060 class ClefChange (Music):
1061     def __init__ (self):
1062         Music.__init__ (self)
1063         self.type = 'G'
1064         self.position = 2
1065         self.octave = 0
1066
1067     def octave_modifier (self):
1068         return {1: "^8", 2: "^15", -1: "_8", -2: "_15"}.get (self.octave, '')
1069     def clef_name (self):
1070         return {('G', 2): "treble",
1071                 ('G', 1): "french",
1072                 ('C', 1): "soprano",
1073                 ('C', 2): "mezzosoprano",
1074                 ('C', 3): "alto",
1075                 ('C', 4): "tenor",
1076                 ('C', 5): "baritone",
1077                 ('F', 3): "varbaritone",
1078                 ('F', 4): "bass",
1079                 ('F', 5): "subbass",
1080                 ("percussion", 2): "percussion",
1081                 ("TAB", 5): "tab"}.get ((self.type, self.position), None)
1082     def ly_expression (self):
1083         return '\\clef "%s%s"' % (self.clef_name (), self.octave_modifier ())
1084
1085     clef_dict = {
1086         "G": ("clefs.G", -2, -6),
1087         "C": ("clefs.C", 0, 0),
1088         "F": ("clefs.F", 2, 6),
1089         }
1090     
1091     def lisp_expression (self):
1092         try:
1093             (glyph, pos, c0) = self.clef_dict[self.type]
1094         except KeyError:
1095             return ""
1096         clefsetting = """
1097         (make-music 'SequentialMusic
1098         'elements (list
1099    (context-spec-music
1100    (make-property-set 'clefGlyph "%s") 'Staff)
1101    (context-spec-music
1102    (make-property-set 'clefPosition %d) 'Staff)
1103    (context-spec-music
1104    (make-property-set 'middleCPosition %d) 'Staff)))
1105 """ % (glyph, pos, c0)
1106         return clefsetting
1107
1108
1109 class StaffChange (Music):
1110     def __init__ (self, staff):
1111         Music.__init__ (self)
1112         self.staff = staff
1113     def ly_expression (self):
1114         if self.staff:
1115             return "\\change Staff=\"%s\"" % self.staff
1116         else:
1117             return ''
1118
1119
1120 class MultiMeasureRest(Music):
1121
1122     def lisp_expression (self):
1123         return """
1124 (make-music
1125   'MultiMeasureRestMusicGroup
1126   'elements
1127   (list (make-music (quote BarCheck))
1128         (make-music
1129           'EventChord
1130           'elements
1131           (list (make-music
1132                   'MultiMeasureRestEvent
1133                   'duration
1134                   %s)))
1135         (make-music (quote BarCheck))))
1136 """ % self.duration.lisp_expression ()
1137
1138     def ly_expression (self):
1139         return 'R%s' % self.duration.ly_expression ()
1140
1141
1142 class StaffGroup:
1143     def __init__ (self, command = "StaffGroup"):
1144         self.stafftype = command
1145         self.id = None
1146         self.instrument_name = None
1147         self.short_instrument_name = None
1148         self.symbol = None
1149         self.spanbar = None
1150         self.children = []
1151         self.is_group = True
1152         # part_information is a list with entries of the form
1153         #     [staffid, voicelist]
1154         # where voicelist is a list with entries of the form
1155         #     [voiceid1, [lyricsid11, lyricsid12,...] ]
1156         self.part_information = None
1157
1158     def append_staff (self, staff):
1159         self.children.append (staff)
1160
1161     def set_part_information (self, part_name, staves_info):
1162         if part_name == self.id:
1163             self.part_information = staves_info
1164         else:
1165             for c in self.children:
1166                 c.set_part_information (part_name, staves_info)
1167
1168     def print_ly_contents (self, printer):
1169         for c in self.children:
1170             if c:
1171                 c.print_ly (printer)
1172     def print_ly_overrides (self, printer):
1173         needs_with = False
1174         needs_with |= self.spanbar == "no"
1175         needs_with |= self.instrument_name != None
1176         needs_with |= self.short_instrument_name != None
1177         needs_with |= (self.symbol != None) and (self.symbol != "bracket")
1178         if needs_with:
1179             printer.dump ("\\with {")
1180             if self.instrument_name or self.short_instrument_name:
1181                 printer.dump ("\\consists \"Instrument_name_engraver\"")
1182             if self.spanbar == "no":
1183                 printer.dump ("\\override SpanBar #'transparent = ##t")
1184             brack = {"brace": "SystemStartBrace",
1185                      "none": "f",
1186                      "line": "SystemStartSquare"}.get (self.symbol, None)
1187             if brack:
1188                 printer.dump ("systemStartDelimiter = #'%s" % brack)
1189             printer.dump ("}")
1190
1191     def print_ly (self, printer):
1192         if self.stafftype:
1193             printer.dump ("\\new %s" % self.stafftype)
1194         self.print_ly_overrides (printer)
1195         printer.dump ("<<")
1196         printer.newline ()
1197         if self.stafftype and self.instrument_name:
1198             printer.dump ("\\set %s.instrumentName = %s" % (self.stafftype, 
1199                     escape_instrument_string (self.instrument_name)))
1200             printer.newline ()
1201         if self.stafftype and self.short_instrument_name:
1202             printer.dump ("\\set %s.shortInstrumentName = %s" % (self.stafftype,
1203                     escape_instrument_string (self.short_instrument_name)))
1204             printer.newline ()
1205         self.print_ly_contents (printer)
1206         printer.newline ()
1207         printer.dump (">>")
1208         printer.newline ()
1209
1210
1211 class Staff (StaffGroup):
1212     def __init__ (self, command = "Staff"):
1213         StaffGroup.__init__ (self, command)
1214         self.is_group = False
1215         self.part = None
1216         self.voice_command = "Voice"
1217         self.substafftype = None
1218
1219     def print_ly_overrides (self, printer):
1220         pass
1221
1222     def print_ly_contents (self, printer):
1223         if not self.id or not self.part_information:
1224             return
1225         sub_staff_type = self.substafftype
1226         if not sub_staff_type:
1227             sub_staff_type = self.stafftype
1228
1229         for [staff_id, voices] in self.part_information:
1230             if staff_id:
1231                 printer ('\\context %s = "%s" << ' % (sub_staff_type, staff_id))
1232             else:
1233                 printer ('\\context %s << ' % sub_staff_type)
1234             printer.newline ()
1235             n = 0
1236             nr_voices = len (voices)
1237             for [v, lyrics] in voices:
1238                 n += 1
1239                 voice_count_text = ''
1240                 if nr_voices > 1:
1241                     voice_count_text = {1: ' \\voiceOne', 2: ' \\voiceTwo',
1242                                         3: ' \\voiceThree'}.get (n, ' \\voiceFour')
1243                 printer ('\\context %s = "%s" {%s \\%s }' % (self.voice_command, v, voice_count_text, v))
1244                 printer.newline ()
1245
1246                 for l in lyrics:
1247                     printer ('\\new Lyrics \\lyricsto "%s" \\%s' % (v,l))
1248                     printer.newline()
1249             printer ('>>')
1250
1251     def print_ly (self, printer):
1252         if self.part_information and len (self.part_information) > 1:
1253             self.stafftype = "PianoStaff"
1254             self.substafftype = "Staff"
1255         StaffGroup.print_ly (self, printer)
1256
1257 class TabStaff (Staff):
1258     def __init__ (self, command = "TabStaff"):
1259         Staff.__init__ (self, command)
1260         self.string_tunings = []
1261         self.tablature_format = None
1262         self.voice_command = "TabVoice"
1263     def print_ly_overrides (self, printer):
1264         if self.string_tunings or self.tablature_format:
1265             printer.dump ("\\with {")
1266             if self.string_tunings:
1267                 printer.dump ("stringTunings = #'(")
1268                 for i in self.string_tunings:
1269                     printer.dump ("%s" % i.semitones ())
1270                 printer.dump (")")
1271             if self.tablature_format:
1272                 printer.dump ("tablatureFormat = #%s" % self.tablature_format)
1273             printer.dump ("}")
1274
1275
1276 class DrumStaff (Staff):
1277     def __init__ (self, command = "DrumStaff"):
1278         Staff.__init__ (self, command)
1279         self.drum_style_table = None
1280         self.voice_command = "DrumVoice"
1281     def print_ly_overrides (self, printer):
1282         if self.drum_style_table:
1283             printer.dump ("\with {")
1284             printer.dump ("drumStyleTable = #%s" % self.drum_style_table)
1285             printer.dump ("}")
1286
1287 class RhythmicStaff (Staff):
1288     def __init__ (self, command = "RhythmicStaff"):
1289         Staff.__init__ (self, command)
1290
1291
1292 def test_pitch ():
1293     bflat = Pitch()
1294     bflat.alteration = -1
1295     bflat.step =  6
1296     bflat.octave = -1
1297     fifth = Pitch()
1298     fifth.step = 4
1299     down = Pitch ()
1300     down.step = -4
1301     down.normalize ()
1302     
1303     
1304     print bflat.semitones()
1305     print bflat.transposed (fifth),  bflat.transposed (fifth).transposed (fifth)
1306     print bflat.transposed (fifth).transposed (fifth).transposed (fifth)
1307
1308     print bflat.semitones(), 'down'
1309     print bflat.transposed (down)
1310     print bflat.transposed (down).transposed (down)
1311     print bflat.transposed (down).transposed (down).transposed (down)
1312
1313
1314
1315 def test_printer ():
1316     def make_note ():
1317         evc = EventChord()
1318         n = NoteEvent()
1319         evc.append (n)
1320         return n
1321
1322     def make_tup ():
1323         m = SequentialMusic()
1324         m.append (make_note ())
1325         m.append (make_note ())
1326         m.append (make_note ())
1327
1328         
1329         t = TimeScaledMusic ()
1330         t.numerator = 2
1331         t.denominator = 3
1332         t.element = m
1333         return t
1334
1335     m = SequentialMusic ()
1336     m.append (make_tup ())
1337     m.append (make_tup ())
1338     m.append (make_tup ())
1339     
1340     printer = Output_printer()
1341     m.print_ly (printer)
1342     printer.newline ()
1343     
1344 def test_expr ():
1345     m = SequentialMusic()
1346     l = 2  
1347     evc = EventChord()
1348     n = NoteEvent()
1349     n.duration.duration_log = l
1350     n.pitch.step = 1
1351     evc.insert_around (None, n, 0)
1352     m.insert_around (None, evc, 0)
1353
1354     evc = EventChord()
1355     n = NoteEvent()
1356     n.duration.duration_log = l
1357     n.pitch.step = 3
1358     evc.insert_around (None, n, 0)
1359     m.insert_around (None, evc, 0)
1360
1361     evc = EventChord()
1362     n = NoteEvent()
1363     n.duration.duration_log = l
1364     n.pitch.step = 2 
1365     evc.insert_around (None, n, 0)
1366     m.insert_around (None, evc, 0)
1367
1368     evc = ClefChange()
1369     evc.type = 'treble'
1370     m.insert_around (None, evc, 0)
1371
1372     evc = EventChord()
1373     tonic = Pitch ()
1374     tonic.step = 2
1375     tonic.alteration = -2
1376     n = KeySignatureChange()
1377     n.tonic=tonic.copy()
1378     n.scale = [0, 0, -2, 0, 0,-2,-2]
1379     
1380     evc.insert_around (None, n, 0)
1381     m.insert_around (None, evc, 0)
1382
1383     return m
1384
1385
1386 if __name__ == '__main__':
1387     test_printer ()
1388     raise 'bla'
1389     test_pitch()
1390     
1391     expr = test_expr()
1392     expr.set_start (Rational (0))
1393     print expr.ly_expression()
1394     start = Rational (0,4)
1395     stop = Rational (4,2)
1396     def sub(x, start=start, stop=stop):
1397         ok = x.start >= start and x.start +x.get_length() <= stop
1398         return ok
1399     
1400     print expr.lisp_sub_expression(sub)
1401