]> git.donarmstrong.com Git - lilypond.git/blobdiff - python/musicexp.py
Merge branch 'master' into nested-bookparts
[lilypond.git] / python / musicexp.py
index 07e4f5d7928f42f476ea17dfc3c0ad48238eb2e6..df54f70106c113f3e30681093e043511d3e2e59f 100644 (file)
@@ -2,6 +2,9 @@ import inspect
 import sys
 import string
 import re
+import lilylib as ly
+
+_ = ly._
 
 from rational import Rational
 
@@ -9,6 +12,10 @@ from rational import Rational
 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, "\"", "\\\"")
     if re.match ('.*[\r\n]+.*', retstring):
@@ -145,11 +152,19 @@ class Duration:
                              self.factor.denominator ())
 
 
-    def ly_expression (self, factor = None):
+    def ly_expression (self, factor = None, scheme_mode = False):
         if not factor:
             factor = self.factor
-            
-        str = '%d%s' % (1 << self.duration_log, '.'*self.dots)
+
+        if self.duration_log < 0:
+            if scheme_mode:
+                longer_dict = {-1: "breve", -2: "longa"}
+            else:
+                longer_dict = {-1: "\\breve", -2: "\\longa"}
+            str = longer_dict.get (self.duration_log, "1")
+        else:
+            str = '%d' % (1 << self.duration_log)
+        str += '.'*self.dots
 
         if factor <> Rational (1,1):
             if factor.denominator () <> 1:
@@ -567,7 +582,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):
@@ -575,7 +591,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 {')
@@ -661,6 +677,31 @@ 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 ChordEvent (NestedMusic):
     def __init__ (self):
@@ -678,7 +719,15 @@ class ChordEvent (NestedMusic):
         for e in self.elements:
             l = max(l, e.get_length())
         return l
-    
+
+    def get_duration (self):
+        note_events = [e for e in self.elements if
+               isinstance (e, NoteEvent) or isinstance (e, RestEvent)]
+        if note_events:
+            return note_events[0].duration
+        else:
+            return None
+
     def print_ly (self, printer):
         note_events = [e for e in self.elements if
                isinstance (e, NoteEvent)]
@@ -697,6 +746,10 @@ class ChordEvent (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)
@@ -712,13 +765,18 @@ class ChordEvent (NestedMusic):
                     basepitch = previous_pitch
             printer ('<%s>' % string.join (pitches))
             previous_pitch = basepitch
-            note_events[0].duration.print_ly (printer)
+            duration = self.get_duration ()
+            if duration:
+                duration.print_ly (printer)
         else:
             pass
         
         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):
@@ -755,6 +813,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):
@@ -772,34 +843,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',
-            1:'\\sustainUp'}.get (self.span_direction, '')
+        return {-1: '\\sustainOn',
+            0:'\\sustainOff\\sustainOn',
+            1:'\\sustainOff'}.get (self.span_direction, '')
+
+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 '';
+
 
-# type==-1 means octave up, type==-2 means octave down
 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):
@@ -812,10 +901,10 @@ class OctaveShiftEvent (SpanEvent):
         dir = self.ly_octave_shift_indicator ()
         value = ''
         if dir:
-            value = '#(set-octavation %s)' % dir
+            value = '\ottava #%s' % dir
         return { 
             -1: value,
-            1: '#(set-octavation 0)'}.get (self.span_direction, '')
+            1: '\ottava #0'}.get (self.span_direction, '')
 
 class TrillSpanEvent (SpanEvent):
     def ly_expression (self):
@@ -824,24 +913,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: "\\arpeggioArrowDown", 1: "\\arpeggioArrowUp" }.get (self.direction, '')
+          if dir:
+              printer.dump (dir)
+    def print_after_note (self, printer):
+        if self.non_arpeggiate or self.direction:
+            printer.dump ("\\arpeggioNormal")
     def ly_expression (self):
-        # TODO: Use self.direction for up/down arpeggios
         return ('\\arpeggio')
 
 
@@ -871,22 +973,45 @@ class HairpinEvent (SpanEvent):
 
 class DynamicsEvent (Event):
     def __init__ (self):
+        Event.__init__ (self)
         self.type = None
     def wait_for_note (self):
-        return True;
+        return True
     def ly_expression (self):
         if self.type:
             return '\%s' % self.type
         else:
-            return;
+            return
 
     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:
+            return ''
+
 
 class TextEvent (Event):
     def __init__ (self):
+        Event.__init__ (self)
         self.Text = None
         self.force_direction = None
         self.markup = ''
@@ -904,10 +1029,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, '')
@@ -973,6 +1099,60 @@ class FretEvent (MarkupEvent):
         else:
             return ''
 
+class ChordPitch:
+    def __init__ (self):
+        self.alteration = 0
+        self.step = 0
+    def __repr__(self):
+        return self.ly_expression()
+    def ly_expression (self): 
+        return pitch_generating_function (self)
+
+class ChordModification:
+    def __init__ (self):
+        self.alteration = 0
+        self.step = 0
+        self.type = 0
+    def ly_expression (self):
+        if self.type:
+            val = {1: ".", -1: "^" }.get (self.type, "")
+            val += "%s" % self.step
+            val += {1: "+", -1: "-"}.get (self.alteration, "")
+            return val
+        else:
+            return ''
+
+class ChordNameEvent (Event):
+    def __init__ (self):
+        Event.__init__ (self)
+        self.root = None
+        self.kind = None
+        self.duration = None
+        self.modifications = []
+        self.bass = None
+    def add_modification (self, mod):
+        self.modifications.append (mod)
+    def ly_expression (self):
+        if not self.root:
+            return ''
+        value = self.root.ly_expression ()
+        if self.duration:
+            value += self.duration.ly_expression ()
+        if self.kind:
+            value += ":"
+            value += self.kind
+        # First print all additions/changes, and only afterwards all subtractions
+        for m in self.modifications:
+            if m.type == 1:
+              value += m.ly_expression ()
+        for m in self.modifications:
+            if m.type == -1:
+              value += m.ly_expression ()
+        if self.bass:
+            value += "/+%s" % self.bass.ly_expression ()
+        return value
+
+
 class TremoloEvent (ArticulationEvent):
     def __init__ (self):
         Event.__init__ (self)
@@ -1159,6 +1339,106 @@ class StaffChange (Music):
             return ''
 
 
+class TempoMark (Music):
+    def __init__ (self):
+        Music.__init__ (self)
+        self.baseduration = None
+        self.newduration = None
+        self.beats = None
+        self.parentheses = False
+    def set_base_duration (self, dur):
+        self.baseduration = dur
+    def set_new_duration (self, dur):
+        self.newduration = dur
+    def set_beats_per_minute (self, beats):
+        self.beats = beats
+    def set_parentheses (self, parentheses):
+        self.parentheses = parentheses
+    def wait_for_note (self):
+        return False
+    def duration_to_markup (self, dur):
+        if dur:
+            # Generate the markup to print the note, use scheme mode for 
+            # ly_expression to get longa and not \longa (which causes an error)
+            return "\\general-align #Y #DOWN \\smaller \\note #\"%s\" #UP" % dur.ly_expression(None, True)
+        else:
+            return ''
+    def tempo_markup_template (self):
+        return "\\mark\\markup { \\fontsize #-2 \\line { %s } }"
+    def ly_expression (self):
+        res = ''
+        if not self.baseduration:
+            return res
+        if self.beats:
+            if self.parentheses:
+                res += "\\tempo \"\" %s=%s" % (self.baseduration.ly_expression(), self.beats)
+            else:
+                res += "\\tempo %s=%s" % (self.baseduration.ly_expression(), self.beats)
+        elif self.newduration:
+            dm = self.duration_to_markup (self.baseduration)
+            ndm = self.duration_to_markup (self.newduration)
+            if self.parentheses:
+                contents = "\"(\" %s = %s \")\"" % (dm, ndm)
+            else:
+                contents = " %s = %s " % (dm, ndm)
+            res += self.tempo_markup_template() % contents
+        else:
+            return ''
+        return res
+
+class FiguredBassNote (Music):
+    def __init__ (self):
+        Music.__init__ (self)
+        self.number = ''
+        self.prefix = ''
+        self.suffix = ''
+    def set_prefix (self, prefix):
+        self.prefix = prefix
+    def set_suffix (self, suffix):
+        self.prefix = suffix
+    def set_number (self, number):
+        self.number = number
+    def ly_expression (self):
+        res = ''
+        if self.number:
+            res += self.number
+        else:
+            res += '_'
+        if self.prefix:
+            res += self.prefix
+        if self.suffix:
+            res += self.suffix
+        return res
+
+
+class FiguredBassEvent (NestedMusic):
+    def __init__ (self):
+        NestedMusic.__init__ (self)
+        self.duration = None
+        self.real_duration = 0
+        self.parentheses = False
+        return
+    def set_duration (self, dur):
+        self.duration = dur
+    def set_parentheses (self, par):
+        self.parentheses = par
+    def set_real_duration (self, dur):
+        self.real_duration = dur
+
+    def print_ly (self, printer):
+        figured_bass_events = [e for e in self.elements if
+               isinstance (e, FiguredBassNote)]
+        if figured_bass_events:
+          notes = []
+          for x in figured_bass_events:
+              notes.append (x.ly_expression ())
+          contents = string.join (notes)
+          if self.parentheses:
+              contents = '[%s]' % contents
+          printer ('<%s>' % contents)
+          self.duration.print_ly (printer)
+
+
 class MultiMeasureRest(Music):
 
     def lisp_expression (self):
@@ -1269,6 +1549,12 @@ class Staff (StaffGroup):
             sub_staff_type = self.stafftype
 
         for [staff_id, voices] in self.part_information:
+            # Chord names need to come before the staff itself!
+            for [v, lyrics, figuredbass, chordnames] in voices:
+                if chordnames:
+                    printer ('\context ChordNames = "%s" \\%s' % (chordnames, chordnames))
+
+            # now comes the real staff definition:
             if staff_id:
                 printer ('\\context %s = "%s" << ' % (sub_staff_type, staff_id))
             else:
@@ -1276,7 +1562,7 @@ class Staff (StaffGroup):
             printer.newline ()
             n = 0
             nr_voices = len (voices)
-            for [v, lyrics] in voices:
+            for [v, lyrics, figuredbass, chordnames] in voices:
                 n += 1
                 voice_count_text = ''
                 if nr_voices > 1:
@@ -1288,6 +1574,8 @@ class Staff (StaffGroup):
                 for l in lyrics:
                     printer ('\\new Lyrics \\lyricsto "%s" \\%s' % (v,l))
                     printer.newline()
+                if figuredbass:
+                    printer ('\context FiguredBass = "%s" \\%s' % (figuredbass, figuredbass))
             printer ('>>')
 
     def print_ly (self, printer):
@@ -1329,6 +1617,34 @@ class DrumStaff (Staff):
 class RhythmicStaff (Staff):
     def __init__ (self, command = "RhythmicStaff"):
         Staff.__init__ (self, command)
+        
+class Score:
+    def __init__ (self):
+        self.contents = None
+        self.create_midi = False
+
+    def set_contents (self, contents):
+        self.contents = contents
+    
+    def set_part_information (self, part_id, staves_info):
+        if self.contents:
+          self.contents.set_part_information (part_id, staves_info)
+
+    def print_ly (self, printer):
+        printer.dump ("\\score {");
+        printer.newline ()
+        if self.contents:
+            self.contents.print_ly (printer);
+        printer.dump ("\\layout {}");
+        printer.newline ()
+        if not self.create_midi:
+            printer.dump ("% To create MIDI output, uncomment the following line:");
+            printer.newline ();
+            printer.dump ("% ");
+        printer.dump ("\\midi {}");
+        printer.newline ()
+        printer.dump ("}");
+        printer.newline ()
 
 
 def test_pitch ():