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