1# This file is part of python-ly, https://pypi.python.org/pypi/python-ly 2# 3# Copyright (c) 2014 - 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""" 21The items a music expression is constructed with in a tree structure. 22 23Whitespace and comments are left out. 24 25All nodes (instances of Item) have a 'position' attribute that indicates 26where the item starts in the source text. Almost all items have the token 27that starts the expression in the 'token' attribute and possibly other 28tokens in the 'tokens' attribute, as a tuple. 29 30The 'end_position()' method returns the position where the node (including 31its child nodes) ends. 32 33You can get the whole tree structure of a LilyPond document by instantiating 34a Document with the ly.document.Document instance. (It will read all the tokens 35from the document using the Reader from the read module.) As a convenience, 36the ly.music.document(doc) function does this. 37 38If you want to add new Item types, you should also add a method to read.Reader 39to construct those items. 40 41""" 42 43from __future__ import unicode_literals 44 45from fractions import Fraction 46import re 47 48import ly.node 49from ly import lex 50from ly.lex import lilypond 51from ly.lex import scheme 52 53 54class Item(ly.node.WeakNode): 55 """Represents any item in the music of a document. 56 57 This can be just a token, or an interpreted construct such as a note, 58 rest or sequential or simultaneous construct , etc. 59 60 Some Item instances just have one responsible token, but others have a 61 list or tuple to tokens. 62 63 An Item also has a pointer to the Document it originates from. 64 65 """ 66 document = None 67 tokens = () 68 token = None 69 position = -1 70 71 def __repr__(self): 72 s = ' ' + repr(self.token[:]) if self.token else '' 73 return '<{0}{1}>'.format(self.__class__.__name__, s) 74 75 def plaintext(self): 76 """Return a plaintext value for this node. 77 78 This only makes sense for items like Markup or String. For other types, 79 an empty string is returned 80 81 """ 82 return "" 83 84 def end_position(self): 85 """Return the end position of this node.""" 86 def ends(): 87 if self.tokens: 88 yield self.tokens[-1].end 89 elif self.token: 90 yield self.token.end 91 else: 92 yield self.position 93 if len(self): 94 # end pos of the last child 95 yield self[-1].end_position() 96 # end pos of Item or Token instances in attributes, such as duration etc 97 for i in vars(self).values(): 98 if isinstance(i, Item): 99 yield i.end_position() 100 elif isinstance(i, lex.Token): 101 yield i.end 102 return max(ends()) 103 104 def events(self, e, time, scaling): 105 """Let the event.Events instance handle the events. Return the time.""" 106 return time 107 108 def length(self): 109 """Return the musical duration.""" 110 return 0 111 112 def iter_toplevel_items(self): 113 """Yield the toplevel items of our Document node in backward direction. 114 115 Iteration starts with the node just before the node "self" is a 116 descendant of. 117 118 """ 119 node = self 120 for doc in self.ancestors(): 121 if isinstance(doc, Document): 122 break 123 node = doc 124 else: 125 return 126 127 # now, doc is the Document node, and node is the child of the Document 128 # node we are a (far) descendant of 129 for i in node.backward(): 130 yield i 131 132 # look in parent Document before the place we were included 133 while doc.include_node: 134 p = doc.include_node.parent() 135 if isinstance(p, Document): 136 for i in doc.include_node.backward(): 137 yield i 138 doc = p 139 else: 140 break 141 142 def iter_toplevel_items_include(self): 143 r"""Same as iter_toplevel_items(), but follows \include commands.""" 144 def follow(it): 145 for i in it: 146 if isinstance(i, Include): 147 doc = i.parent().get_included_document_node(i) 148 if doc: 149 for i in follow(doc[::-1]): 150 yield i 151 else: 152 yield i 153 return follow(self.iter_toplevel_items()) 154 155 def music_parent(self): 156 """Walk up the parent tree until Music is found; return the outermost Music node. 157 158 Returns None is the node does not belong to any music expression (e.g. 159 a toplevel Markup or Scheme object). 160 161 """ 162 node = self 163 mus = isinstance(node, Music) 164 for p in self.ancestors(): 165 pmus = isinstance(p, Music) 166 if mus and not pmus: 167 return node 168 mus = pmus 169 node = p 170 171 def music_children(self, depth=-1): 172 """Yield all the children that are new music expressions 173 174 (i.e. that are inside other constructions). 175 176 """ 177 def find(node, depth): 178 if depth != 0: 179 if isinstance(node, Music): 180 for i in node: 181 for i in find(i, depth-1): 182 yield i 183 else: 184 for i in node: 185 if isinstance(i, Music): 186 yield i 187 else: 188 for i in find(i, depth-1): 189 yield i 190 return find(self, depth) 191 192 def has_output(self, _seen_docs=None): 193 """Return True if this node has toplevel music, markup, book etc. 194 195 I.e. returns True when LilyPond would likely generate output. Usually 196 you'll call this method on a Document, Score, BookPart or Book node. 197 198 You should not supply the _seen_docs argument; it is used internally 199 to avoid traversing recursively nested include files. 200 201 """ 202 if _seen_docs is None: 203 _seen_docs = set() 204 _seen_docs.add(self) 205 for n in self: 206 if isinstance(n, (Music, Markup)): 207 return True 208 elif isinstance(n, (Book, BookPart, Score)): 209 if n.has_output(_seen_docs): 210 return True 211 elif isinstance(n, Include): 212 doc = self.toplevel().get_included_document_node(n) 213 if doc and doc not in _seen_docs and doc.has_output(_seen_docs): 214 return True 215 216 217class Document(Item): 218 """A toplevel item representing a ly.document.Document.""" 219 220 def __init__(self, doc): 221 super(Document, self).__init__() 222 self.document = doc 223 self.include_node = None 224 self.include_path = [] 225 self.relative_includes = True 226 import ly.document 227 c = ly.document.Cursor(doc) 228 s = ly.document.Source(c, True, tokens_with_position=True) 229 from .read import Reader 230 r = Reader(s) 231 self.extend(r.read()) 232 233 def node(self, position, depth=-1): 234 """Return the node at or just before the specified position.""" 235 def bisect(n, depth): 236 end = len(n) 237 if depth == 0 or end == 0: 238 return n 239 pos = 0 240 while pos < end: 241 mid = (pos + end) // 2 242 if position < n[mid].position: 243 end = mid 244 else: 245 pos = mid + 1 246 pos -= 1 247 if n[pos].position == position: 248 return n[pos] 249 elif n[pos].position > position: 250 return n 251 return bisect(n[pos], depth - 1) 252 return bisect(self, depth) 253 254 def music_events_til_position(self, position): 255 """Return a list of tuples. 256 257 Every tuple is a (parent, nodes, scaling). If an empty list is 258 returned, there is no music expression at this position. 259 260 """ 261 node = self.node(position) 262 # be nice and allow including an assignment 263 if (isinstance(node, Assignment) and node.parent() is self 264 and isinstance(node.value(), Music)): 265 return [(node, [], 1)] 266 267 if isinstance(node.parent(), Chord): 268 node = node.parent() 269 270 l = [] 271 mus = isinstance(node, (Music, Durable)) 272 if mus: 273 l.append((node, [], 1)) 274 for p in node.ancestors(): 275 pmus = isinstance(p, Music) 276 end = node.end_position() 277 if pmus: 278 if position > end: 279 preceding, s = p.preceding(node.next_sibling()) 280 l = [(p, preceding, s)] 281 elif position == end: 282 preceding, s = p.preceding(node) 283 l = [(p, preceding + [node], s)] 284 else: 285 preceding, s = p.preceding(node) 286 l.append((p, preceding, s)) 287 elif mus: 288 # we are at the musical top 289 if position > end: 290 return [] 291 elif position == end: 292 l = [(p, [node], 1)] 293 else: 294 l.append((p, [], 1)) 295 break 296 node = p 297 mus = pmus 298 l.reverse() 299 return l 300 301 def time_position(self, position): 302 """Return the time position in the music at the specified cursor position. 303 304 The value is a fraction. If None is returned, we are not in a music 305 expression. 306 307 """ 308 events = self.music_events_til_position(position) 309 if events: 310 from . import event 311 e = event.Events() 312 time = 0 313 scaling = 1 314 for parent, nodes, s in events: 315 scaling *= s 316 for n in nodes: 317 time = e.traverse(n, time, scaling) 318 return time 319 320 def time_length(self, start, end): 321 """Return the length of the music between start and end positions. 322 323 Returns None if start and end are not in the same expression. 324 325 """ 326 def mk_list(evts): 327 """Make a flat list of all the events.""" 328 l = [] 329 scaling = 1 330 for p, nodes, s in evts: 331 scaling *= s 332 for n in nodes: 333 l.append((n, scaling)) 334 return l 335 336 if start > end: 337 start, end = end, start 338 339 start_evts = self.music_events_til_position(start) 340 if start_evts: 341 end_evts = self.music_events_til_position(end) 342 if end_evts and start_evts[0][0] is end_evts[0][0]: 343 # yes, we have the same toplevel expression. 344 start_evts = mk_list(start_evts) 345 end_evts = mk_list(end_evts) 346 from . import event 347 e = event.Events() 348 time = 0 349 i = 0 350 # traverse the common events only once 351 for i, ((evt, s), (end_evt, end_s)) in enumerate(zip(start_evts, end_evts)): 352 if evt is end_evt: 353 time = e.traverse(evt, time, s) 354 else: 355 break 356 end_time = time 357 # handle the remaining events for the start position 358 for evt, s in start_evts[i::]: 359 time = e.traverse(evt, time, s) 360 # handle the remaining events for the end position 361 for evt, s in end_evts[i::]: 362 end_time = e.traverse(evt, end_time, s) 363 return end_time - time 364 365 def substitute_for_node(self, node): 366 """Returns a node that replaces the specified node (e.g. in music). 367 368 For example: a variable reference returns its value. 369 Returns nothing if the node is not substitutable. 370 Returns the node itself if it was substitutable, but the substitution 371 failed. 372 373 """ 374 if isinstance(node, UserCommand): 375 value = node.value() 376 if value: 377 return self.substitute_for_node(value) or value 378 return node 379 elif isinstance(node, Include): 380 return self.get_included_document_node(node) or node 381 382 # maybe other substitutions 383 384 def iter_music(self, node=None): 385 """Iter over the music, following references to other assignments.""" 386 for n in node or self: 387 n = self.substitute_for_node(n) or n 388 yield n 389 for n in self.iter_music(n): 390 yield n 391 392 def get_included_document_node(self, node): 393 """Return a Document for the Include node. May return None.""" 394 try: 395 return node._document 396 except AttributeError: 397 node._document = None 398 filename = node.filename() 399 if filename: 400 resolved = self.resolve_filename(filename) 401 if resolved: 402 docnode = self.get_music(resolved) 403 docnode.include_node = node 404 docnode.include_path = self.include_path 405 node._document = docnode 406 return node._document 407 408 def resolve_filename(self, filename): 409 """Resolve filename against our document and include_path.""" 410 import os 411 if os.path.isabs(filename): 412 return filename 413 path = list(self.include_path) 414 if self.document.filename: 415 basedir = os.path.dirname(self.document.filename) 416 try: 417 path.remove(basedir) 418 except ValueError: 419 pass 420 path.insert(0, basedir) 421 for p in path: 422 fullpath = os.path.join(p, filename) 423 if os.path.exists(fullpath): 424 return fullpath 425 426 def get_music(self, filename): 427 """Return the music Document for the specified filename. 428 429 This implementation loads a ly.document.Document using utf-8 430 encoding. Inherit from this class to implement other loading 431 mechanisms or caching. 432 433 """ 434 import ly.document 435 return type(self)(ly.document.Document.load(filename)) 436 437 438class Token(Item): 439 """Any token that is not otherwise recognized""" 440 441 442class Container(Item): 443 """An item having a list of child items.""" 444 445 446class Duration(Item): 447 """A written duration""" 448 449 450class Durable(Item): 451 """An Item that has a musical duration, in the duration attribute.""" 452 duration = 0, 1 # two Fractions: (base, scaling) 453 454 def length(self): 455 """Return the musical duration (our base * our scaling).""" 456 base, scaling = self.duration 457 return base * scaling 458 459 def events(self, e, time, scaling): 460 """Let the event.Events instance handle the events. Return the time.""" 461 return time + self.duration[0] * self.duration[1] * scaling 462 463 464class Chord(Durable, Container): 465 pass 466 467 468class Unpitched(Durable): 469 """A "note" without pitch, just a standalone duration.""" 470 pitch = None 471 472 473class Note(Durable): 474 """A Note that has a ly.pitch.Pitch""" 475 pitch = None 476 octave_token = None 477 accidental_token = None 478 octavecheck_token = None 479 480 481class Skip(Durable): 482 pass 483 484 485class Rest(Durable): 486 pass 487 488 489class Q(Durable): 490 pass 491 492 493class DrumNote(Durable): 494 pass 495 496 497class Music(Container): 498 """Any music expression, to be inherited of.""" 499 def events(self, e, time, scaling): 500 """Let the event.Events instance handle the events. Return the time.""" 501 for node in self: 502 time = e.traverse(node, time, scaling) 503 return time 504 505 def length(self): 506 """Return the musical duration.""" 507 from . import event 508 return event.Events().read(self) 509 510 def preceding(self, node=None): 511 """Return a two-tuple (nodes, scaling). 512 513 The nodes are the nodes in time before the node (which must be a 514 child), and the scaling is the scaling this node applies (normally 1). 515 516 If node is None, all nodes that would precede a fictive node at the 517 end are returned. 518 519 """ 520 i = self.index(node) if node else None 521 return self[:i:], 1 522 523 524class MusicList(Music): 525 """A music expression, either << >> or { }.""" 526 simultaneous = False 527 528 def events(self, e, time, scaling): 529 """Let the event.Events instance handle the events. Return the time.""" 530 if self.simultaneous: 531 if len(self): 532 time = max(e.traverse(node, time, scaling) for node in self) 533 else: 534 time = super(MusicList, self).events(e, time, scaling) 535 return time 536 537 def preceding(self, node=None): 538 """Return a two-tuple (nodes, scaling). 539 540 The nodes are the nodes in time before the node (which must be a 541 child), and the scaling is the scaling this node applies (normally 1). 542 543 If node is None, all nodes that would precede a fictive node at the 544 end are returned. 545 546 """ 547 if self.simultaneous: 548 return [], 1 549 return super(MusicList, self).preceding(node) 550 551 552class Tag(Music): 553 r"""A \tag, \keepWithTag or \removeWithTag command.""" 554 555 def events(self, e, time, scaling): 556 """Let the event.Events instance handle the events. Return the time.""" 557 for node in self[-1:]: 558 time = e.traverse(node, time, scaling) 559 return time 560 561 def preceding(self, node=None): 562 """Return a two-tuple (nodes, scaling). 563 564 The nodes are the nodes in time before the node (which must be a 565 child), and the scaling is the scaling this node applies (normally 1). 566 567 If node is None, all nodes that would precede a fictive node at the 568 end are returned. 569 570 """ 571 return [], 1 572 573 574class Scaler(Music): 575 r"""A music construct that scales the duration of its contents. 576 577 In the numerator and denominator attributes the values specified for 578 LilyPond are stored, e.g. with \times 3/2 { c d e }, the numerator is 579 integer 3 and the denominator is integer 2. Note that for \tuplet and 580 \times the meaning of these numbers is reversed. 581 582 The algebraic scaling is stored in the scaling attribute. 583 584 """ 585 scaling = 1 586 587 numerator = 0 588 denominator = 0 589 590 def events(self, e, time, scaling): 591 """Let the event.Events instance handle the events. Return the time.""" 592 return super(Scaler, self).events(e, time, scaling * self.scaling) 593 594 def preceding(self, node=None): 595 """Return a two-tuple (nodes, scaling). 596 597 The nodes are the nodes in time before the node (which must be a 598 child), and the scaling is the scaling this node applies. 599 600 If node is None, all nodes that would precede a fictive node at the 601 end are returned. 602 603 """ 604 i = self.index(node) if node else None 605 return self[:i:], self.scaling 606 607 608class Grace(Music): 609 """Music that has grace timing, i.e. 0 as far as computation is concerned.""" 610 611 def events(self, e, time, scaling): 612 """Let the event.Events instance handle the events. Return the time.""" 613 return super(Grace, self).events(e, time, 0) 614 615 def preceding(self, node=None): 616 """Return a two-tuple (nodes, scaling). 617 618 The nodes are the nodes in time before the node (which must be a 619 child), and the scaling is 0 for (because we have grace notes). 620 621 If node is None, all nodes that would precede a fictive node at the 622 end are returned. 623 624 """ 625 i = self.index(node) if node else None 626 return self[:i:], 0 627 628 629class AfterGrace(Music): 630 r"""The \afterGrace function with its two arguments. 631 632 Only the duration of the first is counted. 633 634 """ 635 636 637class PartCombine(Music): 638 r"""The \partcombine command with 2 music arguments.""" 639 def events(self, e, time, scaling): 640 """Let the event.Events instance handle the events. Return the time.""" 641 if len(self): 642 time = max(e.traverse(node, time, scaling) for node in self) 643 return time 644 645 def preceding(self, node=None): 646 """Return a two-tuple (nodes, scaling). 647 648 The nodes are the nodes in time before the node (which must be a 649 child), and the scaling is the scaling this node applies (normally 1). 650 651 If node is None, all nodes that would precede a fictive node at the 652 end are returned. 653 654 """ 655 return [], 1 656 657 658class Relative(Music): 659 r"""A \relative music expression. Has one or two children (Note, Music).""" 660 pass 661 662 663class Absolute(Music): 664 r"""An \absolute music expression. Has one child (normally Music).""" 665 pass 666 667 668class Transpose(Music): 669 r"""A \transpose music expression. Has normally three children (Note, Note, Music).""" 670 671 672class Repeat(Music): 673 r"""A \repeat expression.""" 674 def specifier(self): 675 if isinstance(self._specifier, Scheme): 676 return self._specifier.get_string() 677 elif isinstance(self._specifier, String): 678 return self._specifier.value() 679 return self._specifier 680 681 def repeat_count(self): 682 if isinstance(self._repeat_count, Scheme): 683 return self._repeat_count.get_int() or 1 684 return int(self._repeat_count or '1') or 1 685 686 def events(self, e, time, scaling): 687 """Let the event.Events instance handle the events. Return the time.""" 688 if len(self) and isinstance(self[-1], Alternative): 689 alt = self[-1] 690 children = self[:-1] 691 else: 692 alt = None 693 children = self[:] 694 695 if e.unfold_repeats or self.specifier() != "volta": 696 count = self.repeat_count() 697 if alt and len(alt) and len(alt[0]): 698 alts = list(alt[0])[:count+1] 699 alts[0:0] = [alts[0]] * (count - len(alts)) 700 for a in alts: 701 for n in children: 702 time = e.traverse(n, time, scaling) 703 time = e.traverse(a, time, scaling) 704 else: 705 for i in range(count): 706 for n in children: 707 time = e.traverse(n, time, scaling) 708 else: 709 for n in children: 710 time = e.traverse(n, time, scaling) 711 if alt: 712 time = e.traverse(alt, time, scaling) 713 return time 714 715 716class Alternative(Music): 717 r"""An \alternative expression.""" 718 719 720class InputMode(Music): 721 """Base class for inputmode-changing commands.""" 722 723 724class NoteMode(InputMode): 725 r"""A \notemode or \notes expression.""" 726 727 728class ChordMode(InputMode): 729 r"""A \chordmode or \chords expression.""" 730 731 732class DrumMode(InputMode): 733 r"""A \drummode or \drums expression.""" 734 735 736class FigureMode(InputMode): 737 r"""A \figuremode or \figures expression.""" 738 739 740class LyricMode(InputMode): 741 r"""A \lyricmode, \lyrics or \addlyrics expression.""" 742 743 744class LyricsTo(InputMode): 745 r"""A \lyricsto expression.""" 746 _context_id = None 747 748 def context_id(self): 749 if isinstance(self._context_id, String): 750 return self._context_id.value() 751 elif isinstance(self._context_id, Scheme): 752 return self._context_id.get_string() 753 return self._context_id 754 755 756class LyricText(Durable): 757 """A lyric text (word, markup or string), with a Duration.""" 758 759 760class LyricItem(Item): 761 """Another lyric item (skip, extender, hyphen or tie).""" 762 763 764class ChordSpecifier(Item): 765 """Chord specifications after a note in chord mode. 766 767 Has children of Note or ChordItem class. 768 769 """ 770 771 772class ChordItem(Item): 773 """An item inside a ChordSpecifier, e.g. a number or modifier.""" 774 775 776class Tremolo(Item): 777 """A tremolo item ":". The duration attribute is a tuple (base, scaling).""" 778 duration = 0, 1 779 780 781class Translator(Item): 782 r"""Base class for a \change, \new, or \context music expression.""" 783 _context = None 784 _context_id = None 785 786 def context(self): 787 return self._context 788 789 def context_id(self): 790 """The context id, if specified after an equal sign.""" 791 if isinstance(self._context_id, String): 792 return self._context_id.value() 793 return self._context_id 794 795 796class Context(Translator, Music): 797 r"""A \new or \context music expression.""" 798 799 800class Change(Translator): 801 r"""A \change music expression.""" 802 803 804class Tempo(Item): 805 duration = 0, 1 806 807 def fraction(self): 808 """Return the note value as a fraction given before the equal sign.""" 809 base, scaling = self.duration # (scaling will normally be 1) 810 return base * scaling 811 812 def text(self): 813 """Return the text, if set. Can be Markup, Scheme, or String.""" 814 for i in self: 815 if isinstance(i, (Markup, Scheme, String)): 816 return i 817 return 818 819 def tempo(self): 820 """Return a list of integer values describing the tempo or range.""" 821 nodes = iter(self) 822 result = [] 823 for i in nodes: 824 if isinstance(i, Duration): 825 for i in nodes: 826 if isinstance(i, Scheme): 827 v = i.get_int() 828 if v is not None: 829 result.append(v) 830 elif isinstance(i, Number): 831 result.append(i.value()) 832 return result 833 834 835class TimeSignature(Item): 836 r"""A \time command.""" 837 _num = 4 838 _fraction = Fraction(1, 4) 839 _beatstructure = None 840 841 def measure_length(self): 842 """The length of one measure in this time signature as a Fraction.""" 843 return self._num * self._fraction 844 845 def numerator(self): 846 """The upper number (e.g. for 3/2 it returns 3).""" 847 return self._num 848 849 def fraction(self): 850 """The lower number as a Fraction (e.g. for 3/2 it returns 1/2).""" 851 return self._fraction 852 853 def beatstructure(self): 854 """The scheme expressions denoting the beat structure, if specified.""" 855 return self._beatstructure 856 857 858class Partial(Item): 859 r"""A \partial command.""" 860 duration = 0, 1 861 862 def partial_length(self): 863 """Return the duration given as argument as a Fraction.""" 864 base, scaling = self.duration 865 return base * scaling 866 867 868class Clef(Item): 869 r"""A \clef item.""" 870 _specifier = None 871 872 def specifier(self): 873 if isinstance(self._specifier, String): 874 return self._specifier.value() 875 return self._specifier 876 877 878class KeySignature(Item): 879 r"""A \key pitch \mode command.""" 880 def pitch(self): 881 """The ly.pitch.Pitch that denotes the pitch.""" 882 for i in self.find(Note): 883 return i.pitch 884 885 def mode(self): 886 """The mode, e.g. "major", "minor", etc.""" 887 for i in self.find(Command): 888 return i.token[1:] 889 890 891class PipeSymbol(Item): 892 r"""A pipe symbol: |""" 893 894 895class VoiceSeparator(Item): 896 r"""A voice separator: \\""" 897 898 899class Postfix(Item): 900 """Any item that is prefixed with a _, - or ^ direction token.""" 901 902 903class Tie(Item): 904 """A tie.""" 905 906 907class Slur(Item): 908 """A ( or ).""" 909 event = None 910 911 912class PhrasingSlur(Item): 913 r"""A \( or \).""" 914 event = None 915 916 917class Beam(Item): 918 """A [ or ].""" 919 event = None 920 921 922class Dynamic(Item): 923 """Any dynamic symbol.""" 924 925 926class Articulation(Item): 927 """An articulation, fingering, string number, or other symbol.""" 928 929 930class StringTuning(Item): 931 r"""A \stringTuning command (with a chord as argument).""" 932 933 934class Keyword(Item): 935 """A LilyPond keyword.""" 936 937 938class Command(Item): 939 """A LilyPond command.""" 940 941 942class UserCommand(Music): 943 """A user command, most probably referring to music.""" 944 def name(self): 945 """Return the name of this user command (without the leading backslash).""" 946 return self.token[1:] 947 948 def value(self): 949 """Find the value assigned to this variable.""" 950 for i in self.iter_toplevel_items_include(): 951 if isinstance(i, Assignment) and i.name() == self.name(): 952 return i.value() 953 954 def events(self, e, time, scaling): 955 """Let the event.Events instance handle the events. Return the time.""" 956 value = self.value() 957 if value: 958 time = e.traverse(value, time, scaling) 959 return time 960 961 962class Version(Item): 963 r"""A \version command.""" 964 def version_string(self): 965 """The version as a string.""" 966 for i in self: 967 if isinstance(i, String): 968 return i.value() 969 elif isinstance(i, Scheme): 970 return i.get_string() 971 return '' 972 973 def version(self): 974 """The version as a tuple of ints.""" 975 return tuple(map(int, re.findall(r'\d+', self.version_string()))) 976 977 978class Include(Item): 979 r"""An \include command (not changing the language).""" 980 def filename(self): 981 """Returns the filename.""" 982 for i in self: 983 if isinstance(i, String): 984 return i.value() 985 elif isinstance(i, Scheme): 986 return i.get_string() 987 988 989class Language(Item): 990 r"""A command (\language or certain \include commands) that changes the pitch language.""" 991 language = None 992 993 994class Markup(Item): 995 r"""A command starting markup (\markup, -lines and -list).""" 996 def plaintext(self): 997 """Return the plain text value of this node.""" 998 return ' '.join(n.plaintext() for n in self) 999 1000 1001class MarkupCommand(Item): 1002 r"""A markup command, such as \italic etc.""" 1003 def plaintext(self): 1004 """Return the plain text value of this node.""" 1005 if self.token == '\\concat': 1006 joiner = '' 1007 #elif 'column' in self.token: 1008 #joiner = '\n' 1009 else: 1010 joiner = ' ' 1011 if len(self) == 1 and isinstance(self[0], MarkupList): 1012 node = self[0] 1013 else: 1014 node = self 1015 return joiner.join(n.plaintext() for n in node) 1016 1017 1018class MarkupUserCommand(Item): 1019 """A user-defined markup command""" 1020 def name(self): 1021 """Return the name of this user command (without the leading backslash).""" 1022 return self.token[1:] 1023 1024 def value(self): 1025 """Find the value assigned to this variable.""" 1026 for i in self.iter_toplevel_items_include(): 1027 if isinstance(i, Assignment) and i.name() == self.name(): 1028 return i.value() 1029 elif isinstance(i, Scheme): 1030 for j in i: 1031 if isinstance(j, SchemeList): 1032 for k in j: 1033 if isinstance(k, SchemeItem) and k.token == 'define-markup-command': 1034 for l in j[1::]: 1035 if isinstance(l, SchemeList): 1036 for m in l: 1037 if isinstance(m, SchemeItem) and m.token == self.name(): 1038 return i 1039 break 1040 break 1041 break 1042 break 1043 1044 1045class MarkupScore(Item): 1046 r"""A \score inside Markup.""" 1047 1048 1049class MarkupList(Item): 1050 r"""The group of markup items inside { and }. NOTE: *not* a \markuplist.""" 1051 def plaintext(self): 1052 """Return the plain text value of this node.""" 1053 return ' '.join(n.plaintext() for n in self) 1054 1055 1056class MarkupWord(Item): 1057 """A MarkupWord token.""" 1058 def plaintext(self): 1059 return self.token 1060 1061 1062class Assignment(Item): 1063 """A variable = value construct.""" 1064 def name(self): 1065 """The variable name.""" 1066 return self.token 1067 1068 def value(self): 1069 """The assigned value.""" 1070 if len(self): 1071 return self[-1] 1072 1073 1074class Book(Container): 1075 r"""A \book { ... } construct.""" 1076 1077 1078class BookPart(Container): 1079 r"""A \bookpart { ... } construct.""" 1080 1081 1082class Score(Container): 1083 r"""A \score { ... } construct.""" 1084 1085 1086class Header(Container): 1087 r"""A \header { ... } construct.""" 1088 1089 1090class Paper(Container): 1091 r"""A \paper { ... } construct.""" 1092 1093 1094class Layout(Container): 1095 r"""A \layout { ... } construct.""" 1096 1097 1098class Midi(Container): 1099 r"""A \midi { ... } construct.""" 1100 1101 1102class LayoutContext(Container): 1103 r"""A \context { ... } construct within Layout or Midi.""" 1104 1105 1106class With(Container): 1107 r"""A \with ... construct.""" 1108 1109 1110class Set(Item): 1111 r"""A \set command.""" 1112 def context(self): 1113 """The context, if specified.""" 1114 for t in self.tokens: 1115 if isinstance(t, lilypond.ContextName): 1116 return t 1117 1118 def property(self): 1119 """The property.""" 1120 for t in self.tokens: 1121 if isinstance(t, lilypond.ContextProperty): 1122 return t 1123 for t in self.tokens[::-1]: 1124 if isinstance(t, lilypond.Name): 1125 return t 1126 1127 def value(self): 1128 """The value, given as argument. This is simply the child element.""" 1129 for i in self: 1130 return i 1131 1132 1133class Unset(Item): 1134 """An \\unset command.""" 1135 def context(self): 1136 """The context, if specified.""" 1137 for t in self.tokens: 1138 if isinstance(t, lilypond.ContextName): 1139 return t 1140 1141 def property(self): 1142 """The property.""" 1143 for t in self.tokens: 1144 if isinstance(t, lilypond.ContextProperty): 1145 return t 1146 for t in self.tokens[::-1]: 1147 if isinstance(t, lilypond.Name): 1148 return t 1149 1150 1151class Override(Item): 1152 r"""An \override command.""" 1153 def context(self): 1154 for i in self: 1155 if isinstance(i.token, lilypond.ContextName): 1156 return i.token 1157 1158 def grob(self): 1159 for i in self: 1160 if isinstance(i.token, lilypond.GrobName): 1161 return i.token 1162 1163 1164class Revert(Item): 1165 r"""A \revert command.""" 1166 def context(self): 1167 for i in self: 1168 if isinstance(i.token, lilypond.ContextName): 1169 return i.token 1170 1171 def grob(self): 1172 for i in self: 1173 if isinstance(i.token, lilypond.GrobName): 1174 return i.token 1175 1176 1177class Tweak(Item): 1178 r"""A \tweak command.""" 1179 1180 1181class PathItem(Item): 1182 r"""An item in the path of an \override or \revert command.""" 1183 1184 1185class String(Item): 1186 """A double-quoted string.""" 1187 1188 def plaintext(self): 1189 """Return the plaintext value of this string, without escapes and quotes.""" 1190 # TEMP use the value(), must become token independent. 1191 return self.value() 1192 1193 def value(self): 1194 return ''.join( 1195 t[1:] if isinstance(t, lex.Character) and t.startswith('\\') else t 1196 for t in self.tokens[:-1]) 1197 1198 1199class Number(Item): 1200 """A numerical value, directly entered.""" 1201 def value(self): 1202 if isinstance(self.token, lilypond.IntegerValue): 1203 return int(self.token) 1204 elif isinstance(self.token, lilypond.DecimalValue): 1205 return float(self.token) 1206 elif isinstance(self.token, lilypond.Fraction): 1207 return Fraction(self.token) 1208 elif self.token.isdigit(): 1209 return int(self.token) 1210 1211 1212class Scheme(Item): 1213 """A Scheme expression inside LilyPond.""" 1214 def plaintext(self): 1215 """A crude way to get the plain text in this node.""" 1216 # TEMP use get_string() 1217 return self.get_string() 1218 1219 def get_pair_ints(self): 1220 """Very basic way to get two integers specified as a pair.""" 1221 result = [int(i.token) for i in self.find(SchemeItem) if i.token.isdigit()] 1222 if len(result) >= 2: 1223 return tuple(result[:2]) 1224 1225 def get_list_ints(self): 1226 """A basic way to get a list of integer values.""" 1227 return [int(i.token) for i in self.find(SchemeItem) if i.token.isdigit()] 1228 1229 def get_int(self): 1230 """A basic way to get one integer value.""" 1231 for i in self.find(SchemeItem): 1232 if i.token.isdigit(): 1233 return int(i.token) 1234 1235 def get_fraction(self): 1236 """A basic way to get one (may be fractional) numerical value.""" 1237 for i in self.find(SchemeItem): 1238 if i.token.isdigit(): 1239 return int(i.token) 1240 elif isinstance(i.token, scheme.Fraction): 1241 return Fraction(i.token) 1242 1243 def get_string(self): 1244 """A basic way to get a quoted string value (without the quotes).""" 1245 return ''.join(i.value() for i in self.find(String)) 1246 1247 def get_ly_make_moment(self): 1248 """A basic way to get a ly:make-moment fraction.""" 1249 tokens = [i.token for i in self.find(SchemeItem)] 1250 if len(tokens) == 3 and tokens[0] == 'ly:make-moment': 1251 if tokens[1].isdigit() and tokens[2].isdigit(): 1252 return Fraction(int(tokens[1]), int(tokens[2])) 1253 1254 1255class SchemeItem(Item): 1256 """Any scheme token.""" 1257 1258 1259class SchemeList(Container): 1260 """A ( ... ) expression.""" 1261 1262 1263class SchemeQuote(Item): 1264 """A ' in scheme.""" 1265 1266 1267class SchemeLily(Container): 1268 """A music expression inside #{ and #}.""" 1269 1270 1271 1272