]> git.donarmstrong.com Git - lilypond.git/blob - python/musicexp.py
MusicXML: Better support for dynamics
[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             1:'\\sustainUp'}.get (self.span_direction, '')
798
799 # type==-1 means octave up, type==-2 means octave down
800 class OctaveShiftEvent (SpanEvent):
801     def wait_for_note (self):
802         return False;
803     def set_span_type (self, type):
804         self.span_type = {'up': 1, 'down': -1}.get (type, 0)
805     def ly_octave_shift_indicator (self):
806         # convert 8/15 to lilypond indicators (+-1/+-2)
807         value = {8: 1, 15: 2}.get (self.size, 0)
808         # negative values go up!
809         value *= -1*self.span_type
810         return value
811     def ly_expression (self):
812         dir = self.ly_octave_shift_indicator ()
813         value = ''
814         if dir:
815             value = '#(set-octavation %s)' % dir
816         return { 
817             -1: value,
818             1: '#(set-octavation 0)'}.get (self.span_direction, '')
819
820 class TrillSpanEvent (SpanEvent):
821     def ly_expression (self):
822         return {-1: '\\startTrillSpan',
823             0: '', # no need to write out anything for type='continue'
824             1:'\\stopTrillSpan'}.get (self.span_direction, '')
825
826 class GlissandoEvent (SpanEvent):
827     def ly_expression (self):
828         style = ''
829         # TODO: wavy-line glissandos don't work, becasue the style has to be
830         #       set before the note, at the \glissando it's already too late!
831         #if self.line_type == 'wavy':
832             #style = "\once\override Glissando #'style = #'zigzag"
833         # In lilypond, glissando is NOT a spanner, unlike MusicXML.
834         return {-1: style + '\\glissando',
835             1:''}.get (self.span_direction, '')
836
837 class ArpeggioEvent(Event):
838     def __init__ (self):
839         Event.__init__ (self)
840         self.direction = 0
841     def wait_for_note (self):
842         return True;
843     def ly_expression (self):
844         # TODO: Use self.direction for up/down arpeggios
845         return ('\\arpeggio')
846
847
848 class TieEvent(Event):
849     def ly_expression (self):
850         return '~'
851
852
853 class HairpinEvent (SpanEvent):
854     def set_span_type (self, type):
855         self.span_type = {'crescendo' : 1, 'decrescendo' : -1, 'diminuendo' : -1 }.get (type, 0)
856     def hairpin_to_ly (self):
857         if self.span_direction == 1:
858             return '\!'
859         else:
860             return {1: '\<', -1: '\>'}.get (self.span_type, '')
861     
862     def ly_expression (self):
863         return self.hairpin_to_ly ()
864     
865     def print_ly (self, printer):
866         val = self.hairpin_to_ly ()
867         if val:
868             printer.dump (val)
869
870
871
872 class DynamicsEvent (Event):
873     def __init__ (self):
874         self.type = None
875     def wait_for_note (self):
876         return True;
877     def ly_expression (self):
878         if self.type:
879             return '\%s' % self.type
880         else:
881             return;
882
883     def print_ly (self, printer):
884         if self.type:
885             printer.dump ("\\%s" % self.type)
886
887
888 class TextEvent (Event):
889     def __init__ (self):
890         self.Text = None
891         self.force_direction = None
892         self.markup = ''
893     def wait_for_note (self):
894         return True
895
896     def direction_mod (self):
897         return { 1: '^', -1: '_', 0: '-' }.get (self.force_direction, '-')
898
899     def ly_expression (self):
900         base_string = '%s\"%s\"'
901         if self.markup:
902             base_string = '%s\markup{ ' + self.markup + ' {%s} }'
903         return base_string % (self.direction_mod (), self.text)
904
905 class ArticulationEvent (Event):
906     def __init__ (self):
907         self.type = None
908         self.force_direction = None
909     def wait_for_note (self):
910         return True;
911
912     def direction_mod (self):
913         return { 1: '^', -1: '_', 0: '-' }.get (self.force_direction, '')
914
915     def ly_expression (self):
916         return '%s\\%s' % (self.direction_mod (), self.type)
917
918 class ShortArticulationEvent (ArticulationEvent):
919     def direction_mod (self):
920         # default is -
921         return { 1: '^', -1: '_', 0: '-' }.get (self.force_direction, '-')
922     def ly_expression (self):
923         if self.type:
924             return '%s%s' % (self.direction_mod (), self.type)
925         else:
926             return ''
927
928 class NoDirectionArticulationEvent (ArticulationEvent):
929     def ly_expression (self):
930         if self.type:
931             return '\\%s' % self.type
932         else:
933             return ''
934
935 class MarkupEvent (ShortArticulationEvent):
936     def __init__ (self):
937         ArticulationEvent.__init__ (self)
938         self.contents = None
939     def ly_expression (self):
940         if self.contents:
941             return "%s\\markup { %s }" % (self.direction_mod (), self.contents)
942         else:
943             return ''
944
945 class FretEvent (MarkupEvent):
946     def __init__ (self):
947         MarkupEvent.__init__ (self)
948         self.force_direction = 1
949         self.strings = 6
950         self.frets = 4
951         self.barre = None
952         self.elements = []
953     def ly_expression (self):
954         val = ""
955         if self.strings <> 6:
956             val += "w:%s;" % self.strings
957         if self.frets <> 4:
958             val += "h:%s;" % self.frets
959         if self.barre and len (self.barre) >= 3:
960             val += "c:%s-%s-%s;" % (self.barre[0], self.barre[1], self.barre[2])
961         have_fingering = False
962         for i in self.elements:
963             if len (i) > 1:
964                 val += "%s-%s" % (i[0], i[1])
965             if len (i) > 2:
966                 have_fingering = True
967                 val += "-%s" % i[2]
968             val += ";"
969         if have_fingering:
970             val = "f:1;" + val
971         if val:
972             return "%s\\markup { \\fret-diagram #\"%s\" }" % (self.direction_mod (), val)
973         else:
974             return ''
975
976 class TremoloEvent (ArticulationEvent):
977     def __init__ (self):
978         Event.__init__ (self)
979         self.bars = 0
980
981     def ly_expression (self):
982         str=''
983         if self.bars and self.bars > 0:
984             str += ':%s' % (2 ** (2 + string.atoi (self.bars)))
985         return str
986
987 class BendEvent (ArticulationEvent):
988     def __init__ (self):
989         Event.__init__ (self)
990         self.alter = 0
991     def ly_expression (self):
992         if self.alter:
993             return "-\\bendAfter #%s" % self.alter
994         else:
995             return ''
996
997 class RhythmicEvent(Event):
998     def __init__ (self):
999         Event.__init__ (self)
1000         self.duration = Duration()
1001         
1002     def get_length (self):
1003         return self.duration.get_length()
1004         
1005     def get_properties (self):
1006         return ("'duration %s"
1007                 % self.duration.lisp_expression ())
1008     
1009 class RestEvent (RhythmicEvent):
1010     def __init__ (self):
1011         RhythmicEvent.__init__ (self)
1012         self.pitch = None
1013     def ly_expression (self):
1014         if self.pitch:
1015             return "%s%s\\rest" % (self.pitch.ly_expression (), self.duration.ly_expression ())
1016         else:
1017             return 'r%s' % self.duration.ly_expression ()
1018     
1019     def print_ly (self, printer):
1020         if self.pitch:
1021             self.pitch.print_ly (printer)
1022             self.duration.print_ly (printer)
1023             printer ('\\rest')
1024         else:
1025             printer('r')
1026             self.duration.print_ly (printer)
1027
1028 class SkipEvent (RhythmicEvent):
1029     def ly_expression (self):
1030         return 's%s' % self.duration.ly_expression () 
1031
1032 class NoteEvent(RhythmicEvent):
1033     def  __init__ (self):
1034         RhythmicEvent.__init__ (self)
1035         self.pitch = None
1036         self.drum_type = None
1037         self.cautionary = False
1038         self.forced_accidental = False
1039         
1040     def get_properties (self):
1041         str = RhythmicEvent.get_properties (self)
1042         
1043         if self.pitch:
1044             str += self.pitch.lisp_expression ()
1045         elif self.drum_type:
1046             str += "'drum-type '%s" % self.drum_type
1047
1048         return str
1049     
1050     def pitch_mods (self):
1051         excl_question = ''
1052         if self.cautionary:
1053             excl_question += '?'
1054         if self.forced_accidental:
1055             excl_question += '!'
1056
1057         return excl_question
1058     
1059     def ly_expression (self):
1060         if self.pitch:
1061             return '%s%s%s' % (self.pitch.ly_expression (),
1062                                self.pitch_mods(),
1063                                self.duration.ly_expression ())
1064         elif self.drum_type:
1065             return '%s%s' (self.drum_type,
1066                            self.duration.ly_expression ())
1067
1068     def print_ly (self, printer):
1069         if self.pitch:
1070             self.pitch.print_ly (printer)
1071             printer (self.pitch_mods ())
1072         else:
1073             printer (self.drum_type)
1074
1075         self.duration.print_ly (printer)
1076
1077 class KeySignatureChange (Music):
1078     def __init__ (self):
1079         Music.__init__ (self)
1080         self.scale = []
1081         self.tonic = Pitch()
1082         self.mode = 'major'
1083         
1084     def ly_expression (self):
1085         return '\\key %s \\%s' % (self.tonic.ly_step_expression (),
1086                      self.mode)
1087     
1088     def lisp_expression (self):
1089         pairs = ['(%d . %d)' % (i , self.scale[i]) for i in range (0,7)]
1090         scale_str = ("'(%s)" % string.join (pairs))
1091
1092         return """ (make-music 'KeyChangeEvent
1093      'pitch-alist %s) """ % scale_str
1094
1095 class TimeSignatureChange (Music):
1096     def __init__ (self):
1097         Music.__init__ (self)
1098         self.fraction = (4,4)
1099     def ly_expression (self):
1100         return '\\time %d/%d ' % self.fraction
1101     
1102 class ClefChange (Music):
1103     def __init__ (self):
1104         Music.__init__ (self)
1105         self.type = 'G'
1106         self.position = 2
1107         self.octave = 0
1108
1109     def octave_modifier (self):
1110         return {1: "^8", 2: "^15", -1: "_8", -2: "_15"}.get (self.octave, '')
1111     def clef_name (self):
1112         return {('G', 2): "treble",
1113                 ('G', 1): "french",
1114                 ('C', 1): "soprano",
1115                 ('C', 2): "mezzosoprano",
1116                 ('C', 3): "alto",
1117                 ('C', 4): "tenor",
1118                 ('C', 5): "baritone",
1119                 ('F', 3): "varbaritone",
1120                 ('F', 4): "bass",
1121                 ('F', 5): "subbass",
1122                 ("percussion", 2): "percussion",
1123                 ("TAB", 5): "tab"}.get ((self.type, self.position), None)
1124     def ly_expression (self):
1125         return '\\clef "%s%s"' % (self.clef_name (), self.octave_modifier ())
1126
1127     clef_dict = {
1128         "G": ("clefs.G", -2, -6),
1129         "C": ("clefs.C", 0, 0),
1130         "F": ("clefs.F", 2, 6),
1131         }
1132     
1133     def lisp_expression (self):
1134         try:
1135             (glyph, pos, c0) = self.clef_dict[self.type]
1136         except KeyError:
1137             return ""
1138         clefsetting = """
1139         (make-music 'SequentialMusic
1140         'elements (list
1141    (context-spec-music
1142    (make-property-set 'clefGlyph "%s") 'Staff)
1143    (context-spec-music
1144    (make-property-set 'clefPosition %d) 'Staff)
1145    (context-spec-music
1146    (make-property-set 'middleCPosition %d) 'Staff)))
1147 """ % (glyph, pos, c0)
1148         return clefsetting
1149
1150
1151 class StaffChange (Music):
1152     def __init__ (self, staff):
1153         Music.__init__ (self)
1154         self.staff = staff
1155     def ly_expression (self):
1156         if self.staff:
1157             return "\\change Staff=\"%s\"" % self.staff
1158         else:
1159             return ''
1160
1161
1162 class MultiMeasureRest(Music):
1163
1164     def lisp_expression (self):
1165         return """
1166 (make-music
1167   'MultiMeasureRestMusicGroup
1168   'elements
1169   (list (make-music (quote BarCheck))
1170         (make-music
1171           'ChordEvent
1172           'elements
1173           (list (make-music
1174                   'MultiMeasureRestEvent
1175                   'duration
1176                   %s)))
1177         (make-music (quote BarCheck))))
1178 """ % self.duration.lisp_expression ()
1179
1180     def ly_expression (self):
1181         return 'R%s' % self.duration.ly_expression ()
1182
1183
1184 class StaffGroup:
1185     def __init__ (self, command = "StaffGroup"):
1186         self.stafftype = command
1187         self.id = None
1188         self.instrument_name = None
1189         self.short_instrument_name = None
1190         self.symbol = None
1191         self.spanbar = None
1192         self.children = []
1193         self.is_group = True
1194         # part_information is a list with entries of the form
1195         #     [staffid, voicelist]
1196         # where voicelist is a list with entries of the form
1197         #     [voiceid1, [lyricsid11, lyricsid12,...] ]
1198         self.part_information = None
1199
1200     def append_staff (self, staff):
1201         self.children.append (staff)
1202
1203     def set_part_information (self, part_name, staves_info):
1204         if part_name == self.id:
1205             self.part_information = staves_info
1206         else:
1207             for c in self.children:
1208                 c.set_part_information (part_name, staves_info)
1209
1210     def print_ly_contents (self, printer):
1211         for c in self.children:
1212             if c:
1213                 c.print_ly (printer)
1214     def print_ly_overrides (self, printer):
1215         needs_with = False
1216         needs_with |= self.spanbar == "no"
1217         needs_with |= self.instrument_name != None
1218         needs_with |= self.short_instrument_name != None
1219         needs_with |= (self.symbol != None) and (self.symbol != "bracket")
1220         if needs_with:
1221             printer.dump ("\\with {")
1222             if self.instrument_name or self.short_instrument_name:
1223                 printer.dump ("\\consists \"Instrument_name_engraver\"")
1224             if self.spanbar == "no":
1225                 printer.dump ("\\override SpanBar #'transparent = ##t")
1226             brack = {"brace": "SystemStartBrace",
1227                      "none": "f",
1228                      "line": "SystemStartSquare"}.get (self.symbol, None)
1229             if brack:
1230                 printer.dump ("systemStartDelimiter = #'%s" % brack)
1231             printer.dump ("}")
1232
1233     def print_ly (self, printer):
1234         if self.stafftype:
1235             printer.dump ("\\new %s" % self.stafftype)
1236         self.print_ly_overrides (printer)
1237         printer.dump ("<<")
1238         printer.newline ()
1239         if self.stafftype and self.instrument_name:
1240             printer.dump ("\\set %s.instrumentName = %s" % (self.stafftype, 
1241                     escape_instrument_string (self.instrument_name)))
1242             printer.newline ()
1243         if self.stafftype and self.short_instrument_name:
1244             printer.dump ("\\set %s.shortInstrumentName = %s" % (self.stafftype,
1245                     escape_instrument_string (self.short_instrument_name)))
1246             printer.newline ()
1247         self.print_ly_contents (printer)
1248         printer.newline ()
1249         printer.dump (">>")
1250         printer.newline ()
1251
1252
1253 class Staff (StaffGroup):
1254     def __init__ (self, command = "Staff"):
1255         StaffGroup.__init__ (self, command)
1256         self.is_group = False
1257         self.part = None
1258         self.voice_command = "Voice"
1259         self.substafftype = None
1260
1261     def print_ly_overrides (self, printer):
1262         pass
1263
1264     def print_ly_contents (self, printer):
1265         if not self.id or not self.part_information:
1266             return
1267         sub_staff_type = self.substafftype
1268         if not sub_staff_type:
1269             sub_staff_type = self.stafftype
1270
1271         for [staff_id, voices] in self.part_information:
1272             if staff_id:
1273                 printer ('\\context %s = "%s" << ' % (sub_staff_type, staff_id))
1274             else:
1275                 printer ('\\context %s << ' % sub_staff_type)
1276             printer.newline ()
1277             n = 0
1278             nr_voices = len (voices)
1279             for [v, lyrics] in voices:
1280                 n += 1
1281                 voice_count_text = ''
1282                 if nr_voices > 1:
1283                     voice_count_text = {1: ' \\voiceOne', 2: ' \\voiceTwo',
1284                                         3: ' \\voiceThree'}.get (n, ' \\voiceFour')
1285                 printer ('\\context %s = "%s" {%s \\%s }' % (self.voice_command, v, voice_count_text, v))
1286                 printer.newline ()
1287
1288                 for l in lyrics:
1289                     printer ('\\new Lyrics \\lyricsto "%s" \\%s' % (v,l))
1290                     printer.newline()
1291             printer ('>>')
1292
1293     def print_ly (self, printer):
1294         if self.part_information and len (self.part_information) > 1:
1295             self.stafftype = "PianoStaff"
1296             self.substafftype = "Staff"
1297         StaffGroup.print_ly (self, printer)
1298
1299 class TabStaff (Staff):
1300     def __init__ (self, command = "TabStaff"):
1301         Staff.__init__ (self, command)
1302         self.string_tunings = []
1303         self.tablature_format = None
1304         self.voice_command = "TabVoice"
1305     def print_ly_overrides (self, printer):
1306         if self.string_tunings or self.tablature_format:
1307             printer.dump ("\\with {")
1308             if self.string_tunings:
1309                 printer.dump ("stringTunings = #'(")
1310                 for i in self.string_tunings:
1311                     printer.dump ("%s" % i.semitones ())
1312                 printer.dump (")")
1313             if self.tablature_format:
1314                 printer.dump ("tablatureFormat = #%s" % self.tablature_format)
1315             printer.dump ("}")
1316
1317
1318 class DrumStaff (Staff):
1319     def __init__ (self, command = "DrumStaff"):
1320         Staff.__init__ (self, command)
1321         self.drum_style_table = None
1322         self.voice_command = "DrumVoice"
1323     def print_ly_overrides (self, printer):
1324         if self.drum_style_table:
1325             printer.dump ("\with {")
1326             printer.dump ("drumStyleTable = #%s" % self.drum_style_table)
1327             printer.dump ("}")
1328
1329 class RhythmicStaff (Staff):
1330     def __init__ (self, command = "RhythmicStaff"):
1331         Staff.__init__ (self, command)
1332
1333
1334 def test_pitch ():
1335     bflat = Pitch()
1336     bflat.alteration = -1
1337     bflat.step =  6
1338     bflat.octave = -1
1339     fifth = Pitch()
1340     fifth.step = 4
1341     down = Pitch ()
1342     down.step = -4
1343     down.normalize ()
1344     
1345     
1346     print bflat.semitones()
1347     print bflat.transposed (fifth),  bflat.transposed (fifth).transposed (fifth)
1348     print bflat.transposed (fifth).transposed (fifth).transposed (fifth)
1349
1350     print bflat.semitones(), 'down'
1351     print bflat.transposed (down)
1352     print bflat.transposed (down).transposed (down)
1353     print bflat.transposed (down).transposed (down).transposed (down)
1354
1355
1356
1357 def test_printer ():
1358     def make_note ():
1359         evc = ChordEvent()
1360         n = NoteEvent()
1361         evc.append (n)
1362         return n
1363
1364     def make_tup ():
1365         m = SequentialMusic()
1366         m.append (make_note ())
1367         m.append (make_note ())
1368         m.append (make_note ())
1369
1370         
1371         t = TimeScaledMusic ()
1372         t.numerator = 2
1373         t.denominator = 3
1374         t.element = m
1375         return t
1376
1377     m = SequentialMusic ()
1378     m.append (make_tup ())
1379     m.append (make_tup ())
1380     m.append (make_tup ())
1381     
1382     printer = Output_printer()
1383     m.print_ly (printer)
1384     printer.newline ()
1385     
1386 def test_expr ():
1387     m = SequentialMusic()
1388     l = 2  
1389     evc = ChordEvent()
1390     n = NoteEvent()
1391     n.duration.duration_log = l
1392     n.pitch.step = 1
1393     evc.insert_around (None, n, 0)
1394     m.insert_around (None, evc, 0)
1395
1396     evc = ChordEvent()
1397     n = NoteEvent()
1398     n.duration.duration_log = l
1399     n.pitch.step = 3
1400     evc.insert_around (None, n, 0)
1401     m.insert_around (None, evc, 0)
1402
1403     evc = ChordEvent()
1404     n = NoteEvent()
1405     n.duration.duration_log = l
1406     n.pitch.step = 2 
1407     evc.insert_around (None, n, 0)
1408     m.insert_around (None, evc, 0)
1409
1410     evc = ClefChange()
1411     evc.type = 'treble'
1412     m.insert_around (None, evc, 0)
1413
1414     evc = ChordEvent()
1415     tonic = Pitch ()
1416     tonic.step = 2
1417     tonic.alteration = -2
1418     n = KeySignatureChange()
1419     n.tonic=tonic.copy()
1420     n.scale = [0, 0, -2, 0, 0,-2,-2]
1421     
1422     evc.insert_around (None, n, 0)
1423     m.insert_around (None, evc, 0)
1424
1425     return m
1426
1427
1428 if __name__ == '__main__':
1429     test_printer ()
1430     raise 'bla'
1431     test_pitch()
1432     
1433     expr = test_expr()
1434     expr.set_start (Rational (0))
1435     print expr.ly_expression()
1436     start = Rational (0,4)
1437     stop = Rational (4,2)
1438     def sub(x, start=start, stop=stop):
1439         ok = x.start >= start and x.start +x.get_length() <= stop
1440         return ok
1441     
1442     print expr.lisp_sub_expression(sub)
1443