Source code for ly.music.items

# This file is part of python-ly, https://pypi.python.org/pypi/python-ly
#
# Copyright (c) 2014 - 2015 by Wilbert Berendsen
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
# See http://www.gnu.org/licenses/ for more information.

"""
The items a music expression is constructed with in a tree structure.

Whitespace and comments are left out.

All nodes (instances of Item) have a 'position' attribute that indicates 
where the item starts in the source text. Almost all items have the token 
that starts the expression in the 'token' attribute and possibly other 
tokens in the 'tokens' attribute, as a tuple. 

The 'end_position()' method returns the position where the node (including 
its child nodes) ends.

You can get the whole tree structure of a LilyPond document by instantiating
a Document with the ly.document.Document instance. (It will read all the tokens
from the document using the Reader from the read module.) As a convenience,
the ly.music.document(doc) function does this.

If you want to add new Item types, you should also add a method to read.Reader
to construct those items.

"""

from __future__ import unicode_literals

from fractions import Fraction
import re

import ly.node
from ly import lex
from ly.lex import lilypond
from ly.lex import scheme


[docs]class Item(ly.node.WeakNode): """Represents any item in the music of a document. This can be just a token, or an interpreted construct such as a note, rest or sequential or simultaneous construct , etc. Some Item instances just have one responsible token, but others have a list or tuple to tokens. An Item also has a pointer to the Document it originates from. """ document = None tokens = () token = None position = -1 def __repr__(self): s = ' ' + repr(self.token[:]) if self.token else '' return '<{0}{1}>'.format(self.__class__.__name__, s)
[docs] def plaintext(self): """Return a plaintext value for this node. This only makes sense for items like Markup or String. For other types, an empty string is returned """ return ""
[docs] def end_position(self): """Return the end position of this node.""" def ends(): if self.tokens: yield self.tokens[-1].end elif self.token: yield self.token.end else: yield self.position if len(self): # end pos of the last child yield self[-1].end_position() # end pos of Item or Token instances in attributes, such as duration etc for i in vars(self).values(): if isinstance(i, Item): yield i.end_position() elif isinstance(i, lex.Token): yield i.end return max(ends())
[docs] def events(self, e, time, scaling): """Let the event.Events instance handle the events. Return the time.""" return time
[docs] def length(self): """Return the musical duration.""" return 0
[docs] def iter_toplevel_items(self): """Yield the toplevel items of our Document node in backward direction. Iteration starts with the node just before the node "self" is a descendant of. """ node = self for doc in self.ancestors(): if isinstance(doc, Document): break node = doc else: return # now, doc is the Document node, and node is the child of the Document # node we are a (far) descendant of for i in node.backward(): yield i # look in parent Document before the place we were included while doc.include_node: p = doc.include_node.parent() if isinstance(p, Document): for i in doc.include_node.backward(): yield i doc = p else: break
[docs] def iter_toplevel_items_include(self): r"""Same as iter_toplevel_items(), but follows \include commands.""" def follow(it): for i in it: if isinstance(i, Include): doc = i.parent().get_included_document_node(i) if doc: for i in follow(doc[::-1]): yield i else: yield i return follow(self.iter_toplevel_items())
[docs] def music_parent(self): """Walk up the parent tree until Music is found; return the outermost Music node. Returns None is the node does not belong to any music expression (e.g. a toplevel Markup or Scheme object). """ node = self mus = isinstance(node, Music) for p in self.ancestors(): pmus = isinstance(p, Music) if mus and not pmus: return node mus = pmus node = p
[docs] def music_children(self, depth=-1): """Yield all the children that are new music expressions (i.e. that are inside other constructions). """ def find(node, depth): if depth != 0: if isinstance(node, Music): for i in node: for i in find(i, depth-1): yield i else: for i in node: if isinstance(i, Music): yield i else: for i in find(i, depth-1): yield i return find(self, depth)
[docs] def has_output(self, _seen_docs=None): """Return True if this node has toplevel music, markup, book etc. I.e. returns True when LilyPond would likely generate output. Usually you'll call this method on a Document, Score, BookPart or Book node. You should not supply the _seen_docs argument; it is used internally to avoid traversing recursively nested include files. """ if _seen_docs is None: _seen_docs = set() _seen_docs.add(self) for n in self: if isinstance(n, (Music, Markup)): return True elif isinstance(n, (Book, BookPart, Score)): if n.has_output(_seen_docs): return True elif isinstance(n, Include): doc = self.toplevel().get_included_document_node(n) if doc and doc not in _seen_docs and doc.has_output(_seen_docs): return True
[docs]class Document(Item): """A toplevel item representing a ly.document.Document.""" def __init__(self, doc): super(Document, self).__init__() self.document = doc self.include_node = None self.include_path = [] self.relative_includes = True import ly.document c = ly.document.Cursor(doc) s = ly.document.Source(c, True, tokens_with_position=True) from .read import Reader r = Reader(s) self.extend(r.read())
[docs] def node(self, position, depth=-1): """Return the node at or just before the specified position.""" def bisect(n, depth): end = len(n) if depth == 0 or end == 0: return n pos = 0 while pos < end: mid = (pos + end) // 2 if position < n[mid].position: end = mid else: pos = mid + 1 pos -= 1 if n[pos].position == position: return n[pos] elif n[pos].position > position: return n return bisect(n[pos], depth - 1) return bisect(self, depth)
[docs] def music_events_til_position(self, position): """Return a list of tuples. Every tuple is a (parent, nodes, scaling). If an empty list is returned, there is no music expression at this position. """ node = self.node(position) # be nice and allow including an assignment if (isinstance(node, Assignment) and node.parent() is self and isinstance(node.value(), Music)): return [(node, [], 1)] if isinstance(node.parent(), Chord): node = node.parent() l = [] mus = isinstance(node, (Music, Durable)) if mus: l.append((node, [], 1)) for p in node.ancestors(): pmus = isinstance(p, Music) end = node.end_position() if pmus: if position > end: preceding, s = p.preceding(node.next_sibling()) l = [(p, preceding, s)] elif position == end: preceding, s = p.preceding(node) l = [(p, preceding + [node], s)] else: preceding, s = p.preceding(node) l.append((p, preceding, s)) elif mus: # we are at the musical top if position > end: return [] elif position == end: l = [(p, [node], 1)] else: l.append((p, [], 1)) break node = p mus = pmus l.reverse() return l
[docs] def time_position(self, position): """Return the time position in the music at the specified cursor position. The value is a fraction. If None is returned, we are not in a music expression. """ events = self.music_events_til_position(position) if events: from . import event e = event.Events() time = 0 scaling = 1 for parent, nodes, s in events: scaling *= s for n in nodes: time = e.traverse(n, time, scaling) return time
[docs] def time_length(self, start, end): """Return the length of the music between start and end positions. Returns None if start and end are not in the same expression. """ def mk_list(evts): """Make a flat list of all the events.""" l = [] scaling = 1 for p, nodes, s in evts: scaling *= s for n in nodes: l.append((n, scaling)) return l if start > end: start, end = end, start start_evts = self.music_events_til_position(start) if start_evts: end_evts = self.music_events_til_position(end) if end_evts and start_evts[0][0] is end_evts[0][0]: # yes, we have the same toplevel expression. start_evts = mk_list(start_evts) end_evts = mk_list(end_evts) from . import event e = event.Events() time = 0 i = 0 # traverse the common events only once for i, ((evt, s), (end_evt, end_s)) in enumerate(zip(start_evts, end_evts)): if evt is end_evt: time = e.traverse(evt, time, s) else: break end_time = time # handle the remaining events for the start position for evt, s in start_evts[i::]: time = e.traverse(evt, time, s) # handle the remaining events for the end position for evt, s in end_evts[i::]: end_time = e.traverse(evt, end_time, s) return end_time - time
[docs] def substitute_for_node(self, node): """Returns a node that replaces the specified node (e.g. in music). For example: a variable reference returns its value. Returns nothing if the node is not substitutable. Returns the node itself if it was substitutable, but the substitution failed. """ if isinstance(node, UserCommand): value = node.value() if value: return self.substitute_for_node(value) or value return node elif isinstance(node, Include): return self.get_included_document_node(node) or node
# maybe other substitutions
[docs] def iter_music(self, node=None): """Iter over the music, following references to other assignments.""" for n in node or self: n = self.substitute_for_node(n) or n yield n for n in self.iter_music(n): yield n
[docs] def get_included_document_node(self, node): """Return a Document for the Include node. May return None.""" try: return node._document except AttributeError: node._document = None filename = node.filename() if filename: resolved = self.resolve_filename(filename) if resolved: docnode = self.get_music(resolved) docnode.include_node = node docnode.include_path = self.include_path node._document = docnode return node._document
[docs] def resolve_filename(self, filename): """Resolve filename against our document and include_path.""" import os if os.path.isabs(filename): return filename path = list(self.include_path) if self.document.filename: basedir = os.path.dirname(self.document.filename) try: path.remove(basedir) except ValueError: pass path.insert(0, basedir) for p in path: fullpath = os.path.join(p, filename) if os.path.exists(fullpath): return fullpath
[docs] def get_music(self, filename): """Return the music Document for the specified filename. This implementation loads a ly.document.Document using utf-8 encoding. Inherit from this class to implement other loading mechanisms or caching. """ import ly.document return type(self)(ly.document.Document.load(filename))
[docs]class Token(Item): """Any token that is not otherwise recognized"""
[docs]class Container(Item): """An item having a list of child items."""
[docs]class Duration(Item): """A written duration"""
[docs]class Durable(Item): """An Item that has a musical duration, in the duration attribute.""" duration = 0, 1 # two Fractions: (base, scaling)
[docs] def length(self): """Return the musical duration (our base * our scaling).""" base, scaling = self.duration return base * scaling
[docs] def events(self, e, time, scaling): """Let the event.Events instance handle the events. Return the time.""" return time + self.duration[0] * self.duration[1] * scaling
[docs]class Chord(Durable, Container): pass
[docs]class Unpitched(Durable): """A "note" without pitch, just a standalone duration.""" pitch = None
[docs]class Note(Durable): """A Note that has a ly.pitch.Pitch""" pitch = None octave_token = None accidental_token = None octavecheck_token = None
[docs]class Rest(Durable): pass
[docs]class Q(Durable): pass
[docs]class DrumNote(Durable): pass
[docs]class Music(Container): """Any music expression, to be inherited of."""
[docs] def events(self, e, time, scaling): """Let the event.Events instance handle the events. Return the time.""" for node in self: time = e.traverse(node, time, scaling) return time
[docs] def length(self): """Return the musical duration.""" from . import event return event.Events().read(self)
[docs] def preceding(self, node=None): """Return a two-tuple (nodes, scaling). The nodes are the nodes in time before the node (which must be a child), and the scaling is the scaling this node applies (normally 1). If node is None, all nodes that would precede a fictive node at the end are returned. """ i = self.index(node) if node else None return self[:i:], 1
[docs]class MusicList(Music): """A music expression, either << >> or { }.""" simultaneous = False
[docs] def events(self, e, time, scaling): """Let the event.Events instance handle the events. Return the time.""" if self.simultaneous: if len(self): time = max(e.traverse(node, time, scaling) for node in self) else: time = super(MusicList, self).events(e, time, scaling) return time
[docs] def preceding(self, node=None): """Return a two-tuple (nodes, scaling). The nodes are the nodes in time before the node (which must be a child), and the scaling is the scaling this node applies (normally 1). If node is None, all nodes that would precede a fictive node at the end are returned. """ if self.simultaneous: return [], 1 return super(MusicList, self).preceding(node)
[docs]class Tag(Music): r"""A \tag, \keepWithTag or \removeWithTag command."""
[docs] def events(self, e, time, scaling): """Let the event.Events instance handle the events. Return the time.""" for node in self[-1:]: time = e.traverse(node, time, scaling) return time
[docs] def preceding(self, node=None): """Return a two-tuple (nodes, scaling). The nodes are the nodes in time before the node (which must be a child), and the scaling is the scaling this node applies (normally 1). If node is None, all nodes that would precede a fictive node at the end are returned. """ return [], 1
[docs]class Scaler(Music): r"""A music construct that scales the duration of its contents. In the numerator and denominator attributes the values specified for LilyPond are stored, e.g. with \times 3/2 { c d e }, the numerator is integer 3 and the denominator is integer 2. Note that for \tuplet and \times the meaning of these numbers is reversed. The algebraic scaling is stored in the scaling attribute. """ scaling = 1 numerator = 0 denominator = 0
[docs] def events(self, e, time, scaling): """Let the event.Events instance handle the events. Return the time.""" return super(Scaler, self).events(e, time, scaling * self.scaling)
[docs] def preceding(self, node=None): """Return a two-tuple (nodes, scaling). The nodes are the nodes in time before the node (which must be a child), and the scaling is the scaling this node applies. If node is None, all nodes that would precede a fictive node at the end are returned. """ i = self.index(node) if node else None return self[:i:], self.scaling
[docs]class Grace(Music): """Music that has grace timing, i.e. 0 as far as computation is concerned."""
[docs] def events(self, e, time, scaling): """Let the event.Events instance handle the events. Return the time.""" return super(Grace, self).events(e, time, 0)
[docs] def preceding(self, node=None): """Return a two-tuple (nodes, scaling). The nodes are the nodes in time before the node (which must be a child), and the scaling is 0 for (because we have grace notes). If node is None, all nodes that would precede a fictive node at the end are returned. """ i = self.index(node) if node else None return self[:i:], 0
[docs]class AfterGrace(Music): r"""The \afterGrace function with its two arguments. Only the duration of the first is counted. """
[docs]class PartCombine(Music): r"""The \partcombine command with 2 music arguments."""
[docs] def events(self, e, time, scaling): """Let the event.Events instance handle the events. Return the time.""" if len(self): time = max(e.traverse(node, time, scaling) for node in self) return time
[docs] def preceding(self, node=None): """Return a two-tuple (nodes, scaling). The nodes are the nodes in time before the node (which must be a child), and the scaling is the scaling this node applies (normally 1). If node is None, all nodes that would precede a fictive node at the end are returned. """ return [], 1
[docs]class Relative(Music): r"""A \relative music expression. Has one or two children (Note, Music).""" pass
[docs]class Absolute(Music): r"""An \absolute music expression. Has one child (normally Music).""" pass
[docs]class Transpose(Music): r"""A \transpose music expression. Has normally three children (Note, Note, Music)."""
[docs]class Repeat(Music): r"""A \repeat expression."""
[docs] def specifier(self): if isinstance(self._specifier, Scheme): return self._specifier.get_string() elif isinstance(self._specifier, String): return self._specifier.value() return self._specifier
[docs] def repeat_count(self): if isinstance(self._repeat_count, Scheme): return self._repeat_count.get_int() or 1 return int(self._repeat_count or '1') or 1
[docs] def events(self, e, time, scaling): """Let the event.Events instance handle the events. Return the time.""" if len(self) and isinstance(self[-1], Alternative): alt = self[-1] children = self[:-1] else: alt = None children = self[:] if e.unfold_repeats or self.specifier() != "volta": count = self.repeat_count() if alt and len(alt) and len(alt[0]): alts = list(alt[0])[:count+1] alts[0:0] = [alts[0]] * (count - len(alts)) for a in alts: for n in children: time = e.traverse(n, time, scaling) time = e.traverse(a, time, scaling) else: for i in range(count): for n in children: time = e.traverse(n, time, scaling) else: for n in children: time = e.traverse(n, time, scaling) if alt: time = e.traverse(alt, time, scaling) return time
[docs]class Alternative(Music): r"""An \alternative expression."""
[docs]class InputMode(Music): """Base class for inputmode-changing commands."""
[docs]class NoteMode(InputMode): r"""A \notemode or \notes expression."""
[docs]class ChordMode(InputMode): r"""A \chordmode or \chords expression."""
[docs]class DrumMode(InputMode): r"""A \drummode or \drums expression."""
[docs]class FigureMode(InputMode): r"""A \figuremode or \figures expression."""
[docs]class LyricMode(InputMode): r"""A \lyricmode, \lyrics or \addlyrics expression."""
[docs]class LyricsTo(InputMode): r"""A \lyricsto expression.""" _context_id = None
[docs] def context_id(self): if isinstance(self._context_id, String): return self._context_id.value() elif isinstance(self._context_id, Scheme): return self._context_id.get_string() return self._context_id
[docs]class LyricText(Durable): """A lyric text (word, markup or string), with a Duration."""
[docs]class LyricItem(Item): """Another lyric item (skip, extender, hyphen or tie)."""
[docs]class ChordSpecifier(Item): """Chord specifications after a note in chord mode. Has children of Note or ChordItem class. """
[docs]class ChordItem(Item): """An item inside a ChordSpecifier, e.g. a number or modifier."""
[docs]class Tremolo(Item): """A tremolo item ":". The duration attribute is a tuple (base, scaling).""" duration = 0, 1
[docs]class Translator(Item): r"""Base class for a \change, \new, or \context music expression.""" _context = None _context_id = None
[docs] def context(self): return self._context
[docs] def context_id(self): """The context id, if specified after an equal sign.""" if isinstance(self._context_id, String): return self._context_id.value() return self._context_id
[docs]class Context(Translator, Music): r"""A \new or \context music expression."""
[docs]class Change(Translator): r"""A \change music expression."""
[docs]class Tempo(Item): duration = 0, 1
[docs] def fraction(self): """Return the note value as a fraction given before the equal sign.""" base, scaling = self.duration # (scaling will normally be 1) return base * scaling
[docs] def text(self): """Return the text, if set. Can be Markup, Scheme, or String.""" for i in self: if isinstance(i, (Markup, Scheme, String)): return i return
[docs] def tempo(self): """Return a list of integer values describing the tempo or range.""" nodes = iter(self) result = [] for i in nodes: if isinstance(i, Duration): for i in nodes: if isinstance(i, Scheme): v = i.get_int() if v is not None: result.append(v) elif isinstance(i, Number): result.append(i.value()) return result
[docs]class TimeSignature(Item): r"""A \time command.""" _num = 4 _fraction = Fraction(1, 4) _beatstructure = None
[docs] def measure_length(self): """The length of one measure in this time signature as a Fraction.""" return self._num * self._fraction
[docs] def numerator(self): """The upper number (e.g. for 3/2 it returns 3).""" return self._num
[docs] def fraction(self): """The lower number as a Fraction (e.g. for 3/2 it returns 1/2).""" return self._fraction
[docs] def beatstructure(self): """The scheme expressions denoting the beat structure, if specified.""" return self._beatstructure
[docs]class Partial(Item): r"""A \partial command.""" duration = 0, 1
[docs] def partial_length(self): """Return the duration given as argument as a Fraction.""" base, scaling = self.duration return base * scaling
[docs]class Clef(Item): r"""A \clef item.""" _specifier = None
[docs] def specifier(self): if isinstance(self._specifier, String): return self._specifier.value() return self._specifier
[docs]class KeySignature(Item): r"""A \key pitch \mode command."""
[docs] def pitch(self): """The ly.pitch.Pitch that denotes the pitch.""" for i in self.find(Note): return i.pitch
[docs] def mode(self): """The mode, e.g. "major", "minor", etc.""" for i in self.find(Command): return i.token[1:]
[docs]class PipeSymbol(Item): r"""A pipe symbol: |"""
[docs]class VoiceSeparator(Item): r"""A voice separator: \\"""
[docs]class Postfix(Item): """Any item that is prefixed with a _, - or ^ direction token."""
[docs]class Tie(Item): """A tie."""
[docs]class Slur(Item): """A ( or ).""" event = None
[docs]class PhrasingSlur(Item): r"""A \( or \).""" event = None
[docs]class Beam(Item): """A [ or ].""" event = None
[docs]class Dynamic(Item): """Any dynamic symbol."""
[docs]class Articulation(Item): """An articulation, fingering, string number, or other symbol."""
[docs]class StringTuning(Item): r"""A \stringTuning command (with a chord as argument)."""
[docs]class Keyword(Item): """A LilyPond keyword."""
[docs]class Command(Item): """A LilyPond command."""
[docs]class UserCommand(Music): """A user command, most probably referring to music."""
[docs] def name(self): """Return the name of this user command (without the leading backslash).""" return self.token[1:]
[docs] def value(self): """Find the value assigned to this variable.""" for i in self.iter_toplevel_items_include(): if isinstance(i, Assignment) and i.name() == self.name(): return i.value()
[docs] def events(self, e, time, scaling): """Let the event.Events instance handle the events. Return the time.""" value = self.value() if value: time = e.traverse(value, time, scaling) return time
[docs]class Version(Item): r"""A \version command."""
[docs] def version_string(self): """The version as a string.""" for i in self: if isinstance(i, String): return i.value() elif isinstance(i, Scheme): return i.get_string() return ''
[docs] def version(self): """The version as a tuple of ints.""" return tuple(map(int, re.findall(r'\d+', self.version_string())))
[docs]class Include(Item): r"""An \include command (not changing the language)."""
[docs] def filename(self): """Returns the filename.""" for i in self: if isinstance(i, String): return i.value() elif isinstance(i, Scheme): return i.get_string()
[docs]class Language(Item): r"""A command (\language or certain \include commands) that changes the pitch language.""" language = None
[docs]class Markup(Item): r"""A command starting markup (\markup, -lines and -list)."""
[docs] def plaintext(self): """Return the plain text value of this node.""" return ' '.join(n.plaintext() for n in self)
[docs]class MarkupCommand(Item): r"""A markup command, such as \italic etc."""
[docs] def plaintext(self): """Return the plain text value of this node.""" if self.token == '\\concat': joiner = '' #elif 'column' in self.token: #joiner = '\n' else: joiner = ' ' if len(self) == 1 and isinstance(self[0], MarkupList): node = self[0] else: node = self return joiner.join(n.plaintext() for n in node)
[docs]class MarkupUserCommand(Item): """A user-defined markup command"""
[docs] def name(self): """Return the name of this user command (without the leading backslash).""" return self.token[1:]
[docs] def value(self): """Find the value assigned to this variable.""" for i in self.iter_toplevel_items_include(): if isinstance(i, Assignment) and i.name() == self.name(): return i.value() elif isinstance(i, Scheme): for j in i: if isinstance(j, SchemeList): for k in j: if isinstance(k, SchemeItem) and k.token == 'define-markup-command': for l in j[1::]: if isinstance(l, SchemeList): for m in l: if isinstance(m, SchemeItem) and m.token == self.name(): return i break break break break
[docs]class MarkupScore(Item): r"""A \score inside Markup."""
[docs]class MarkupList(Item): r"""The group of markup items inside { and }. NOTE: *not* a \markuplist."""
[docs] def plaintext(self): """Return the plain text value of this node.""" return ' '.join(n.plaintext() for n in self)
[docs]class MarkupWord(Item): """A MarkupWord token."""
[docs] def plaintext(self): return self.token
[docs]class Assignment(Item): """A variable = value construct."""
[docs] def name(self): """The variable name.""" return self.token
[docs] def value(self): """The assigned value.""" if len(self): return self[-1]
[docs]class Book(Container): r"""A \book { ... } construct."""
[docs]class BookPart(Container): r"""A \bookpart { ... } construct."""
[docs]class Score(Container): r"""A \score { ... } construct."""
[docs]class Paper(Container): r"""A \paper { ... } construct."""
[docs]class Layout(Container): r"""A \layout { ... } construct."""
[docs]class Midi(Container): r"""A \midi { ... } construct."""
[docs]class LayoutContext(Container): r"""A \context { ... } construct within Layout or Midi."""
[docs]class With(Container): r"""A \with ... construct."""
[docs]class Set(Item): r"""A \set command."""
[docs] def context(self): """The context, if specified.""" for t in self.tokens: if isinstance(t, lilypond.ContextName): return t
[docs] def property(self): """The property.""" for t in self.tokens: if isinstance(t, lilypond.ContextProperty): return t for t in self.tokens[::-1]: if isinstance(t, lilypond.Name): return t
[docs] def value(self): """The value, given as argument. This is simply the child element.""" for i in self: return i
[docs]class Unset(Item): """An \\unset command."""
[docs] def context(self): """The context, if specified.""" for t in self.tokens: if isinstance(t, lilypond.ContextName): return t
[docs] def property(self): """The property.""" for t in self.tokens: if isinstance(t, lilypond.ContextProperty): return t for t in self.tokens[::-1]: if isinstance(t, lilypond.Name): return t
[docs]class Override(Item): r"""An \override command."""
[docs] def context(self): for i in self: if isinstance(i.token, lilypond.ContextName): return i.token
[docs] def grob(self): for i in self: if isinstance(i.token, lilypond.GrobName): return i.token
[docs]class Revert(Item): r"""A \revert command."""
[docs] def context(self): for i in self: if isinstance(i.token, lilypond.ContextName): return i.token
[docs] def grob(self): for i in self: if isinstance(i.token, lilypond.GrobName): return i.token
[docs]class Tweak(Item): r"""A \tweak command."""
[docs]class PathItem(Item): r"""An item in the path of an \override or \revert command."""
[docs]class String(Item): """A double-quoted string."""
[docs] def plaintext(self): """Return the plaintext value of this string, without escapes and quotes.""" # TEMP use the value(), must become token independent. return self.value()
[docs] def value(self): return ''.join( t[1:] if isinstance(t, lex.Character) and t.startswith('\\') else t for t in self.tokens[:-1])
[docs]class Number(Item): """A numerical value, directly entered."""
[docs] def value(self): if isinstance(self.token, lilypond.IntegerValue): return int(self.token) elif isinstance(self.token, lilypond.DecimalValue): return float(self.token) elif isinstance(self.token, lilypond.Fraction): return Fraction(self.token) elif self.token.isdigit(): return int(self.token)
[docs]class Scheme(Item): """A Scheme expression inside LilyPond."""
[docs] def plaintext(self): """A crude way to get the plain text in this node.""" # TEMP use get_string() return self.get_string()
[docs] def get_pair_ints(self): """Very basic way to get two integers specified as a pair.""" result = [int(i.token) for i in self.find(SchemeItem) if i.token.isdigit()] if len(result) >= 2: return tuple(result[:2])
[docs] def get_list_ints(self): """A basic way to get a list of integer values.""" return [int(i.token) for i in self.find(SchemeItem) if i.token.isdigit()]
[docs] def get_int(self): """A basic way to get one integer value.""" for i in self.find(SchemeItem): if i.token.isdigit(): return int(i.token)
[docs] def get_fraction(self): """A basic way to get one (may be fractional) numerical value.""" for i in self.find(SchemeItem): if i.token.isdigit(): return int(i.token) elif isinstance(i.token, scheme.Fraction): return Fraction(i.token)
[docs] def get_string(self): """A basic way to get a quoted string value (without the quotes).""" return ''.join(i.value() for i in self.find(String))
[docs] def get_ly_make_moment(self): """A basic way to get a ly:make-moment fraction.""" tokens = [i.token for i in self.find(SchemeItem)] if len(tokens) == 3 and tokens[0] == 'ly:make-moment': if tokens[1].isdigit() and tokens[2].isdigit(): return Fraction(int(tokens[1]), int(tokens[2]))
[docs]class SchemeItem(Item): """Any scheme token."""
[docs]class SchemeList(Container): """A ( ... ) expression."""
[docs]class SchemeQuote(Item): """A ' in scheme."""
[docs]class SchemeLily(Container): """A music expression inside #{ and #}."""