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