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