]> git.donarmstrong.com Git - lilypond.git/blobdiff - python/musicexp.py
Merge branch 'master' of ssh+git://hanwen@git.sv.gnu.org/srv/git/lilypond
[lilypond.git] / python / musicexp.py
index 168be1a33077a20bb639e37f15f0f1a0d1ecc208..2ae8a6784f3ea480f3343d6539323fd79ec706d0 100644 (file)
@@ -2,9 +2,19 @@ import inspect
 import sys
 import string
 import re
+import lilylib as ly
+
+_ = ly._
 
 from rational import Rational
 
+# Store previously converted pitch for \relative conversion as a global state variable
+previous_pitch = None
+relative_pitches = False
+
+def warning (str):
+    ly.stderr_write ((_ ("warning: %s") % str) + "\n")
+
 
 def escape_instrument_string (input_string):
     retstring = string.replace (input_string, "\"", "\\\"")
@@ -145,8 +155,12 @@ class Duration:
     def ly_expression (self, factor = None):
         if not factor:
             factor = self.factor
-            
-        str = '%d%s' % (1 << self.duration_log, '.'*self.dots)
+
+        if self.duration_log < 0:
+            str = {-1: "\\breve", -2: "\\longa"}.get (self.duration_log, "1")
+        else:
+            str = '%d' % (1 << self.duration_log)
+        str += '.'*self.dots
 
         if factor <> Rational (1,1):
             if factor.denominator () <> 1:
@@ -295,14 +309,38 @@ class Pitch:
     
     def ly_step_expression (self): 
         return pitch_generating_function (self)
-    
-    def ly_expression (self):
-        str = self.ly_step_expression ()
+
+    def absolute_pitch (self):
         if self.octave >= 0:
-            str += "'" * (self.octave + 1) 
+            return "'" * (self.octave + 1)
         elif self.octave < -1:
-            str += "," * (-self.octave - 1) 
-            
+            return "," * (-self.octave - 1)
+        else:
+            return ''
+
+    def relative_pitch (self):
+        global previous_pitch
+        if not previous_pitch:
+            previous_pitch = self
+            return self.absolute_pitch ()
+        previous_pitch_steps = previous_pitch.octave * 7 + previous_pitch.step
+        this_pitch_steps = self.octave * 7 + self.step
+        pitch_diff = (this_pitch_steps - previous_pitch_steps)
+        previous_pitch = self
+        if pitch_diff > 3:
+            return "'" * ((pitch_diff + 3) / 7)
+        elif pitch_diff < -3:
+            return "," * ((-pitch_diff + 3) / 7)
+        else:
+            return ""
+
+    def ly_expression (self):
+        str = self.ly_step_expression ()
+        if relative_pitches:
+            str += self.relative_pitch ()
+        else:
+            str += self.absolute_pitch ()
+
         return str
     
     def print_ly (self, outputter):
@@ -390,6 +428,24 @@ class ModeChangingMusicWrapper (MusicWrapper):
         func ('\\%s' % self.mode)
         MusicWrapper.print_ly (self, func)
 
+class RelativeMusic (MusicWrapper):
+    def __init__ (self):
+        MusicWrapper.__init__ (self)
+        self.basepitch = None
+
+    def print_ly (self, func):
+        global previous_pitch
+        global relative_pitches
+        prev_relative_pitches = relative_pitches
+        relative_pitches = True
+        previous_pitch = self.basepitch
+        if not previous_pitch:
+            previous_pitch = Pitch ()
+        func ('\\relative %s%s' % (pitch_generating_function (previous_pitch), 
+                                   previous_pitch.absolute_pitch ()))
+        MusicWrapper.print_ly (self, func)
+        relative_pitches = prev_relative_pitches
+
 class TimeScaledMusic (MusicWrapper):
     def print_ly (self, func):
         func ('\\times %d/%d ' %
@@ -474,11 +530,11 @@ class SequentialMusic (NestedMusic):
         value = None
         at = len( self.elements ) - 1
         while (at >= 0 and
-               not isinstance (self.elements[at], EventChord) and
+               not isinstance (self.elements[at], ChordEvent) and
                not isinstance (self.elements[at], BarLine)):
             at -= 1
 
-        if (at >= 0 and isinstance (self.elements[at], EventChord)):
+        if (at >= 0 and isinstance (self.elements[at], ChordEvent)):
             value = self.elements[at]
         return value
 
@@ -522,7 +578,8 @@ class RepeatedMusic:
             self.music = SequentialMusic ()
             self.music.elements = music
         else:
-            sys.stderr.write ("WARNING: Unable to set the music %s for the repeat %s" % (music, self))
+            warning (_ ("unable to set the music %(music)s for the repeat %(repeat)s" % \
+                            {'music':music, 'repeat':self}))
     def add_ending (self, music):
         self.endings.append (music)
     def print_ly (self, printer):
@@ -530,7 +587,7 @@ class RepeatedMusic:
         if self.music:
             self.music.print_ly (printer)
         else:
-            sys.stderr.write ("WARNING: Encountered repeat without body\n")
+            warning (_ ("encountered repeat without body"))
             printer.dump ('{}')
         if self.endings:
             printer.dump ('\\alternative {')
@@ -616,8 +673,33 @@ class Paper:
         printer.dump ('}')
         printer.newline ()
 
+class Layout:
+    def __init__ (self):
+        self.context_dict = {}
+    def add_context (self, context):
+        if not self.context_dict.has_key (context):
+            self.context_dict[context] = []
+    def set_context_item (self, context, item):
+        self.add_context (context)
+        if not item in self.context_dict[context]:
+            self.context_dict[context].append (item)
+    def print_ly (self, printer):
+        if self.context_dict.items ():
+            printer.dump ('\\layout {')
+            printer.newline ()
+            for (context, defs) in self.context_dict.items ():
+                printer.dump ('\\context { \\%s' % context)
+                printer.newline ()
+                for d in defs:
+                    printer.dump (d)
+                    printer.newline ()
+                printer.dump ('}')
+                printer.newline ()
+            printer.dump ('}')
+            printer.newline ()
+
 
-class EventChord (NestedMusic):
+class ChordEvent (NestedMusic):
     def __init__ (self):
         NestedMusic.__init__ (self)
         self.grace_elements = None
@@ -652,14 +734,25 @@ class EventChord (NestedMusic):
                 printer ('\\grace')
             # don't print newlines after the { and } braces
             self.grace_elements.print_ly (printer, False)
+        # Print all overrides and other settings needed by the 
+        # articulations/ornaments before the note
+        for e in other_events:
+            e.print_before_note (printer)
 
         if rest_events:
             rest_events[0].print_ly (printer)
         elif len (note_events) == 1:
             note_events[0].print_ly (printer)
         elif note_events:
-            pitches = [x.pitch.ly_expression () for x in note_events]
+            global previous_pitch
+            pitches = []
+            basepitch = None
+            for x in note_events:
+                pitches.append (x.pitch.ly_expression ())
+                if not basepitch:
+                    basepitch = previous_pitch
             printer ('<%s>' % string.join (pitches))
+            previous_pitch = basepitch
             note_events[0].duration.print_ly (printer)
         else:
             pass
@@ -667,6 +760,9 @@ class EventChord (NestedMusic):
         for e in other_events:
             e.print_ly (printer)
 
+        for e in other_events:
+            e.print_after_note (printer)
+
         self.print_comment (printer)
             
 class Partial (Music):
@@ -703,6 +799,19 @@ class BarLine (Music):
         return " | "
 
 class Event(Music):
+    def __init__ (self):
+        # strings to print before the note to which an event is attached.
+        # Ignored for notes etc.
+        self.before_note = None
+        self.after_note = None
+   # print something before the note to which an event is attached, e.g. overrides
+    def print_before_note (self, printer):
+        if self.before_note:
+            printer.dump (self.before_note)
+   # print something after the note to which an event is attached, e.g. resetting
+    def print_after_note (self, printer):
+        if self.after_note:
+            printer.dump (self.after_note)
     pass
 
 class SpanEvent (Event):
@@ -720,34 +829,52 @@ class SpanEvent (Event):
         self.span_type = type
 
 class SlurEvent (SpanEvent):
+    def print_before_note (self, printer):
+        command = {'dotted': '\\slurDotted', 
+                  'dashed' : '\\slurDashed'}.get (self.line_type, '')
+        if command and self.span_direction == -1:
+            printer.dump (command)
+    def print_after_note (self, printer):
+        # reset non-solid slur types!
+        command = {'dotted': '\\slurSolid', 
+                  'dashed' : '\\slurSolid'}.get (self.line_type, '')
+        if command and self.span_direction == -1:
+            printer.dump (command)
     def ly_expression (self):
-        before = ''
-        after = ''
-        # TODO: setting dashed/dotted line style does not work, because that
-        #       command needs to be written before the note, not when the
-        #       event is observed after the note!
-        #before = {'dotted': '\\slurDotted', 
-        #          'dashed' : '\\slurDashed'}.get (self.line_type, '')
-        #if before:
-            #after = '\\slurSolid'
-
-        return {-1: before + '(' + after,
-            1:')'}.get (self.span_direction, '')
+        return {-1: '(', 1:')'}.get (self.span_direction, '')
 
 class BeamEvent (SpanEvent):
     def ly_expression (self):
-        return {-1: '[',
-            1:']'}.get (self.span_direction, '')
+        return {-1: '[', 1:']'}.get (self.span_direction, '')
 
 class PedalEvent (SpanEvent):
     def ly_expression (self):
         return {-1: '\\sustainDown',
+            0:'\\sustainUp\\sustainDown',
             1:'\\sustainUp'}.get (self.span_direction, '')
 
-# type==-1 means octave up, type==-2 means octave down
+class TextSpannerEvent (SpanEvent):
+    def ly_expression (self):
+        return {-1: '\\startTextSpan',
+            1:'\\stopTextSpan'}.get (self.span_direction, '')
+
+class BracketSpannerEvent (SpanEvent):
+    # Ligature brackets use prefix-notation!!!
+    def print_before_note (self, printer):
+        if self.span_direction == -1:
+            printer.dump ('\[')
+    # the the bracket after the last note
+    def print_after_note (self, printer):
+        if self.span_direction == 1:
+            printer.dump ('\]')
+    # we're printing everything in print_(before|after)_note...
+    def ly_expression (self):
+        return '';
+
+
 class OctaveShiftEvent (SpanEvent):
     def wait_for_note (self):
-        return False;
+        return False
     def set_span_type (self, type):
         self.span_type = {'up': 1, 'down': -1}.get (type, 0)
     def ly_octave_shift_indicator (self):
@@ -772,24 +899,37 @@ class TrillSpanEvent (SpanEvent):
             1:'\\stopTrillSpan'}.get (self.span_direction, '')
 
 class GlissandoEvent (SpanEvent):
+    def print_before_note (self, printer):
+        if self.span_direction == -1:
+            style= {
+                "dashed" : "dashed-line",
+                "dotted" : "dotted-line",
+                "wavy"   : "zigzag" 
+            }. get (self.line_type, None)
+            if style:
+                printer.dump ("\once \override Glissando #'style = #'%s" % style)
     def ly_expression (self):
-        style = ''
-        # TODO: wavy-line glissandos don't work, becasue the style has to be
-        #       set before the note, at the \glissando it's already too late!
-        #if self.line_type == 'wavy':
-            #style = "\once\override Glissando #'style = #'zigzag"
-        # In lilypond, glissando is NOT a spanner, unlike MusicXML.
-        return {-1: style + '\\glissando',
+        return {-1: '\\glissando',
             1:''}.get (self.span_direction, '')
 
 class ArpeggioEvent(Event):
     def __init__ (self):
         Event.__init__ (self)
         self.direction = 0
+        self.non_arpeggiate = False
     def wait_for_note (self):
-        return True;
+        return True
+    def print_before_note (self, printer):
+        if self.non_arpeggiate:
+            printer.dump ("\\arpeggioBracket")
+        else:
+          dir = { -1: "\\arpeggioDown", 1: "\\arpeggioUp" }.get (self.direction, '')
+          if dir:
+              printer.dump (dir)
+    def print_after_note (self, printer):
+        if self.non_arpeggiate or self.direction:
+            printer.dump ("\\arpeggioNeutral")
     def ly_expression (self):
-        # TODO: Use self.direction for up/down arpeggios
         return ('\\arpeggio')
 
 
@@ -819,32 +959,45 @@ class HairpinEvent (SpanEvent):
 
 class DynamicsEvent (Event):
     def __init__ (self):
+        Event.__init__ (self)
         self.type = None
-        self.available_commands = [ "ppppp", "pppp", "ppp", "pp", "p", 
-                                    "mp", "mf", 
-                                    "f", "ff", "fff", "ffff", 
-                                    "fp", "sf", "sff", "sp", "spp", "sfz", "rfz" ];
     def wait_for_note (self):
-        return True;
+        return True
     def ly_expression (self):
-        if self.type == None:
-            return;
-        elif self.type in self.available_commands:
+        if self.type:
             return '\%s' % self.type
         else:
-            return '-\markup{ \dynamic %s }' % self.type
-        
-    def print_ly (self, printer):
-        if self.type == None:
             return
-        elif self.type in self.available_commands:
+
+    def print_ly (self, printer):
+        if self.type:
             printer.dump ("\\%s" % self.type)
+
+class MarkEvent (Event):
+    def __init__ (self, text="\\default"):
+        Event.__init__ (self)
+        self.mark = text
+    def wait_for_note (self):
+        return False
+    def ly_contents (self):
+        if self.mark:
+            return '%s' % self.mark
+        else:
+            return "\"ERROR\""
+    def ly_expression (self):
+        return '\\mark %s' % self.ly_contents ()
+
+class MusicGlyphMarkEvent (MarkEvent):
+    def ly_contents (self):
+        if self.mark:
+            return '\\markup { \\musicglyph #"scripts.%s" }' % self.mark
         else:
-            printer.dump ("-\\markup{ \\dynamic %s }" % self.type)
+            return ''
 
 
 class TextEvent (Event):
     def __init__ (self):
+        Event.__init__ (self)
         self.Text = None
         self.force_direction = None
         self.markup = ''
@@ -862,10 +1015,11 @@ class TextEvent (Event):
 
 class ArticulationEvent (Event):
     def __init__ (self):
+        Event.__init__ (self)
         self.type = None
         self.force_direction = None
     def wait_for_note (self):
-        return True;
+        return True
 
     def direction_mod (self):
         return { 1: '^', -1: '_', 0: '-' }.get (self.force_direction, '')
@@ -1126,7 +1280,7 @@ class MultiMeasureRest(Music):
   'elements
   (list (make-music (quote BarCheck))
         (make-music
-          'EventChord
+          'ChordEvent
           'elements
           (list (make-music
                   'MultiMeasureRestEvent
@@ -1314,7 +1468,7 @@ def test_pitch ():
 
 def test_printer ():
     def make_note ():
-        evc = EventChord()
+        evc = ChordEvent()
         n = NoteEvent()
         evc.append (n)
         return n
@@ -1344,21 +1498,21 @@ def test_printer ():
 def test_expr ():
     m = SequentialMusic()
     l = 2  
-    evc = EventChord()
+    evc = ChordEvent()
     n = NoteEvent()
     n.duration.duration_log = l
     n.pitch.step = 1
     evc.insert_around (None, n, 0)
     m.insert_around (None, evc, 0)
 
-    evc = EventChord()
+    evc = ChordEvent()
     n = NoteEvent()
     n.duration.duration_log = l
     n.pitch.step = 3
     evc.insert_around (None, n, 0)
     m.insert_around (None, evc, 0)
 
-    evc = EventChord()
+    evc = ChordEvent()
     n = NoteEvent()
     n.duration.duration_log = l
     n.pitch.step = 2 
@@ -1369,7 +1523,7 @@ def test_expr ():
     evc.type = 'treble'
     m.insert_around (None, evc, 0)
 
-    evc = EventChord()
+    evc = ChordEvent()
     tonic = Pitch ()
     tonic.step = 2
     tonic.alteration = -2