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