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