]> git.donarmstrong.com Git - lilypond.git/blobdiff - python/musicexp.py
Typos: Lilypond -> LilyPond, explicite -> explicit, fix docstring
[lilypond.git] / python / musicexp.py
index e73f78b1a4d92fb457792eb237a2341dab8b594a..9ebdb70a0005d8e93d403a0d7ba8568fa470a1b5 100644 (file)
@@ -43,7 +43,6 @@ class Output_printer:
     Music expression as a .ly file.
     
     """
-    ## TODO: support for \relative.
     
     def __init__ (self):
         self._line = ''
@@ -205,10 +204,20 @@ class Duration:
 # Implement the different note names for the various languages
 def pitch_generic (pitch, notenames, accidentals):
     str = notenames[pitch.step]
-    if pitch.alteration < 0:
-        str += accidentals[0] * (-pitch.alteration)
+    halftones = int (pitch.alteration)
+    if halftones < 0:
+        str += accidentals[0] * (-halftones)
     elif pitch.alteration > 0:
-        str += accidentals[3] * (pitch.alteration)
+        str += accidentals[3] * (halftones)
+    # Handle remaining fraction to pitch.alteration (for microtones)
+    if (halftones != pitch.alteration):
+        if None in accidentals[1:3]:
+            warning (_ ("Language does not support microtones contained in the piece"))
+        else:
+            try:
+                str += {-0.5: accidentals[1], 0.5: accidentals[2]}[pitch.alteration-halftones]
+            except KeyError:
+                warning (_ ("Language does not support microtones contained in the piece"))
     return str
 
 def pitch_general (pitch):
@@ -230,7 +239,7 @@ def pitch_norsk (pitch):
     return pitch_deutsch (pitch)
 
 def pitch_svenska (pitch):
-    str = pitch_generic (pitch, ['c', 'd', 'e', 'f', 'g', 'a', 'h'], ['ess', '', '', 'iss'])
+    str = pitch_generic (pitch, ['c', 'd', 'e', 'f', 'g', 'a', 'h'], ['ess', None, None, 'iss'])
     return str.replace ('hess', 'b').replace ('aes', 'as').replace ('ees', 'es')
 
 def pitch_italiano (pitch):
@@ -241,11 +250,11 @@ def pitch_catalan (pitch):
     return pitch_italiano (pitch)
 
 def pitch_espanol (pitch):
-    str = pitch_generic (pitch, ['do', 're', 'mi', 'fa', 'sol', 'la', 'si'], ['b', '', '', 's'])
+    str = pitch_generic (pitch, ['do', 're', 'mi', 'fa', 'sol', 'la', 'si'], ['b', None, None, 's'])
     return str
 
 def pitch_vlaams (pitch):
-    str = pitch_generic (pitch, ['do', 're', 'mi', 'fa', 'sol', 'la', 'si'], ['b', '', '', 'k'])
+    str = pitch_generic (pitch, ['do', 're', 'mi', 'fa', 'sol', 'la', 'si'], ['b', None, None, 'k'])
     return str
 
 def set_pitch_language (language):
@@ -271,6 +280,7 @@ class Pitch:
         self.alteration = 0
         self.step = 0
         self.octave = 0
+        self._force_absolute_pitch = False
         
     def __repr__(self):
         return self.ly_expression()
@@ -340,7 +350,7 @@ class Pitch:
 
     def ly_expression (self):
         str = self.ly_step_expression ()
-        if relative_pitches:
+        if relative_pitches and not self._force_absolute_pitch:
             str += self.relative_pitch ()
         else:
             str += self.absolute_pitch ()
@@ -451,7 +461,80 @@ class RelativeMusic (MusicWrapper):
         relative_pitches = prev_relative_pitches
 
 class TimeScaledMusic (MusicWrapper):
+    def __init__ (self):
+        MusicWrapper.__init__ (self)
+        self.numerator = 1
+        self.denominator = 1
+        self.display_number = "actual" # valid values "actual" | "both" | None
+        # Display the basic note length for the tuplet:
+        self.display_type = None       # value values "actual" | "both" | None
+        self.display_bracket = "bracket" # valid values "bracket" | "curved" | None
+        self.actual_type = None   # The actually played unit of the scaling
+        self.normal_type = None   # The basic unit of the scaling
+        self.display_numerator = None
+        self.display_denominator = None
+
     def print_ly (self, func):
+        if self.display_bracket == None:
+            func ("\\once \\override TupletBracket #'stencil = ##f")
+            func.newline ()
+        elif self.display_bracket == "curved":
+            warning (_ ("Tuplet brackets of curved shape are not correctly implemented"))
+            func ("\\once \\override TupletBracket #'stencil = #ly:slur::print")
+            func.newline ()
+
+        base_number_function = {None: "#f", 
+             "actual": "tuplet-number::calc-denominator-text", 
+             "both": "tuplet-number::calc-fraction-text"}.get (self.display_number, None)
+        # If we have non-standard numerator/denominator, use our custom function
+        if self.display_number == "actual" and self.display_denominator:
+            base_number_function = "(tuplet-number::non-default-tuplet-denominator-text %s)" % self.display_denominator
+        elif self.display_number == "both" and (self.display_denominator or self.display_numerator):
+            if self.display_numerator:
+                num = self.display_numerator
+            else:
+                num = "#f"
+            if self.display_denominator:
+                den = self.display_denominator
+            else:
+                den = "#f"
+            base_number_function = "(tuplet-number::non-default-tuplet-fraction-text %s %s)" % (den, num)
+
+
+        if self.display_type == "actual" and self.normal_type:
+            # Obtain the note duration in scheme-mode, i.e. \longa as \\longa
+            base_duration = self.normal_type.ly_expression (None, True)
+            func ("\\once \\override TupletNumber #'text = #(tuplet-number::append-note-wrapper %s \"%s\")" %
+                (base_number_function, base_duration))
+            func.newline ()
+        elif self.display_type == "both": # TODO: Implement this using actual_type and normal_type!
+            if self.display_number == None:
+                func ("\\once \\override TupletNumber #'stencil = ##f")
+                func.newline ()
+            elif self.display_number == "both":
+                den_duration = self.normal_type.ly_expression (None, True)
+                # If we don't have an actual type set, use the normal duration!
+                if self.actual_type:
+                    num_duration = self.actual_type.ly_expression (None, True)
+                else:
+                    num_duration = den_duration
+                if (self.display_denominator or self.display_numerator):
+                    func ("\\once \\override TupletNumber #'text = #(tuplet-number::non-default-fraction-with-notes %s \"%s\" %s \"%s\")" % 
+                                (self.display_denominator, den_duration,
+                                 self.display_numerator, num_duration))
+                    func.newline ()
+                else:
+                    func ("\\once \\override TupletNumber #'text = #(tuplet-number::fraction-with-notes \"%s\" \"%s\")" % 
+                                (den_duration, num_duration))
+                    func.newline ()
+        else:
+            if self.display_number == None:
+                func ("\\once \\override TupletNumber #'stencil = ##f")
+                func.newline ()
+            elif self.display_number == "both":
+                func ("\\once \\override TupletNumber #'text = #%s" % base_number_function)
+                func.newline ()
+
         func ('\\times %d/%d ' %
            (self.numerator, self.denominator))
         func.add_factor (Rational (self.numerator, self.denominator))
@@ -582,8 +665,8 @@ class RepeatedMusic:
             self.music = SequentialMusic ()
             self.music.elements = music
         else:
-            warning (_ ("unable to set the music %(music)s for the repeat %(repeat)s" % \
-                            {'music':music, 'repeat':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):
@@ -670,7 +753,7 @@ class Paper:
         # TODO: maybe set line-width instead of right-margin?
         self.print_length_field (printer, "right-margin", self.right_margin)
         # TODO: What's the corresponding setting for system_left_margin and
-        #        system_right_margin in Lilypond?
+        #        system_right_margin in LilyPond?
         self.print_length_field (printer, "between-system-space", self.system_distance)
         self.print_length_field (printer, "page-top-space", self.top_system_distance)
 
@@ -706,6 +789,7 @@ class Layout:
 class ChordEvent (NestedMusic):
     def __init__ (self):
         NestedMusic.__init__ (self)
+        self.after_grace_elements = None
         self.grace_elements = None
         self.grace_type = None
     def append_grace (self, element):
@@ -713,6 +797,16 @@ class ChordEvent (NestedMusic):
             if not self.grace_elements:
                 self.grace_elements = SequentialMusic ()
             self.grace_elements.append (element)
+    def append_after_grace (self, element):
+        if element:
+            if not self.after_grace_elements:
+                self.after_grace_elements = SequentialMusic ()
+            self.after_grace_elements.append (element)
+
+    def has_elements (self):
+        return [e for e in self.elements if
+               isinstance (e, NoteEvent) or isinstance (e, RestEvent)] != []
+
 
     def get_length (self):
         l = Rational (0)
@@ -739,6 +833,9 @@ class ChordEvent (NestedMusic):
         other_events = [e for e in self.elements if
                 not isinstance (e, RhythmicEvent)]
 
+        if self.after_grace_elements:
+            printer ('\\afterGrace {')
+
         if self.grace_elements and self.elements:
             if self.grace_type:
                 printer ('\\%s' % self.grace_type)
@@ -746,6 +843,15 @@ class ChordEvent (NestedMusic):
                 printer ('\\grace')
             # don't print newlines after the { and } braces
             self.grace_elements.print_ly (printer, False)
+        elif self.grace_elements: # no self.elements!
+            warning (_ ("Grace note with no following music: %s") % self.grace_elements)
+            if self.grace_type:
+                printer ('\\%s' % self.grace_type)
+            else:
+                printer ('\\grace')
+            self.grace_elements.print_ly (printer, False)
+            printer ('{}')
+
         # Print all overrides and other settings needed by the 
         # articulations/ornaments before the note
         for e in other_events:
@@ -760,7 +866,7 @@ class ChordEvent (NestedMusic):
             pitches = []
             basepitch = None
             for x in note_events:
-                pitches.append (x.pitch.ly_expression ())
+                pitches.append (x.chord_element_ly ())
                 if not basepitch:
                     basepitch = previous_pitch
             printer ('<%s>' % string.join (pitches))
@@ -777,6 +883,10 @@ class ChordEvent (NestedMusic):
         for e in other_events:
             e.print_after_note (printer)
 
+        if self.after_grace_elements:
+            printer ('}')
+            self.after_grace_elements.print_ly (printer, False)
+
         self.print_comment (printer)
             
 class Partial (Music):
@@ -794,10 +904,10 @@ class BarLine (Music):
         self.type = None
         
     def print_ly (self, printer):
-        bar_symbol = { 'regular': "|", 'dotted': ":", 'dashed': ":",
+        bar_symbol = { 'regular': "|", 'dotted': ":", 'dashed': "dashed",
                        'heavy': "|", 'light-light': "||", 'light-heavy': "|.",
                        'heavy-light': ".|", 'heavy-heavy': ".|.", 'tick': "'",
-                       'short': "'", 'none': "" }.get (self.type, None)
+                       'short': "'|", 'none': "" }.get (self.type, None)
         if bar_symbol <> None:
             printer.dump ('\\bar "%s"' % bar_symbol)
         else:
@@ -805,7 +915,7 @@ class BarLine (Music):
 
         if self.bar_number > 0 and (self.bar_number % 10) == 0:
             printer.dump ("\\barNumberCheck #%d " % self.bar_number)
-        else:
+        elif self.bar_number > 0:
             printer.print_verbatim (' %% %d' % self.bar_number)
         printer.newline ()
 
@@ -863,9 +973,9 @@ class BeamEvent (SpanEvent):
 
 class PedalEvent (SpanEvent):
     def ly_expression (self):
-        return {-1: '\\sustainDown',
-            0:'\\sustainUp\\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):
@@ -893,7 +1003,11 @@ class OctaveShiftEvent (SpanEvent):
         self.span_type = {'up': 1, 'down': -1}.get (type, 0)
     def ly_octave_shift_indicator (self):
         # convert 8/15 to lilypond indicators (+-1/+-2)
-        value = {8: 1, 15: 2}.get (self.size, 0)
+        try:
+            value = {8: 1, 15: 2}[self.size]
+        except KeyError:
+            warning (_ ("Invalid octave shift size found: %s. Using no shift.") % self.size)
+            value = 0
         # negative values go up!
         value *= -1*self.span_type
         return value
@@ -901,10 +1015,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):
@@ -937,12 +1051,12 @@ class ArpeggioEvent(Event):
         if self.non_arpeggiate:
             printer.dump ("\\arpeggioBracket")
         else:
-          dir = { -1: "\\arpeggioDown", 1: "\\arpeggioUp" }.get (self.direction, '')
+          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 ("\\arpeggioNeutral")
+            printer.dump ("\\arpeggioNormal")
     def ly_expression (self):
         return ('\\arpeggio')
 
@@ -1099,6 +1213,47 @@ class FretEvent (MarkupEvent):
         else:
             return ''
 
+
+class FunctionWrapperEvent (Event):
+    def __init__ (self, function_name = None):
+        Event.__init__ (self)
+        self.function_name = function_name
+    def pre_note_ly (self, is_chord_element):
+        if self.function_name:
+            return "\\%s" % self.function_name
+        else:
+            return ''
+    def pre_chord_ly (self):
+        return ''
+    def ly_expression (self):
+        if self.function_name:
+            return "\\%s" % self.function_name
+        else:
+            return ''
+
+class ParenthesizeEvent (FunctionWrapperEvent):
+    def __init__ (self):
+        FunctionWrapperEvent.__init__ (self, "parenthesize")
+
+class NotestyleEvent (Event):
+    def __init__ (self):
+        Event.__init__ (self)
+        self.style = None
+        self.filled = None
+    def pre_chord_ly (self):
+        if self.style:
+            return "\\once \\override NoteHead #'style = #%s" % self.style
+        else:
+            return ''
+    def pre_note_ly (self, is_chord_element):
+        if self.style and is_chord_element:
+            return "\\tweak #'style #%s" % self.style
+        else:
+            return ''
+    def ly_expression (self):
+        return self.pre_chord_ly ()
+
+
 class ChordPitch:
     def __init__ (self):
         self.alteration = 0
@@ -1167,9 +1322,9 @@ class TremoloEvent (ArticulationEvent):
 class BendEvent (ArticulationEvent):
     def __init__ (self):
         Event.__init__ (self)
-        self.alter = 0
+        self.alter = None
     def ly_expression (self):
-        if self.alter:
+        if self.alter != None:
             return "-\\bendAfter #%s" % self.alter
         else:
             return ''
@@ -1178,7 +1333,24 @@ class RhythmicEvent(Event):
     def __init__ (self):
         Event.__init__ (self)
         self.duration = Duration()
-        
+        self.associated_events = []
+
+    def add_associated_event (self, ev):
+        if ev:
+            self.associated_events.append (ev)
+
+    def pre_chord_ly (self):
+        return [ev.pre_chord_ly () for ev in self.associated_events]
+
+    def pre_note_ly (self, is_chord_element):
+        return [ev.pre_note_ly (is_chord_element) for ev in self.associated_events]
+
+    def ly_expression_pre_note (self, is_chord_element):
+        res = string.join (self.pre_note_ly (is_chord_element), ' ')
+        if res != '':
+            res = res + ' '
+        return res
+
     def get_length (self):
         return self.duration.get_length()
         
@@ -1190,13 +1362,17 @@ class RestEvent (RhythmicEvent):
     def __init__ (self):
         RhythmicEvent.__init__ (self)
         self.pitch = None
+
     def ly_expression (self):
+        res = self.ly_expression_pre_note (False)
         if self.pitch:
-            return "%s%s\\rest" % (self.pitch.ly_expression (), self.duration.ly_expression ())
+            return res + "%s%s\\rest" % (self.pitch.ly_expression (), self.duration.ly_expression ())
         else:
             return 'r%s' % self.duration.ly_expression ()
     
     def print_ly (self, printer):
+        for ev in self.associated_events:
+            ev.print_ly (printer)
         if self.pitch:
             self.pitch.print_ly (printer)
             self.duration.print_ly (printer)
@@ -1216,7 +1392,7 @@ class NoteEvent(RhythmicEvent):
         self.drum_type = None
         self.cautionary = False
         self.forced_accidental = False
-        
+
     def get_properties (self):
         str = RhythmicEvent.get_properties (self)
         
@@ -1235,17 +1411,31 @@ class NoteEvent(RhythmicEvent):
             excl_question += '!'
 
         return excl_question
-    
+
     def ly_expression (self):
+        # obtain all stuff that needs to be printed before the note:
+        res = self.ly_expression_pre_note (True)
         if self.pitch:
-            return '%s%s%s' % (self.pitch.ly_expression (),
+            return res + '%s%s%s' % (self.pitch.ly_expression (),
                                self.pitch_mods(),
                                self.duration.ly_expression ())
         elif self.drum_type:
-            return '%s%s' (self.drum_type,
+            return res + '%s%s' (self.drum_type,
                            self.duration.ly_expression ())
 
+    def chord_element_ly (self):
+        # obtain all stuff that needs to be printed before the note:
+        res = self.ly_expression_pre_note (True)
+        if self.pitch:
+            return res + '%s%s' % (self.pitch.ly_expression (),
+                               self.pitch_mods())
+        elif self.drum_type:
+            return res + '%s%s' (self.drum_type)
+
+
     def print_ly (self, printer):
+        for ev in self.associated_events:
+            ev.print_ly (printer)
         if self.pitch:
             self.pitch.print_ly (printer)
             printer (self.pitch_mods ())
@@ -1257,27 +1447,76 @@ class NoteEvent(RhythmicEvent):
 class KeySignatureChange (Music):
     def __init__ (self):
         Music.__init__ (self)
-        self.scale = []
-        self.tonic = Pitch()
+        self.tonic = None
         self.mode = 'major'
-        
+        self.non_standard_alterations = None
+
+    def format_non_standard_alteration (self, a):
+        alter_dict = { -2:   ",DOUBLE-FLAT",
+                       -1.5: ",THREE-Q-FLAT",
+                       -1:   ",FLAT",
+                       -0.5: ",SEMI-FLAT",
+                        0:   ",NATURAL",
+                        0.5: ",SEMI-SHARP",
+                        1:   ",SHARP",
+                        1.5: ",THREE-Q-SHARP",
+                        2:   ",DOUBLE-SHARP"}
+        try:
+            accidental = alter_dict[a[1]]
+        except KeyError:
+            warning (_ ("Unable to convert alteration %s to a lilypond expression") % a[1])
+            return ''
+        if len (a) == 2:
+            return "( %s . %s )" % (a[0], accidental)
+        elif len (a) == 3:
+            return "(( %s . %s ) . %s )" % (a[2], a[0], accidental)
+        else:
+            return ''
+
     def ly_expression (self):
-        return '\\key %s \\%s' % (self.tonic.ly_step_expression (),
+        if self.tonic:
+            return '\\key %s \\%s' % (self.tonic.ly_step_expression (),
                      self.mode)
-    
-    def lisp_expression (self):
-        pairs = ['(%d . %d)' % (i , self.scale[i]) for i in range (0,7)]
-        scale_str = ("'(%s)" % string.join (pairs))
-
-        return """ (make-music 'KeyChangeEvent
-     'pitch-alist %s) """ % scale_str
+        elif self.non_standard_alterations:
+            alterations = [self.format_non_standard_alteration (a) for
+                                        a in self.non_standard_alterations]
+            return "\\set Staff.keySignature = #`(%s)" % string.join (alterations, " ")
+        else:
+            return ''
 
 class TimeSignatureChange (Music):
     def __init__ (self):
         Music.__init__ (self)
-        self.fraction = (4,4)
+        self.fractions = [4,4]
+        self.style = None
+    def format_fraction (self, frac):
+        if isinstance (frac, list):
+            l = [self.format_fraction (f) for f in frac]
+            return "(" + string.join (l, " ") + ")"
+        else:
+            return "%s" % frac
+
     def ly_expression (self):
-        return '\\time %d/%d ' % self.fraction
+        st = ''
+        # Print out the style if we have ome, but the '() should only be 
+        # forced for 2/2 or 4/4, since in all other cases we'll get numeric 
+        # signatures anyway despite the default 'C signature style!
+        is_common_signature = self.fractions in ([2,2], [4,4], [4,2])
+        if self.style:
+            if self.style == "common":
+                st = "\\defaultTimeSignature"
+            elif (self.style != "'()"):
+                st = "\\once \\override Staff.TimeSignature #'style = #%s " % self.style
+            elif (self.style != "'()") or is_common_signature:
+                st = "\\numericTimeSignature"
+
+        # Easy case: self.fractions = [n,d] => normal \time n/d call:
+        if len (self.fractions) == 2 and isinstance (self.fractions[0], int):
+            return st + '\\time %d/%d ' % tuple (self.fractions)
+        elif self.fractions:
+            return st + "\\compoundMeter #'%s" % self.format_fraction (self.fractions)
+        else:
+            return st + ''
     
 class ClefChange (Music):
     def __init__ (self):
@@ -1300,6 +1539,8 @@ class ClefChange (Music):
                 ('F', 4): "bass",
                 ('F', 5): "subbass",
                 ("percussion", 2): "percussion",
+                # Workaround: MuseScore uses PERC instead of percussion
+                ("PERC", 2): "percussion",
                 ("TAB", 5): "tab"}.get ((self.type, self.position), None)
     def ly_expression (self):
         return '\\clef "%s%s"' % (self.clef_name (), self.octave_modifier ())
@@ -1327,6 +1568,13 @@ class ClefChange (Music):
 """ % (glyph, pos, c0)
         return clefsetting
 
+class Transposition (Music):
+    def __init__ (self):
+        Music.__init__ (self)
+        self.pitch = None
+    def ly_expression (self):
+        self.pitch._force_absolute_pitch = True
+        return '\\transposition %s' % self.pitch.ly_expression ()
 
 class StaffChange (Music):
     def __init__ (self, staff):
@@ -1461,6 +1709,14 @@ class MultiMeasureRest(Music):
         return 'R%s' % self.duration.ly_expression ()
 
 
+class Break (Music):
+    def __init__ (self, tp="break"):
+        Music.__init__ (self)
+        self.type = tp
+    def print_ly (self, printer):
+        if self.type:
+            printer.dump ("\\%s" % self.type)
+
 class StaffGroup:
     def __init__ (self, command = "StaffGroup"):
         self.stafftype = command
@@ -1617,6 +1873,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 ():