1# This file is part of python-ly, https://pypi.python.org/pypi/python-ly 2# 3# Copyright (c) 2008 - 2015 by Wilbert Berendsen 4# 5# This program is free software; you can redistribute it and/or 6# modify it under the terms of the GNU General Public License 7# as published by the Free Software Foundation; either version 2 8# of the License, or (at your option) any later version. 9# 10# This program is distributed in the hope that it will be useful, 11# but WITHOUT ANY WARRANTY; without even the implied warranty of 12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13# GNU General Public License for more details. 14# 15# You should have received a copy of the GNU General Public License 16# along with this program; if not, write to the Free Software 17# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 18# See http://www.gnu.org/licenses/ for more information. 19 20""" 21LilyPond DOM 22 23(c) 2008-2011 Wilbert Berendsen 24License: GPL. 25 26A simple Document Object Model for LilyPond documents. 27 28The purpose is to easily build a LilyPond document with good syntax, 29not to fully understand all features LilyPond supports. (This DOM does 30not enforce a legal LilyPond file.) 31 32All elements of a LilyPond document inherit Node. 33 34Note: elements keep a weak reference to their parent. 35 36""" 37 38from __future__ import unicode_literals 39from __future__ import absolute_import # prevent picking old stale node.py from package 40 41try: 42 string_types = basestring 43except NameError: 44 string_types = str 45 46import fractions 47import re 48 49import ly.pitch 50import ly.duration 51from ly.node import WeakNode 52 53 54class LyNode(WeakNode): 55 """ 56 Base class for LilyPond objects, based on Node, 57 which takes care of the tree structure. 58 """ 59 60 ## 61 # True if this element is single LilyPond atom, word, note, etc. 62 # When it is the only element inside { }, the brackets can be removed. 63 isAtom = False 64 65 ## 66 # The number of newlines this object wants before it. 67 before = 0 68 69 ## 70 # The number of newlines this object wants after it. 71 after = 0 72 73 def ly(self, printer): 74 """ 75 Returns printable output for this object. 76 Can ask printer for certain settings, e.g. pitch language etc. 77 """ 78 return '' 79 80 def concat(self, other): 81 """ 82 Returns a string with newlines to concat this node to another one. 83 If zero newlines are requested, an empty string is returned. 84 """ 85 return '\n' * max(self.after, other.before) 86 87 88## 89# Leaf and Container are the two base classes the rest of the LilyPond 90# element classes is based on. 91# 92class Leaf(LyNode): 93 """ A leaf node without children """ 94 pass 95 96 97class Container(LyNode): 98 """ A node that concatenates its children on output """ 99 100 ## 101 # default character to concatenate children with 102 defaultSpace = " " 103 104 @property 105 def before(self): 106 if len(self): 107 return self[0].before 108 else: 109 return 0 110 111 @property 112 def after(self): 113 if len(self): 114 return self[-1].after 115 else: 116 return 0 117 118 def ly(self, printer): 119 if len(self) == 0: 120 return '' 121 else: 122 n = self[0] 123 res = [n.ly(printer)] 124 for m in self[1:]: 125 res.append(n.concat(m) or self.defaultSpace) 126 res.append(m.ly(printer)) 127 n = m 128 return "".join(res) 129 130 131## 132# Helper classes 133# 134class Printer(object): 135 """ 136 Performs certain operations on behalf of a LyNode tree, 137 like quoting strings or translating pitch names, etc. 138 """ 139 140 # You may change these 141 primary_quote_left = '\u2018' 142 primary_quote_right = '\u2019' 143 secondary_quote_left = '\u201C' 144 secondary_quote_right = '\u201D' 145 146 def __init__(self): 147 self.typographicalQuotes = True 148 self.language = "nederlands" 149 self.indentString = ' ' 150 151 def quoteString(self, text): 152 if self.typographicalQuotes: 153 text = re.sub(r'"(.*?)"', self.primary_quote_left + r'\1' + self.primary_quote_right, text) 154 text = re.sub(r"'(.*?)'", self.secondary_quote_left + r'\1' + self.secondary_quote_right, text) 155 text = text.replace("'", '\u2018') 156 # escape regular double quotes 157 text = text.replace('"', '\\"') 158 # quote the string 159 return '"{0}"'.format(text) 160 161 def indentGen(self, node, startIndent = 0): 162 """ 163 A generator that walks on the output of the given node, 164 and returns properly indented LilyPond code. 165 """ 166 d = startIndent 167 for t in node.ly(self).splitlines() + [''] * node.after: 168 if d and re.match(r'#?}|>|%}', t): 169 d -= 1 170 yield self.indentString * d + t 171 if re.search(r'(\{|<|%{)$', t): 172 d += 1 173 174 def indent(self, node): 175 """ 176 Return a formatted printout of node (and its children) 177 """ 178 return '\n'.join(self.indentGen(node)) 179 180 181class Reference(object): 182 """ 183 A simple object that keeps a name, to use as a (context) 184 identifier. Set the name attribute to the name you want 185 to display, and on all places in the document the name 186 will show up. 187 """ 188 def __init__(self, name=""): 189 self.name = name 190 191 def __format__(self, format_spec): 192 return self.name 193 194 195class Named(object): 196 r""" 197 Mixin to print a \name before the contents of the container. 198 format() is called on the self.name attribute, so it may also 199 be a Reference. 200 """ 201 name = "" 202 203 def ly(self, printer): 204 return "\\{0} {1}".format(self.name, super(Named, self).ly(printer)) 205 206 207class HandleVars(object): 208 """ 209 A powerful mixin class to facilitate handling unique variable assignments 210 inside a Container more. 211 E.g.: 212 >>> h = Header() 213 >>> h['composer'] = "Johann Sebastian Bach" 214 creates a subnode (by default Assignment) with the name 'composer', and 215 that node again gets an autogenerated subnode of type QuotedString (if the 216 argument wasn't already a Node). 217 """ 218 childClass = None # To be filled in later 219 220 def ifbasestring(func): 221 """ 222 Ensure that the method is only called for basestring objects. 223 Otherwise the same method from the super class is called. 224 """ 225 def newfunc(obj, name, *args): 226 if isinstance(name, string_types): 227 return func(obj, name, *args) 228 else: 229 f = getattr(super(HandleVars, obj), func.__name__) 230 return f(name, *args) 231 return newfunc 232 233 @ifbasestring 234 def __getitem__(self, name): 235 for node in self.find_children(self.childClass, 1): 236 if node.name == name: 237 return node 238 239 @ifbasestring 240 def __setitem__(self, name, valueObj): 241 if not isinstance(valueObj, LyNode): 242 valueObj = self.importNode(valueObj) 243 assignment = self[name] 244 if assignment: 245 assignment.setValue(valueObj) 246 else: 247 self.childClass(name, self, valueObj) 248 249 @ifbasestring 250 def __contains__(self, name): 251 return bool(self[name]) 252 253 @ifbasestring 254 def __delitem__(self, name): 255 h = self[name] 256 if h: 257 self.remove(h) 258 259 def importNode(self, obj): 260 """ 261 Try to interpret the object and transform it into a Node object 262 of the right species. 263 """ 264 return QuotedString(obj) 265 266 267class AddDuration(object): 268 """ Mixin to add a duration (as child). """ 269 def ly(self, printer): 270 s = super(AddDuration, self).ly(printer) 271 dur = self.find_child(Duration, 1) 272 if dur: 273 s += dur.ly(printer) 274 return s 275 276 277class Block(Container): 278 """ 279 A vertical container type that puts everything on a new line. 280 """ 281 defaultSpace = "\n" 282 before, after = 1, 1 283 284 285class Document(Container): 286 """ 287 A container type that puts everything on a new line. 288 To be used as a full LilyPond document. 289 """ 290 defaultSpace = "\n" 291 after = 1 292 293 294## 295# These classes correspond to real LilyPond data. 296# 297class Text(Leaf): 298 """ A leaf node with arbitrary text """ 299 def __init__(self, text="", parent=None): 300 super(Text, self).__init__(parent) 301 self.text = text 302 303 def ly(self, printer): 304 return self.text 305 306 307class TextDur(AddDuration, Text): 308 """ A text note with an optional duration as child. """ 309 pass 310 311 312class Line(Text): 313 """ A text node that claims its own line. """ 314 before, after = 1, 1 315 316 317class Comment(Text): 318 """ A LilyPond comment at the end of a line """ 319 after = 1 320 321 def ly(self, printer): 322 return re.compile('^', re.M).sub('% ', self.text) 323 324 325class LineComment(Comment): 326 """ A LilyPond comment that takes a full line """ 327 before = 1 328 329 330class BlockComment(Comment): 331 """ A block comment between %{ and %} """ 332 @property 333 def before(self): 334 return '\n' in self.text and 1 or 0 335 336 @property 337 def after(self): 338 return '\n' in self.text and 1 or 0 339 340 def ly(self, printer): 341 text = self.text.replace('%}', '') 342 f = "%{{\n{0}\n%}}" if '\n' in text else "%{{ {0} %}}" 343 return f.format(text) 344 345 346class QuotedString(Text): 347 """ A string that is output inside double quotes. """ 348 isAtom = True 349 def ly(self, printer): 350 # we call format(), since self.text MIGHT be a Reference... 351 return printer.quoteString(format(self.text)) 352 353 354class Newline(LyNode): 355 """ A newline. """ 356 after = 1 357 358 359class BlankLine(Newline): 360 """ A blank line. """ 361 before = 1 362 363 364class Scheme(Text): 365 """ A Scheme expression, without the extra # prepended """ 366 isAtom = True 367 368 def ly(self, printer): 369 return '#' + self.text 370 371 372class Version(Line): 373 """ a LilyPond version instruction """ 374 def ly(self, printer): 375 return r'\version "{0}"'.format(self.text) 376 377 378class Include(Line): 379 r""" a LilyPond \include statement """ 380 def ly(self, printer): 381 return r'\include "{0}"'.format(self.text) 382 383 384class Assignment(Container): 385 """ 386 A varname = value construct with it's value as its first child 387 The name can be a string or a Reference object: so that everywhere 388 where this varname is referenced, the name is the same. 389 """ 390 before, after = 1, 1 391 392 def __init__(self, name=None, parent=None, valueObj=None): 393 super(Assignment, self).__init__(parent) 394 self.name = name 395 if valueObj: 396 self.append(valueObj) 397 398 # Convenience methods: 399 def setValue(self, obj): 400 if len(self): 401 self[0] = obj 402 else: 403 self.append(obj) 404 405 def value(self): 406 if len(self): 407 return self[0] 408 409 def ly(self, printer): 410 return "{0} = {1}".format(self.name, super(Assignment, self).ly(printer)) 411 412 413HandleVars.childClass = Assignment 414 415 416class Identifier(Leaf): 417 """ 418 An identifier, prints as \\name. 419 Name may be a string or a Reference object. 420 """ 421 isAtom = True 422 423 def __init__(self, name=None, parent=None): 424 super(Identifier, self).__init__(parent) 425 self.name = name 426 427 def ly(self, printer): 428 return "\\{0}".format(self.name) 429 430 431class Statement(Named, Container): 432 """ 433 Base class for statements with arguments. The statement is read in the 434 name attribute, the arguments are the children. 435 """ 436 before = 0 # do not read property from container 437 isAtom = True 438 439 440class Command(Statement): 441 """ 442 Use this to create a LilyPond command supplying the name (or a Reference) 443 when instantiating. 444 """ 445 def __init__(self, name, parent=None): 446 super(Command, self).__init__(parent) 447 self.name = name 448 449 450class Enclosed(Container): 451 """ 452 Encloses all children between brackets: { ... } 453 If may_remove_brackets is True in subclasses, the brackets are 454 removed if there is only one child and that child is an atom (i.e. 455 a single LilyPond expression. 456 """ 457 may_remove_brackets = False 458 pre, post = "{", "}" 459 before, after = 0, 0 460 isAtom = True 461 462 def ly(self, printer): 463 if len(self) == 0: 464 return " ".join((self.pre, self.post)) 465 sup = super(Enclosed, self) 466 text = sup.ly(printer) 467 if self.may_remove_brackets and len(self) == 1 and self[0].isAtom: 468 return text 469 elif sup.before or sup.after or '\n' in text: 470 return "".join((self.pre, "\n" * max(sup.before, 1), text, 471 "\n" * max(sup.after, 1), self.post)) 472 else: 473 return " ".join((self.pre, text, self.post)) 474 475 476class Seq(Enclosed): 477 """ An SequentialMusic expression between { } """ 478 pre, post = "{", "}" 479 480 481class Sim(Enclosed): 482 """ An SimultaneousMusic expression between << >> """ 483 pre, post = "<<", ">>" 484 485 486class Seqr(Seq): may_remove_brackets = True 487class Simr(Sim): may_remove_brackets = True 488 489 490class SchemeLily(Enclosed): 491 """ A LilyPond expression between #{ #} (inside scheme) """ 492 pre, post = "#{", "#}" 493 494 495class SchemeList(Enclosed): 496 """ A list of items enclosed in parentheses """ 497 pre, post = "(", ")" 498 499 def ly(self, printer): 500 return self.pre + Container.ly(self, printer) + self.post 501 502 503class StatementEnclosed(Named, Enclosed): 504 """ 505 Base class for LilyPond commands that have a single bracket-enclosed 506 list of arguments. 507 """ 508 may_remove_brackets = True 509 510 511class CommandEnclosed(StatementEnclosed): 512 """ 513 Use this to print LilyPond commands that have a single 514 bracket-enclosed list of arguments. The command name is supplied to 515 the constructor. 516 """ 517 def __init__(self, name, parent=None): 518 super(CommandEnclosed, self).__init__(parent) 519 self.name = name 520 521 522class Section(StatementEnclosed): 523 """ 524 Very much like a Statement. Use as base class for \\book { }, \\score { } 525 etc. By default never removes the brackets and always starts on a new line. 526 """ 527 may_remove_brackets = False 528 before, after = 1, 1 529 530 531class Book(Section): name = 'book' 532class BookPart(Section): name = 'bookpart' 533class Score(Section): name = 'score' 534class Paper(HandleVars, Section): name = 'paper' 535class Layout(HandleVars, Section): name = 'layout' 536class Midi(HandleVars, Section): name = 'midi' 537class Header(HandleVars, Section): name = 'header' 538 539 540class With(HandleVars, Section): 541 """ If this item has no children, it prints nothing. """ 542 name = 'with' 543 before, after = 0, 0 544 545 def ly(self, printer): 546 if len(self): 547 return super(With, self).ly(printer) 548 else: 549 return '' 550 551 552class ContextName(Text): 553 """ 554 Used to print a context name, like \\Score. 555 """ 556 def ly(self, printer): 557 return "\\" + self.text 558 559 560class Context(HandleVars, Section): 561 r""" 562 A \context section for use inside Layout or Midi sections. 563 """ 564 name = 'context' 565 566 def __init__(self, contextName="", parent=None): 567 super(Context, self).__init__(parent) 568 if contextName: 569 ContextName(contextName, self) 570 571 572class ContextType(Container): 573 r""" 574 \new or \context Staff = 'bla' \with { } << music >> 575 576 A \with (With) element is added automatically as the first child as soon 577 as you use our convenience methods that manipulate the variables 578 in \with. If the \with element is empty, it does not print anything. 579 You should add one other music object to this. 580 """ 581 before, after = 1, 1 582 isAtom = True 583 ctype = None 584 585 def __init__(self, cid=None, new=True, parent=None): 586 super(ContextType, self).__init__(parent) 587 self.new = new 588 self.cid = cid 589 590 def ly(self, printer): 591 res = [] 592 res.append(self.new and "\\new" or "\\context") 593 res.append(self.ctype or self.__class__.__name__) 594 if self.cid: 595 res.append("=") 596 res.append(printer.quoteString(format(self.cid))) 597 res.append(super(ContextType, self).ly(printer)) 598 return " ".join(res) 599 600 def getWith(self): 601 """ 602 Gets the attached with clause. Creates it if not there. 603 """ 604 for node in self: 605 if isinstance(node, With): 606 return node 607 self.insert(0, With()) 608 return self[0] 609 610 def addInstrumentNameEngraverIfNecessary(self): 611 """ 612 Adds the Instrument_name_engraver to the node if it would need it 613 to print instrument names. 614 """ 615 if not isinstance(self, 616 (Staff, RhythmicStaff, PianoStaff, Lyrics, FretBoards)): 617 Line('\\consists "Instrument_name_engraver"', self.getWith()) 618 619 620class ChoirStaff(ContextType): pass 621class ChordNames(ContextType): pass 622class CueVoice(ContextType): pass 623class Devnull(ContextType): pass 624class DrumStaff(ContextType): pass 625class DrumVoice(ContextType): pass 626class Dynamics(ContextType): pass 627class FiguredBass(ContextType): pass 628class FretBoards(ContextType): pass 629class Global(ContextType): pass 630class GrandStaff(ContextType): pass 631class GregorianTranscriptionStaff(ContextType): pass 632class GregorianTranscriptionVoice(ContextType): pass 633class InnerChoirStaff(ContextType): pass 634class InnerStaffGroup(ContextType): pass 635class Lyrics(ContextType): pass 636class MensuralStaff(ContextType): pass 637class MensuralVoice(ContextType): pass 638class NoteNames(ContextType): pass 639class PianoStaff(ContextType): pass 640class RhythmicStaff(ContextType): pass 641class ScoreContext(ContextType): 642 r""" 643 Represents the Score context in LilyPond, but the name would 644 collide with the Score class that represents \score { } constructs. 645 646 Because the latter is used more often, use ScoreContext to get 647 \new Score etc. 648 """ 649 ctype = 'Score' 650 651class Staff(ContextType): pass 652class StaffGroup(ContextType): pass 653class TabStaff(ContextType): pass 654class TabVoice(ContextType): pass 655class VaticanaStaff(ContextType): pass 656class VaticanaVoice(ContextType): pass 657class Voice(ContextType): pass 658 659 660class UserContext(ContextType): 661 r""" 662 Represents a context the user creates. 663 e.g. \new MyStaff = cid << music >> 664 """ 665 def __init__(self, ctype, cid=None, new=True, parent=None): 666 super(UserContext, self).__init__(cid, new, parent) 667 self.ctype = ctype 668 669 670class ContextProperty(Leaf): 671 """ 672 A Context.property or Context.layoutObject construct. 673 Call e.g. ContextProperty('aDueText', 'Staff') to get 'Staff.aDueText'. 674 """ 675 def __init__(self, prop, context=None, parent=None): 676 self.prop = prop 677 self.context = context 678 679 def ly(self, printer): 680 if self.context: 681 # In \lyrics or \lyricmode: put spaces around dot. 682 p = self.find_parent(InputMode) 683 if p and isinstance(p, LyricMode): 684 f = '{0} . {1}' 685 else: 686 f = '{0}.{1}' 687 return f.format(self.context, self.prop) 688 else: 689 return self.prop 690 691 692class InputMode(StatementEnclosed): 693 """ 694 The abstract base class for input modes such as lyricmode/lyrics, 695 chordmode/chords etc. 696 """ 697 pass 698 699 700class ChordMode(InputMode): name = 'chordmode' 701class InputChords(ChordMode): name = 'chords' 702class LyricMode(InputMode): name = 'lyricmode' 703class InputLyrics(LyricMode): name = 'lyrics' 704class NoteMode(InputMode): name = 'notemode' 705class InputNotes(NoteMode): name = 'notes' 706class FigureMode(InputMode): name = 'figuremode' 707class InputFigures(FigureMode): name = 'figures' 708class DrumMode(InputMode): name = 'drummode' 709class InputDrums(DrumMode): name = 'drums' 710 711 712class AddLyrics(InputLyrics): 713 name = 'addlyrics' 714 may_remove_brackets = False 715 before, after = 1, 1 716 717 718class LyricsTo(LyricMode): 719 name = 'lyricsto' 720 721 def __init__(self, cid, parent=None): 722 super(LyricsTo, self).__init__(parent) 723 self.cid = cid 724 725 def ly(self, printer): 726 res = ["\\" + self.name] 727 res.append(printer.quoteString(format(self.cid))) 728 res.append(Enclosed.ly(self, printer)) 729 return " ".join(res) 730 731 732class Pitch(Leaf): 733 """ 734 A pitch with octave, note, alter. 735 octave is specified by an integer, zero for the octave containing middle C. 736 note is a number from 0 to 6, with 0 corresponding to pitch C and 6 737 corresponding to pitch B. 738 alter is the number of whole tones for alteration (can be int or Fraction) 739 """ 740 741 def __init__(self, octave=0, note=0, alter=0, parent=None): 742 super(Pitch, self).__init__(parent) 743 self.octave = octave 744 self.note = note 745 self.alter = fractions.Fraction(alter) 746 747 def ly(self, printer): 748 """ 749 Print the pitch in the preferred language. 750 """ 751 p = ly.pitch.pitchWriter(printer.language)(self.note, self.alter) 752 if self.octave < -1: 753 return p + ',' * (-self.octave - 1) 754 elif self.octave > -1: 755 return p + "'" * (self.octave + 1) 756 return p 757 758 759class Duration(Leaf): 760 r""" 761 A duration with duration (in logarithmic form): (-2 ... 8), 762 where -2 = \longa, -1 = \breve, 0 = 1, 1 = 2, 2 = 4, 3 = 8, 4 = 16, etc, 763 dots (number of dots), 764 factor (Fraction giving the scaling of the duration). 765 """ 766 def __init__(self, dur, dots=0, factor=1, parent=None): 767 super(Duration, self).__init__(parent) 768 self.dur = dur # log 769 self.dots = dots 770 self.factor = fractions.Fraction(factor) 771 772 def ly(self, printer): 773 return ly.duration.tostring(self.dur, self.dots, self.factor) 774 775 776class Chord(Container): 777 """ 778 A chord containing one of more Pitches and optionally one Duration. 779 This is a bit of a hack, awaiting real music object support. 780 """ 781 def ly(self, printer): 782 pitches = list(self.find_children(Pitch, 1)) 783 if len(pitches) == 1: 784 s = pitches[0].ly(printer) 785 else: 786 s = "<{0}>".format(' '.join(p.ly(printer) for p in pitches)) 787 duration = self.find_child(Duration, 1) 788 if duration: 789 s += duration.ly(printer) 790 return s 791 792 793class Relative(Statement): 794 r""" 795 \relative <pitch> music 796 797 You should add a Pitch (optionally) and another music object, 798 e.g. Sim or Seq, etc. 799 """ 800 name = 'relative' 801 802 803class Transposition(Statement): 804 r""" 805 \transposition <pitch> 806 You should add a Pitch. 807 """ 808 name = 'transposition' 809 810 811class KeySignature(Leaf): 812 r""" 813 A key signature expression, like: 814 815 \key c \major 816 The pitch should be given in the arguments note and alter and is written 817 out in the document's language. 818 """ 819 def __init__(self, note=0, alter=0, mode="major", parent=None): 820 super(KeySignature, self).__init__(parent) 821 self.note = note 822 self.alter = fractions.Fraction(alter) 823 self.mode = mode 824 825 def ly(self, printer): 826 pitch = ly.pitch.pitchWriter(printer.language)(self.note, self.alter) 827 return "\\key {0} \\{1}".format(pitch, self.mode) 828 829 830class TimeSignature(Leaf): 831 r""" 832 A time signature, like: \time 4/4 833 """ 834 def __init__(self, num, beat, parent=None): 835 super(TimeSignature, self).__init__(parent) 836 self.num = num 837 self.beat = beat 838 839 def ly(self, printer): 840 return "\\time {0}/{1}".format(self.num, self.beat) 841 842 843class Partial(Named, Duration): 844 r""" 845 \partial <duration> 846 You should add a Duration element 847 """ 848 name = "partial" 849 before, after = 1, 1 850 851 852class Tempo(Container): 853 r""" 854 A tempo setting, like: \tempo 4 = 100 855 May have a child markup or quoted string. 856 """ 857 before, after = 1, 1 858 859 def __init__(self, duration, value, parent=None): 860 super(Tempo, self).__init__(parent) 861 self.duration = duration 862 self.value = value 863 864 def ly(self, printer): 865 result = ['\\tempo'] 866 if len(self) > 0: 867 result.append(super(Tempo, self).ly(printer)) 868 if self.value: 869 result.append("{0}={1}".format(self.duration, self.value)) 870 return ' '.join(result) 871 872 873class Clef(Leaf): 874 """ 875 A clef. 876 """ 877 def __init__(self, clef, parent=None): 878 super(Clef, self).__init__(parent) 879 self.clef = clef 880 881 def ly(self, printer): 882 clef = self.clef if self.clef.isalpha() else '"{0}"'.format(self.clef) 883 return "\\clef " + clef 884 885 886class VoiceSeparator(Leaf): 887 r""" 888 A Voice Separator: \\ 889 """ 890 def ly(self, printer): 891 return r'\\' 892 893 894class Mark(Statement): 895 r""" 896 The \mark command. 897 """ 898 name = 'mark' 899 900 901class Markup(StatementEnclosed): 902 r""" 903 The \markup command. 904 You can add many children, in that case Markup automatically prints 905 { and } around them. 906 """ 907 name = 'markup' 908 909 910class MarkupEnclosed(CommandEnclosed): 911 """ 912 A markup that auto-encloses all its arguments, like 'italic', 'bold' 913 etc. You must supply the name. 914 """ 915 pass 916 917 918class MarkupCommand(Command): 919 """ 920 A markup command with more or no arguments, that does not auto-enclose 921 its arguments. Useful for commands like note-by-number or hspace. 922 923 You must supply the name. Its arguments are its children. 924 If one argument can be a markup list, use a Enclosed() construct for that. 925 """ 926 pass 927 928 929 930