]> git.donarmstrong.com Git - lilypond.git/blobdiff - python/musicexp.py
Merge branch 'master' of ssh://kainhofer@git.sv.gnu.org/srv/git/lilypond into kainhofer
[lilypond.git] / python / musicexp.py
index ef896f9e53752ec80ab325e8966225865dc6ae23..8fb4e5c31976c972e3544b182b15ccb14104b2a6 100644 (file)
@@ -5,11 +5,15 @@ import re
 
 from rational import Rational
 
+# Store previously converted pitch for \relative conversion as a global state variable
+previous_pitch = None
+relative_pitches = False
 
 def escape_instrument_string (input_string):
     retstring = string.replace (input_string, "\"", "\\\"")
-    if re.match ('.*\n.*', retstring):
-        strings = retstring.split ('\r\n')
+    if re.match ('.*[\r\n]+.*', retstring):
+        rx = re.compile (r'[\n\r]+')
+        strings = rx.split (retstring)
         retstring = "\\markup { \\column { "
         for s in strings:
             retstring += "\\line {\"" + s + "\"} "
@@ -126,8 +130,8 @@ class Output_printer:
         self.newline ()
         self._file.close ()
         self._file = None
-        
-        
+
+
 class Duration:
     def __init__ (self):
         self.duration_log = 0
@@ -182,7 +186,71 @@ class Duration:
 
         return base * dot_fact * self.factor
 
-    
+
+# 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)
+    elif pitch.alteration > 0:
+        str += accidentals[3] * (pitch.alteration)
+    return str
+
+def pitch_general (pitch):
+    str = pitch_generic (pitch, ['c', 'd', 'e', 'f', 'g', 'a', 'b'], ['es', 'eh', 'ih', 'is'])
+    return str.replace ('aes', 'as').replace ('ees', 'es')
+
+def pitch_nederlands (pitch):
+    return pitch_general (pitch)
+
+def pitch_english (pitch):
+    str = pitch_generic (pitch, ['c', 'd', 'e', 'f', 'g', 'a', 'b'], ['f', 'qf', 'qs', 's'])
+    return str.replace ('aes', 'as').replace ('ees', 'es')
+
+def pitch_deutsch (pitch):
+    str = pitch_generic (pitch, ['c', 'd', 'e', 'f', 'g', 'a', 'h'], ['es', 'eh', 'ih', 'is'])
+    return str.replace ('hes', 'b').replace ('aes', 'as').replace ('ees', 'es')
+
+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'])
+    return str.replace ('hess', 'b').replace ('aes', 'as').replace ('ees', 'es')
+
+def pitch_italiano (pitch):
+    str = pitch_generic (pitch, ['do', 're', 'mi', 'fa', 'sol', 'la', 'si'], ['b', 'sb', 'sd', 'd'])
+    return str
+
+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'])
+    return str
+
+def pitch_vlaams (pitch):
+    str = pitch_generic (pitch, ['do', 're', 'mi', 'fa', 'sol', 'la', 'si'], ['b', '', '', 'k'])
+    return str
+
+def set_pitch_language (language):
+    global pitch_generating_function
+    function_dict = {
+        "nederlands": pitch_nederlands,
+        "english": pitch_english,
+        "deutsch": pitch_deutsch,
+        "norsk": pitch_norsk,
+        "svenska": pitch_svenska,
+        "italiano": pitch_italiano,
+        "catalan": pitch_catalan,
+        "espanol": pitch_espanol,
+        "vlaams": pitch_vlaams}
+    pitch_generating_function = function_dict.get (language, pitch_general)
+
+# global variable to hold the formatting function.
+pitch_generating_function = pitch_general
+
+
 class Pitch:
     def __init__ (self):
         self.alteration = 0
@@ -229,21 +297,39 @@ class Pitch:
         return self.octave * 12 + [0,2,4,5,7,9,11][self.step] + self.alteration
     
     def ly_step_expression (self): 
-        str = 'cdefgab'[self.step]
-        if self.alteration > 0:
-            str += 'is'* (self.alteration)
-        elif self.alteration < 0:
-            str += 'es'* (-self.alteration)
+        return pitch_generating_function (self)
 
-        return str.replace ('aes', 'as').replace ('ees', 'es')
-    
-    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):
@@ -331,6 +417,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 ' %
@@ -516,6 +620,46 @@ class Header:
         printer.newline ()
 
 
+class Paper:
+    def __init__ (self):
+        self.global_staff_size = -1
+        # page size
+        self.page_width = -1
+        self.page_height = -1
+        # page margins
+        self.top_margin = -1
+        self.bottom_margin = -1
+        self.left_margin = -1
+        self.right_margin = -1
+        self.system_left_margin = -1
+        self.system_right_margin = -1
+        self.system_distance = -1
+        self.top_system_distance = -1
+
+    def print_length_field (self, printer, field, value):
+        if value >= 0:
+            printer.dump ("%s = %s\\cm" % (field, value))
+            printer.newline ()
+    def print_ly (self, printer):
+        if self.global_staff_size > 0:
+            printer.dump ('#(set-global-staff-size %s)' % self.global_staff_size)
+            printer.newline ()
+        printer.dump ('\\paper {')
+        printer.newline ()
+        self.print_length_field (printer, "paper-width", self.page_width)
+        self.print_length_field (printer, "paper-height", self.page_height)
+        self.print_length_field (printer, "top-margin", self.top_margin)
+        self.print_length_field (printer, "botton-margin", self.bottom_margin)
+        self.print_length_field (printer, "left-margin", self.left_margin)
+        # 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?
+        self.print_length_field (printer, "between-system-space", self.system_distance)
+        self.print_length_field (printer, "page-top-space", self.top_system_distance)
+
+        printer.dump ('}')
+        printer.newline ()
 
 
 class EventChord (NestedMusic):
@@ -559,8 +703,15 @@ class EventChord (NestedMusic):
         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
@@ -684,9 +835,13 @@ class GlissandoEvent (SpanEvent):
             1:''}.get (self.span_direction, '')
 
 class ArpeggioEvent(Event):
+    def __init__ (self):
+        Event.__init__ (self)
+        self.direction = 0
     def wait_for_note (self):
         return True;
     def ly_expression (self):
+        # TODO: Use self.direction for up/down arpeggios
         return ('\\arpeggio')
 
 
@@ -775,11 +930,17 @@ class ShortArticulationEvent (ArticulationEvent):
         # default is -
         return { 1: '^', -1: '_', 0: '-' }.get (self.force_direction, '-')
     def ly_expression (self):
-        return '%s%s' % (self.direction_mod (), self.type)
+        if self.type:
+            return '%s%s' % (self.direction_mod (), self.type)
+        else:
+            return ''
 
 class NoDirectionArticulationEvent (ArticulationEvent):
     def ly_expression (self):
-        return '\\%s' % self.type
+        if self.type:
+            return '\\%s' % self.type
+        else:
+            return ''
 
 class MarkupEvent (ShortArticulationEvent):
     def __init__ (self):
@@ -997,6 +1158,17 @@ class ClefChange (Music):
         return clefsetting
 
 
+class StaffChange (Music):
+    def __init__ (self, staff):
+        Music.__init__ (self)
+        self.staff = staff
+    def ly_expression (self):
+        if self.staff:
+            return "\\change Staff=\"%s\"" % self.staff
+        else:
+            return ''
+
+
 class MultiMeasureRest(Music):
 
     def lisp_expression (self):
@@ -1028,21 +1200,22 @@ class StaffGroup:
         self.symbol = None
         self.spanbar = None
         self.children = []
+        self.is_group = True
         # part_information is a list with entries of the form
         #     [staffid, voicelist]
         # where voicelist is a list with entries of the form
         #     [voiceid1, [lyricsid11, lyricsid12,...] ]
         self.part_information = None
 
-    def appendStaff (self, staff):
+    def append_staff (self, staff):
         self.children.append (staff)
 
-    def setPartInformation (self, part_name, staves_info):
+    def set_part_information (self, part_name, staves_info):
         if part_name == self.id:
             self.part_information = staves_info
         else:
             for c in self.children:
-                c.setPartInformation (part_name, staves_info)
+                c.set_part_information (part_name, staves_info)
 
     def print_ly_contents (self, printer):
         for c in self.children:
@@ -1062,7 +1235,7 @@ class StaffGroup:
                 printer.dump ("\\override SpanBar #'transparent = ##t")
             brack = {"brace": "SystemStartBrace",
                      "none": "f",
-                     "line": "SystemStartBar"}.get (self.symbol, None)
+                     "line": "SystemStartSquare"}.get (self.symbol, None)
             if brack:
                 printer.dump ("systemStartDelimiter = #'%s" % brack)
             printer.dump ("}")
@@ -1078,7 +1251,7 @@ class StaffGroup:
                     escape_instrument_string (self.instrument_name)))
             printer.newline ()
         if self.stafftype and self.short_instrument_name:
-            printer.dump ("\\set %s.shortInstrumentName = %s\n" % (self.stafftype, 
+            printer.dump ("\\set %s.shortInstrumentName = %s" % (self.stafftype,
                     escape_instrument_string (self.short_instrument_name)))
             printer.newline ()
         self.print_ly_contents (printer)
@@ -1088,9 +1261,12 @@ class StaffGroup:
 
 
 class Staff (StaffGroup):
-    def __init__ (self):
-        StaffGroup.__init__ (self, "Staff")
+    def __init__ (self, command = "Staff"):
+        StaffGroup.__init__ (self, command)
+        self.is_group = False
         self.part = None
+        self.voice_command = "Voice"
+        self.substafftype = None
 
     def print_ly_overrides (self, printer):
         pass
@@ -1098,12 +1274,15 @@ class Staff (StaffGroup):
     def print_ly_contents (self, printer):
         if not self.id or not self.part_information:
             return
+        sub_staff_type = self.substafftype
+        if not sub_staff_type:
+            sub_staff_type = self.stafftype
 
         for [staff_id, voices] in self.part_information:
             if staff_id:
-                printer ('\\context Staff = "%s" << ' % staff_id)
+                printer ('\\context %s = "%s" << ' % (sub_staff_type, staff_id))
             else:
-                printer ('\\context Staff << ')
+                printer ('\\context %s << ' % sub_staff_type)
             printer.newline ()
             n = 0
             nr_voices = len (voices)
@@ -1113,7 +1292,7 @@ class Staff (StaffGroup):
                 if nr_voices > 1:
                     voice_count_text = {1: ' \\voiceOne', 2: ' \\voiceTwo',
                                         3: ' \\voiceThree'}.get (n, ' \\voiceFour')
-                printer ('\\context Voice = "%s" {%s \\%s }' % (v,voice_count_text,v))
+                printer ('\\context %s = "%s" {%s \\%s }' % (self.voice_command, v, voice_count_text, v))
                 printer.newline ()
 
                 for l in lyrics:
@@ -1124,8 +1303,43 @@ class Staff (StaffGroup):
     def print_ly (self, printer):
         if self.part_information and len (self.part_information) > 1:
             self.stafftype = "PianoStaff"
+            self.substafftype = "Staff"
         StaffGroup.print_ly (self, printer)
 
+class TabStaff (Staff):
+    def __init__ (self, command = "TabStaff"):
+        Staff.__init__ (self, command)
+        self.string_tunings = []
+        self.tablature_format = None
+        self.voice_command = "TabVoice"
+    def print_ly_overrides (self, printer):
+        if self.string_tunings or self.tablature_format:
+            printer.dump ("\\with {")
+            if self.string_tunings:
+                printer.dump ("stringTunings = #'(")
+                for i in self.string_tunings:
+                    printer.dump ("%s" % i.semitones ())
+                printer.dump (")")
+            if self.tablature_format:
+                printer.dump ("tablatureFormat = #%s" % self.tablature_format)
+            printer.dump ("}")
+
+
+class DrumStaff (Staff):
+    def __init__ (self, command = "DrumStaff"):
+        Staff.__init__ (self, command)
+        self.drum_style_table = None
+        self.voice_command = "DrumVoice"
+    def print_ly_overrides (self, printer):
+        if self.drum_style_table:
+            printer.dump ("\with {")
+            printer.dump ("drumStyleTable = #%s" % self.drum_style_table)
+            printer.dump ("}")
+
+class RhythmicStaff (Staff):
+    def __init__ (self, command = "RhythmicStaff"):
+        Staff.__init__ (self, command)
+
 
 def test_pitch ():
     bflat = Pitch()