]> git.donarmstrong.com Git - lilypond.git/blobdiff - python/musicexp.py
MusicXML: Correctly convert nested staff/part groups
[lilypond.git] / python / musicexp.py
index 5bd1436748dcfb137c3b9ace71e8b86257b9243e..050ae66c03daed3ebeaa162d38ae673e53ffe505 100644 (file)
@@ -6,6 +6,18 @@ import re
 from rational import Rational
 
 
+def escape_instrument_string (input_string):
+    retstring = string.replace (input_string, "\"", "\\\"")
+    if re.match ('.*\n.*', retstring):
+        strings = retstring.split ('\r\n')
+        retstring = "\\markup { \\column { "
+        for s in strings:
+            retstring += "\\line {\"" + s + "\"} "
+        retstring += "} }"
+    else:
+        retstring = "\"" + retstring + "\""
+    return retstring
+
 class Output_stack_element:
     def __init__ (self):
         self.factor = Rational (1)
@@ -63,8 +75,14 @@ class Output_printer:
         self._line += str
 
     def unformatted_output (self, str):
-        self._nesting += str.count ('<') + str.count ('{')
-        self._nesting -= str.count ('>') + str.count ('}')
+        # don't indent on \< and indent only once on <<
+        self._nesting += ( str.count ('<') 
+                         - str.count ('\<') - str.count ('<<') 
+                         + str.count ('{') )
+        self._nesting -= ( str.count ('>') - str.count ('\>') - str.count ('>>')
+                                           - str.count ('->') - str.count ('_>')
+                                           - str.count ('^>')
+                         + str.count ('}') )
         self.print_verbatim (str)
         
     def print_duration_string (self, str):
@@ -130,7 +148,10 @@ class Duration:
         str = '%d%s' % (1 << self.duration_log, '.'*self.dots)
 
         if factor <> Rational (1,1):
-            str += '*%d/%d' % (factor.numerator (), factor.denominator ())
+            if factor.denominator () <> 1:
+                str += '*%d/%d' % (factor.numerator (), factor.denominator ())
+            else:
+                str += '*%d' % factor.numerator ()
 
         return str
     
@@ -390,17 +411,31 @@ class NestedMusic(Music):
         return None
         
 class SequentialMusic (NestedMusic):
-    def print_ly (self, printer):
+    def get_last_event_chord (self):
+        value = None
+        at = len( self.elements ) - 1
+        while (at >= 0 and
+               not isinstance (self.elements[at], EventChord) and
+               not isinstance (self.elements[at], BarLine)):
+            at -= 1
+
+        if (at >= 0 and isinstance (self.elements[at], EventChord)):
+            value = self.elements[at]
+        return value
+
+    def print_ly (self, printer, newline = True):
         printer ('{')
         if self.comment:
             self.print_comment (printer)
 
-        printer.newline()
+        if newline:
+            printer.newline()
         for e in self.elements:
             e.print_ly (printer)
 
         printer ('}')
-        printer.newline()
+        if newline:
+            printer.newline()
             
     def lisp_sub_expression (self, pred):
         name = self.name()
@@ -415,6 +450,36 @@ class SequentialMusic (NestedMusic):
             e.set_start (start)
             start += e.get_length()
 
+class RepeatedMusic:
+    def __init__ (self):
+        self.repeat_type = "volta"
+        self.repeat_count = 2
+        self.endings = []
+        self.music = None
+    def set_music (self, music):
+        if isinstance (music, Music):
+            self.music = music
+        elif isinstance (music, list):
+            self.music = SequentialMusic ()
+            self.music.elements = music
+        else:
+            sys.stderr.write ("WARNING: Unable to set the music %s for the repeat %s" % (music, self))
+    def add_ending (self, music):
+        self.endings.append (music)
+    def print_ly (self, printer):
+        printer.dump ('\\repeat %s %s' % (self.repeat_type, self.repeat_count))
+        if self.music:
+            self.music.print_ly (printer)
+        else:
+            sys.stderr.write ("WARNING: Encountered repeat without body\n")
+            printer.dump ('{}')
+        if self.endings:
+            printer.dump ('\\alternative {')
+            for e in self.endings:
+                e.print_ly (printer)
+            printer.dump ('}')
+
+
 class Lyrics:
     def __init__ (self):
         self.lyrics_syllables = []
@@ -456,9 +521,12 @@ class Header:
 class EventChord (NestedMusic):
     def __init__ (self):
         NestedMusic.__init__ (self)
-        self.grace_elements = []
+        self.grace_elements = None
+        self.grace_type = None
     def append_grace (self, element):
         if element:
+            if not self.grace_elements:
+                self.grace_elements = SequentialMusic ()
             self.grace_elements.append (element)
 
     def get_length (self):
@@ -479,10 +547,12 @@ class EventChord (NestedMusic):
                 not isinstance (e, RhythmicEvent)]
 
         if self.grace_elements and self.elements:
-            printer ('\grace {')
-            for g in self.grace_elements:
-                g.print_ly (printer)
-            printer ('}')
+            if self.grace_type:
+                printer ('\\%s' % self.grace_type)
+            else:
+                printer ('\\grace')
+            # don't print newlines after the { and } braces
+            self.grace_elements.print_ly (printer, False)
 
         if rest_events:
             rest_events[0].print_ly (printer)
@@ -500,21 +570,35 @@ class EventChord (NestedMusic):
 
         self.print_comment (printer)
             
+class Partial (Music):
+    def __init__ (self):
+        Music.__init__ (self)
+        self.partial = None
+    def print_ly (self, printer):
+        if self.partial:
+            printer.dump ("\\partial %s" % self.partial.ly_expression ())
 
-class BarCheck (Music):
+class BarLine (Music):
     def __init__ (self):
         Music.__init__ (self)
         self.bar_number = 0
+        self.type = None
         
     def print_ly (self, printer):
+        bar_symbol = { 'regular': "|", 'dotted': ":", 'dashed': ":",
+                       'heavy': "|", 'light-light': "||", 'light-heavy': "|.",
+                       'heavy-light': ".|", 'heavy-heavy': ".|.", 'tick': "'",
+                       'short': "'", 'none': "" }.get (self.type, None)
+        if bar_symbol <> None:
+            printer.dump ('\\bar "%s"' % bar_symbol)
+        else:
+            printer.dump ("|")
+
         if self.bar_number > 0 and (self.bar_number % 10) == 0:
-            printer.dump ("|  \\barNumberCheck #%d " % self.bar_number)
-            printer.newline ()
+            printer.dump ("\\barNumberCheck #%d " % self.bar_number)
         else:
-            printer.dump ("| ")
             printer.print_verbatim (' %% %d' % self.bar_number)
-            printer.newline ()
+        printer.newline ()
 
     def ly_expression (self):
         return " | "
@@ -523,7 +607,7 @@ class Event(Music):
     pass
 
 class SpanEvent (Event):
-    def __init__(self):
+    def __init__ (self):
         Event.__init__ (self)
         self.span_direction = 0 # start/stop
         self.line_type = 'solid'
@@ -656,10 +740,29 @@ class DynamicsEvent (Event):
             printer.dump ("-\\markup{ \\dynamic %s }" % self.type)
 
 
+class TextEvent (Event):
+    def __init__ (self):
+        self.Text = None
+        self.force_direction = None
+        self.markup = ''
+    def wait_for_note (self):
+        return True
+
+    def direction_mod (self):
+        return { 1: '^', -1: '_', 0: '-' }.get (self.force_direction, '-')
+
+    def ly_expression (self):
+        base_string = '%s\"%s\"'
+        if self.markup:
+            base_string = '%s\markup{ ' + self.markup + ' {%s} }'
+        return base_string % (self.direction_mod (), self.text)
+
 class ArticulationEvent (Event):
     def __init__ (self):
         self.type = None
         self.force_direction = None
+    def wait_for_note (self):
+        return True;
 
     def direction_mod (self):
         return { 1: '^', -1: '_', 0: '-' }.get (self.force_direction, '')
@@ -674,18 +777,63 @@ class ShortArticulationEvent (ArticulationEvent):
     def ly_expression (self):
         return '%s%s' % (self.direction_mod (), self.type)
 
-class TremoloEvent (Event):
+class NoDirectionArticulationEvent (ArticulationEvent):
+    def ly_expression (self):
+        return '\\%s' % self.type
+
+class MarkupEvent (ShortArticulationEvent):
+    def __init__ (self):
+        ArticulationEvent.__init__ (self)
+        self.contents = None
+    def ly_expression (self):
+        if self.contents:
+            return "%s\\markup { %s }" % (self.direction_mod (), self.contents)
+        else:
+            return ''
+
+class FretEvent (MarkupEvent):
+    def __init__ (self):
+        MarkupEvent.__init__ (self)
+        self.force_direction = 1
+        self.strings = 6
+        self.frets = 4
+        self.barre = None
+        self.elements = []
+    def ly_expression (self):
+        val = ""
+        if self.strings <> 6:
+            val += "w:%s;" % self.strings
+        if self.frets <> 4:
+            val += "h:%s;" % self.frets
+        if self.barre and len (self.barre) >= 3:
+            val += "c:%s-%s-%s;" % (self.barre[0], self.barre[1], self.barre[2])
+        have_fingering = False
+        for i in self.elements:
+            if len (i) > 1:
+                val += "%s-%s" % (i[0], i[1])
+            if len (i) > 2:
+                have_fingering = True
+                val += "-%s" % i[2]
+            val += ";"
+        if have_fingering:
+            val = "f:1;" + val
+        if val:
+            return "%s\\markup { \\fret-diagram #\"%s\" }" % (self.direction_mod (), val)
+        else:
+            return ''
+
+class TremoloEvent (ArticulationEvent):
     def __init__ (self):
         Event.__init__ (self)
         self.bars = 0
 
     def ly_expression (self):
         str=''
-        if self.bars > 0:
+        if self.bars and self.bars > 0:
             str += ':%s' % (2 ** (2 + string.atoi (self.bars)))
         return str
 
-class BendEvent (Event):
+class BendEvent (ArticulationEvent):
     def __init__ (self):
         Event.__init__ (self)
         self.alter = 0
@@ -849,6 +997,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):
@@ -871,6 +1030,116 @@ class MultiMeasureRest(Music):
         return 'R%s' % self.duration.ly_expression ()
 
 
+class StaffGroup:
+    def __init__ (self, command = "StaffGroup"):
+        self.stafftype = command
+        self.id = None
+        self.instrument_name = None
+        self.short_instrument_name = None
+        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 append_staff (self, staff):
+        self.children.append (staff)
+
+    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.set_part_information (part_name, staves_info)
+
+    def print_ly_contents (self, printer):
+        for c in self.children:
+            if c:
+                c.print_ly (printer)
+    def print_ly_overrides (self, printer):
+        needs_with = False
+        needs_with |= self.spanbar == "no"
+        needs_with |= self.instrument_name != None
+        needs_with |= self.short_instrument_name != None
+        needs_with |= (self.symbol != None) and (self.symbol != "bracket")
+        if needs_with:
+            printer.dump ("\\with {")
+            if self.instrument_name or self.short_instrument_name:
+                printer.dump ("\\consists \"Instrument_name_engraver\"")
+            if self.spanbar == "no":
+                printer.dump ("\\override SpanBar #'transparent = ##t")
+            brack = {"brace": "SystemStartBrace",
+                     "none": "f",
+                     "line": "SystemStartSquare"}.get (self.symbol, None)
+            if brack:
+                printer.dump ("systemStartDelimiter = #'%s" % brack)
+            printer.dump ("}")
+
+    def print_ly (self, printer):
+        if self.stafftype:
+            printer.dump ("\\new %s" % self.stafftype)
+        self.print_ly_overrides (printer)
+        printer.dump ("<<")
+        printer.newline ()
+        if self.stafftype and self.instrument_name:
+            printer.dump ("\\set %s.instrumentName = %s" % (self.stafftype, 
+                    escape_instrument_string (self.instrument_name)))
+            printer.newline ()
+        if self.stafftype and self.short_instrument_name:
+            printer.dump ("\\set %s.shortInstrumentName = %s" % (self.stafftype,
+                    escape_instrument_string (self.short_instrument_name)))
+            printer.newline ()
+        self.print_ly_contents (printer)
+        printer.newline ()
+        printer.dump (">>")
+        printer.newline ()
+
+
+class Staff (StaffGroup):
+    def __init__ (self):
+        StaffGroup.__init__ (self, "Staff")
+        self.is_group = False
+        self.part = None
+
+    def print_ly_overrides (self, printer):
+        pass
+
+    def print_ly_contents (self, printer):
+        if not self.id or not self.part_information:
+            return
+
+        for [staff_id, voices] in self.part_information:
+            if staff_id:
+                printer ('\\context Staff = "%s" << ' % staff_id)
+            else:
+                printer ('\\context Staff << ')
+            printer.newline ()
+            n = 0
+            nr_voices = len (voices)
+            for [v, lyrics] in voices:
+                n += 1
+                voice_count_text = ''
+                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.newline ()
+
+                for l in lyrics:
+                    printer ('\\new Lyrics \\lyricsto "%s" \\%s' % (v,l))
+                    printer.newline()
+            printer ('>>')
+
+    def print_ly (self, printer):
+        if self.part_information and len (self.part_information) > 1:
+            self.stafftype = "PianoStaff"
+        StaffGroup.print_ly (self, printer)
+
+
 def test_pitch ():
     bflat = Pitch()
     bflat.alteration = -1